0Pricing
C# Academy · Урок

Потоковая передача от клиента и двунаправленная передача

Создавайте каналы потоковой передачи от клиента и полнодуплексной двунаправленной передачи для сценариев с высокой пропускной способностью.

«Потоковая передача от клиента и двунаправленная передача» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Основы gRPC и Protobuf
  2. Унарные вызовы и потоковая передача от сервера
  3. Потоковая передача от клиента и двунаправленная передача
  4. Крайние сроки, отмена и перехватчики
← Назад к C# Academy