0Pricing
C# Academy · บทเรียน

การสตรีมจากไคลเอ็นต์และแบบสองทิศทาง

สร้างช่องสตรีมจากไคลเอ็นต์และช่องสตรีมแบบสองทิศทางเต็มรูปแบบสำหรับสถานการณ์ที่ต้องรองรับปริมาณข้อมูลสูง

การสตรีมจากไคลเอ็นต์และแบบสองทิศทาง เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C# Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

การสตรีมจากไคลเอ็นต์และการสตรีมสองทิศทาง

นอกเหนือจาก RPC แบบคำขอเดียวและการสตรีมจากเซิร์ฟเวอร์แล้ว gRPC ยังรองรับ การสตรีมจากไคลเอ็นต์ (ไคลเอ็นต์ส่งหลายข้อความ แล้วเซิร์ฟเวอร์ตอบกลับครั้งเดียว) และ การสตรีมสองทิศทาง (ทั้งสองฝั่งส่งหลายข้อความพร้อมกันผ่านการเชื่อมต่อเดียว)

การสตรีมจากไคลเอ็นต์: การกำหนดโพรโต

เพิ่มคีย์เวิร์ด stream ไว้หน้าชนิดข้อมูลคำขอ ไคลเอ็นต์จะส่งลำดับข้อความ และเมื่อส่งเสร็จแล้ว เซิร์ฟเวอร์จะคืนการตอบกลับเดียว

service UploadService {
  // Client streams chunks, server returns a summary
  rpc UploadFile (stream FileChunk) returns (UploadSummary);
}

message FileChunk   { bytes data = 1; string filename = 2; }
message UploadSummary { int64 bytes_received = 1; string checksum = 2; }

การนำการสตรีมจากไคลเอ็นต์ไปใช้บนเซิร์ฟเวอร์

ใช้ IAsyncStreamReader<T> เพื่ออ่านข้อความจากไคลเอ็นต์ทันทีที่มาถึง เรียก MoveNext() หรือวนซ้ำด้วย ReadAllAsync()

public override async Task<UploadSummary> UploadFile(
    IAsyncStreamReader<FileChunk> requestStream,
    ServerCallContext context)
{
    long totalBytes = 0;
    using var ms = new MemoryStream();

    await foreach (var chunk in requestStream.ReadAllAsync(context.CancellationToken))
    {
        await ms.WriteAsync(chunk.Data.Memory, context.CancellationToken);
        totalBytes += chunk.Data.Length;
    }

    var checksum = ComputeMd5(ms.ToArray());
    return new UploadSummary { BytesReceived = totalBytes, Checksum = checksum };
}

การเรียกการสตรีมจากไคลเอ็นต์

เปิดการเรียกแบบสตรีม เขียนข้อความด้วย RequestStream.WriteAsync() จากนั้นส่งสัญญาณว่าเสร็จสิ้นด้วย CompleteAsync() แล้วรอการตอบกลับ

using var call = client.UploadFile();

var fileBytes = await File.ReadAllBytesAsync("large-file.bin");
const int chunkSize = 64 * 1024; // 64 KB

for (int offset = 0; offset < fileBytes.Length; offset += chunkSize)
{
    var chunk = fileBytes.Skip(offset).Take(chunkSize).ToArray();
    await call.RequestStream.WriteAsync(new FileChunk
    {
        Filename = "large-file.bin",
        Data = Google.Protobuf.ByteString.CopyFrom(chunk)
    });
}

await call.RequestStream.CompleteAsync(); // signal end
var summary = await call;                // await server response
Console.WriteLine($"Uploaded {summary.BytesReceived} bytes");

การสตรีมสองทิศทาง: การกำหนดโพรโต

เพิ่ม stream ที่ทั้งสองฝั่งเพื่อให้สื่อสารแบบสองทางได้อย่างเต็มรูปแบบ ทั้งไคลเอ็นต์และเซิร์ฟเวอร์สามารถส่งข้อความได้ทุกเมื่อโดยแยกจากกัน

service ChatService {
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}

