Что такое Bidirectional streaming в gRPC?

«Что такое Bidirectional streaming в gRPC?» — вопрос из категории Сети, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Bidirectional streaming (двунаправленная потоковая передача) — это один из четырех типов вызовов в gRPC, при котором и клиент, и сервер могут отправлять последовательность сообщений друг другу асинхронно через одно установленное сетевое соединение. Это позволяет организовать полноценный двусторонний диалог.

Как это выглядит в .proto-файле:

service ChatService {
  // Клиент отправляет поток MessageRequest, сервер отвечает потоком MessageResponse
  rpc StartConversation (stream MessageRequest) returns (stream MessageResponse);
}

message MessageRequest {
  string text = 1;
}

message MessageResponse {
  string text = 1;
  int64 timestamp = 2;
}

Типичные сценарии использования:

  • Чат-приложения и онлайн-игры (обмен сообщениями в реальном времени).
  • Синхронизация данных (клиент отправляет изменения, сервер отправляет подтверждения и обновления).
  • Real-time мониторинг и дашборды (сервер пушит обновления метрик, клиент может отправлять запросы на изменение интервала).

Пример реализации сервера на C#:

public override async Task StartConversation(
    IAsyncStreamReader<MessageRequest> requestStream,
    IServerStreamWriter<MessageResponse> responseStream,
    ServerCallContext context)
{
    // Задача 1: Чтение потока запросов от клиента
    var readTask = Task.Run(async () =>
    {
        await foreach (var message in requestStream.ReadAllAsync())
        {
            Console.WriteLine($"Client said: {message.Text}");
            // Можно обработать сообщение и, например, отправить ответ в другой задаче
        }
    });

    // Задача 2: Отправка потока ответов клиенту (например, broadcast уведомлений)
    var writeTask = Task.Run(async () =>
    {
        int count = 0;
        while (!context.CancellationToken.IsCancellationRequested)
        {
            await responseStream.WriteAsync(new MessageResponse
            {
                Text = $"Server notification #{count++}",
                Timestamp = DateTime.UtcNow.Ticks
            });
            await Task.Delay(TimeSpan.FromSeconds(5), context.CancellationToken);
        }
    });

    await Task.WhenAll(readTask, writeTask); // Ожидаем завершения обеих задач
}

Ключевые преимущества:

  • Эффективность: Одно долгоживущее соединение вместо множества отдельных HTTP-запросов (как в REST).
  • Гибкость и асинхронность: Клиент и сервер могут отправлять данные независимо, без блокировки на ожидание ответа.
  • Реальное время: Идеально подходит для сценариев с низкой задержкой и постоянным обменом данными.