Потоковая передача от клиента и двунаправленная передача
Создавайте каналы потоковой передачи от клиента и полнодуплексной двунаправленной передачи для сценариев с высокой пропускной способностью.
«Потоковая передача от клиента и двунаправленная передача» — бесплатный урок C# Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.
Потоковая передача от клиента и двунаправленная потоковая передача
Помимо унарных вызовов и потоковой передачи от сервера, gRPC поддерживает потоковую передачу от клиента (клиент отправляет множество сообщений, а сервер отвечает один раз) и двунаправленную потоковую передачу (обе стороны одновременно отправляют несколько сообщений через одно соединение).
Потоковая передача от клиента: определение Proto
Добавьте ключевое слово 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");Двунаправленная потоковая передача: определение Proto
Добавьте 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 хорошо сочетается с двунаправленной потоковой передачей в сценариях распределения сообщений
Часто задаваемые вопросы
Урок «Потоковая передача от клиента и двунаправленная передача» бесплатный?
Да — полный текст урока «Потоковая передача от клиента и двунаправленная передача» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.
Чему я научусь в уроке «Потоковая передача от клиента и двунаправленная передача»?
Создавайте каналы потоковой передачи от клиента и полнодуплексной двунаправленной передачи для сценариев с высокой пропускной способностью. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать C# Academy?
Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Потоковая передача от клиента и двунаправленная передача»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке C# Academy?
Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Основы gRPC и Protobuf
- Унарные вызовы и потоковая передача от сервера
- Потоковая передача от клиента и двунаправленная передача
- Крайние сроки, отмена и перехватчики