message ChatMessage {
  string user    = 1;
  string content = 2;
  int64  sent_at = 3;
}

การนำการสตรีมสองทิศทางไปใช้บนเซิร์ฟเวอร์

อ่านจาก IAsyncStreamReader และเขียนไปยัง IServerStreamWriter พร้อมกัน ใช้ Task.WhenAll เพื่อให้ลูปทั้งสองทำงานพร้อมกัน

public override async Task Chat(
    IAsyncStreamReader<ChatMessage> requestStream,
    IServerStreamWriter<ChatMessage> responseStream,
    ServerCallContext context)
{
    // Broadcast to all connected clients
    var readTask = Task.Run(async () =>
    {
        await foreach (var msg in requestStream.ReadAllAsync(context.CancellationToken))
        {
            _chatHub.Broadcast(msg);
        }
    });

    var writeTask = Task.Run(async () =>
    {
        await foreach (var msg in _chatHub.GetMessagesAsync(context.CancellationToken))
        {
            await responseStream.WriteAsync(msg);
        }
    });

    await Task.WhenAll(readTask, writeTask);
}

การสตรีมสองทิศทางจากไคลเอ็นต์

ส่งและรับข้อมูลพร้อมกันโดยเริ่มงานสองงาน — งานหนึ่งสำหรับเขียนข้อความ และอีกงานสำหรับอ่านข้อความตอบกลับ

using var call = client.Chat();

// Read task
var readTask = Task.Run(async () =>
{
    await foreach (var msg in call.ResponseStream.ReadAllAsync())
        Console.WriteLine($"{msg.User}: {msg.Content}");
});

// Write task
while (Console.ReadLine() is string text && text != "exit")
{
    await call.RequestStream.WriteAsync(
        new ChatMessage { User = "Alice", Content = text,
                          SentAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() });
}
await call.RequestStream.CompleteAsync();
await readTask;

การควบคุมการไหลและแรงดันย้อนกลับ

HTTP/2 มีการควบคุมการไหลในตัว หากผู้รับประมวลผลช้า gRPC จะหยุดผู้ส่งชั่วคราวโดยอัตโนมัติ เพื่อป้องกันหน่วยความจำล้น ไม่ควรบัฟเฟอร์ข้อความด้วยตนเอง ให้สตรีมจัดการเรื่องนี้

// DO: Let gRPC flow control handle backpressure
await foreach (var chunk in requestStream.ReadAllAsync(ct))
{
    await ProcessChunkAsync(chunk); // naturally paced
}

// DON'T: Buffering all chunks defeats flow control
var allChunks = new List<FileChunk>();
await foreach (var chunk in requestStream.ReadAllAsync(ct))
    allChunks.Add(chunk); // may OOM on large uploads

การปิดการเขียนครึ่งหนึ่งและการเสร็จสิ้นของสตรีม

ในการสตรีมสองทิศทาง แต่ละฝั่งสามารถปิดส่วนการเขียนของตนได้ ขณะเดียวกันก็ยังอ่านข้อความจากอีกฝั่งต่อไปได้ วิธีนี้ทำให้ไคลเอ็นต์ส่งสัญญาณว่า “ส่งข้อมูลเสร็จแล้ว” ขณะที่ยังรอการตอบกลับจากเซิร์ฟเวอร์

// Client signals it's done sending
await call.RequestStream.CompleteAsync(); // half-close

// Continue reading server responses after half-close
await foreach (var reply in call.ResponseStream.ReadAllAsync())
{
    Console.WriteLine(reply.Content);
}

การใช้แชนเนลสำหรับการสตรีมสองทิศทางที่ปลอดภัยต่อเธรด

ผสาน System.Threading.Channels เข้ากับการสตรีมสองทิศทาง เพื่อสร้างรูปแบบผู้ผลิต-ผู้บริโภคที่เรียบง่าย และกระจายข้อความไปยังผู้ฟังหลายรายได้อย่างปลอดภัย

private readonly Channel<ChatMessage> _broadcast =
    Channel.CreateUnbounded<ChatMessage>();

