การสตรีมจากไคลเอ็นต์และแบบสองทิศทาง
สร้างช่องสตรีมจากไคลเอ็นต์และช่องสตรีมแบบสองทิศทางเต็มรูปแบบสำหรับสถานการณ์ที่ต้องรองรับปริมาณข้อมูลสูง
การสตรีมจากไคลเอ็นต์และแบบสองทิศทาง เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- พื้นฐาน gRPC และ Protobuf
- RPC แบบเอกภาพและการสตรีมจากเซิร์ฟเวอร์
- การสตรีมจากไคลเอ็นต์และแบบสองทิศทาง
- เส้นตาย การยกเลิก และตัวดักจับ