// Producer: receives from each client stream
public async Task ReceiveLoopAsync(
    IAsyncStreamReader<ChatMessage> reader, CancellationToken ct)
{
    await foreach (var msg in reader.ReadAllAsync(ct))
        await _broadcast.Writer.WriteAsync(msg, ct);
}

// Consumer: writes to each client's response stream
public async Task SendLoopAsync(
    IServerStreamWriter<ChatMessage> writer, CancellationToken ct)
{
    await foreach (var msg in _broadcast.Reader.ReadAllAsync(ct))
        await writer.WriteAsync(msg);
}

กรณีใช้งานจริง: ไปป์ไลน์การวิเคราะห์แบบเรียลไทม์

บริการรับข้อมูลจะรับสตรีมเหตุการณ์ทางไกลจากอุปกรณ์ IoT และสตรีมสถิติที่รวบรวมแล้วกลับมาแบบเรียลไทม์ ซึ่งเป็นกรณีใช้งานที่เหมาะอย่างยิ่งสำหรับการสตรีมสองทิศทาง

service TelemetryService {
  rpc StreamTelemetry(stream TelemetryEvent) returns (stream AggregatedStats);
}

// Server implementation sends a rolling aggregate every 100 events:
public override async Task StreamTelemetry(
    IAsyncStreamReader<TelemetryEvent> requests,
    IServerStreamWriter<AggregatedStats> responses,
    ServerCallContext context)
{
    int count = 0;
    double total = 0;
    await foreach (var e in requests.ReadAllAsync(context.CancellationToken))
    {
        total += e.Value;
        if (++count % 100 == 0)
            await responses.WriteAsync(
                new AggregatedStats { Count = count, Average = total / count });
    }
}

ตรวจสอบความเข้าใจ

ในการสตรีมจากไคลเอ็นต์ เซิร์ฟเวอร์จะส่งการตอบกลับเดียวเมื่อใด

สรุป: การสตรีมจากไคลเอ็นต์และการสตรีมสองทิศทาง

ประเด็นสำคัญ:

  • การสตรีมจากไคลเอ็นต์: ไคลเอ็นต์ส่งหลายข้อความ เซิร์ฟเวอร์ส่งการตอบกลับหนึ่งครั้ง — เหมาะสำหรับการอัปโหลดไฟล์
  • การสตรีมสองทิศทาง: ทั้งสองฝั่งส่งข้อมูลได้อย่างอิสระ — เหมาะสำหรับการสนทนาและฟีดข้อมูลแบบสด
  • ใช้ ReadAllAsync() เพื่อรับสตรีมด้วย await foreach
  • เรียก RequestStream.CompleteAsync() เพื่อปิดส่วนการเขียนของไคลเอ็นต์ครึ่งหนึ่ง
  • การควบคุมการไหลของ HTTP/2 จัดการแรงดันย้อนกลับโดยอัตโนมัติ — ไม่ควรบัฟเฟอร์สตรีมทั้งหมด
  • System.Threading.Channels ทำงานร่วมกับการสตรีมสองทิศทางได้ดีสำหรับกรณีที่ต้องกระจายข้อมูล

คำถามที่พบบ่อย

บทเรียน “การสตรีมจากไคลเอ็นต์และแบบสองทิศทาง” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การสตรีมจากไคลเอ็นต์และแบบสองทิศทาง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การสตรีมจากไคลเอ็นต์และแบบสองทิศทาง”

สร้างช่องสตรีมจากไคลเอ็นต์และช่องสตรีมแบบสองทิศทางเต็มรูปแบบสำหรับสถานการณ์ที่ต้องรองรับปริมาณข้อมูลสูง คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “การสตรีมจากไคลเอ็นต์และแบบสองทิศทาง” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม

ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. พื้นฐาน gRPC และ Protobuf
  2. RPC แบบเอกภาพและการสตรีมจากเซิร์ฟเวอร์
  3. การสตรีมจากไคลเอ็นต์และแบบสองทิศทาง
  4. เส้นตาย การยกเลิก และตัวดักจับ
← กลับไปที่ C# Academy