0Pricing
C# Academy · Lekcja

Strumieniowanie klienta i dwukierunkowe

Twórz kanały strumieniowania po stronie klienta oraz pełnego dupleksu dla scenariuszy wymagających dużej przepustowości.

Strumieniowanie klienta i dwukierunkowe to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.

Strumieniowanie po stronie klienta i dwukierunkowe

Oprócz RPC typu unary i strumieniowania po stronie serwera gRPC obsługuje strumieniowanie po stronie klienta (klient wysyła wiele komunikatów, a serwer odpowiada raz) oraz strumieniowanie dwukierunkowe (obie strony wysyłają wiele komunikatów jednocześnie w ramach jednego połączenia).

Strumieniowanie po stronie klienta: definicja proto

Dodaj słowo kluczowe stream przed typem żądania. Klient wysyła sekwencję komunikatów, a po zakończeniu serwer zwraca pojedynczą odpowiedź.

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; }

Implementowanie strumieniowania po stronie klienta na serwerze

Użyj IAsyncStreamReader<T>, aby odczytywać komunikaty klienta w miarę ich nadejścia. Wywołuj MoveNext() lub iteruj za pomocą 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 };
}

Wywoływanie strumieniowania po stronie klienta

Otwórz wywołanie strumieniowe, wysyłaj komunikaty za pomocą RequestStream.WriteAsync(), a następnie zasygnalizuj zakończenie za pomocą CompleteAsync(). Oczekuj na odpowiedź.

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");

Strumieniowanie dwukierunkowe: definicja proto

Dodaj stream po obu stronach, aby uzyskać komunikację pełnodupleksową. Obie strony mogą wysyłać komunikaty w dowolnym momencie, niezależnie od siebie.

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

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

Implementowanie strumieniowania dwukierunkowego na serwerze

Odczytuj z IAsyncStreamReader i zapisuj do IServerStreamWriter równolegle. Użyj Task.WhenAll, aby uruchomić obie pętle jednocześnie.

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);
}

Strumieniowanie dwukierunkowe po stronie klienta

Wysyłaj i odbieraj równolegle, uruchamiając dwa zadania — jedno do zapisywania komunikatów, a drugie do odczytywania odpowiedzi.

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;

Sterowanie przepływem i backpressure

HTTP/2 ma wbudowane sterowanie przepływem. Jeśli odbiorca działa wolno, gRPC automatycznie wstrzymuje nadawcę, zapobiegając przepełnieniu pamięci. Nie należy ręcznie buforować komunikatów; należy pozwolić, aby zajął się tym strumień.

// 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

Częściowe zamknięcie i ukończenie strumienia

W strumieniowaniu dwukierunkowym każda ze stron może zamknąć swoją część zapisu, nadal odczytując dane od drugiej strony. Dzięki temu klient może zasygnalizować „zakończono wysyłanie”, nadal oczekując na odpowiedzi serwera.

// 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);
}

Używanie kanałów do bezpiecznego wątkowo strumieniowania dwukierunkowego

Połącz System.Threading.Channels ze strumieniowaniem dwukierunkowym, aby uzyskać przejrzysty wzorzec producent-konsument, który bezpiecznie rozsyła komunikaty do wielu odbiorców.

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);
}

Praktyczny przykład: potok analityki w czasie rzeczywistym

Usługa pozyskiwania danych przyjmuje strumień zdarzeń telemetrycznych z urządzeń IoT i w czasie rzeczywistym przesyła z powrotem zagregowane statystyki — to idealny przypadek użycia strumieniowania dwukierunkowego.

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 });
    }
}

Szybkie sprawdzenie

Kiedy serwer wysyła swoją pojedynczą odpowiedź w strumieniowaniu po stronie klienta?

Podsumowanie: strumieniowanie po stronie klienta i dwukierunkowe

Najważniejsze informacje:

  • Strumieniowanie po stronie klienta: klient wysyła wiele komunikatów, a serwer wysyła jedną odpowiedź — idealne do przesyłania plików
  • Strumieniowanie dwukierunkowe: obie strony wysyłają dane niezależnie — idealne do czatów i kanałów danych na żywo
  • Użyj ReadAllAsync() do odbierania strumieni za pomocą await foreach
  • Wywołaj RequestStream.CompleteAsync(), aby częściowo zamknąć stronę zapisu klienta
  • Sterowanie przepływem HTTP/2 automatycznie obsługuje backpressure — nie należy buforować całych strumieni
  • System.Threading.Channels dobrze współdziała ze strumieniowaniem dwukierunkowym w scenariuszach rozsyłania komunikatów do wielu odbiorców

Często zadawane pytania

Czy lekcja „Strumieniowanie klienta i dwukierunkowe” jest bezpłatna?

Tak — pełny tekst „Strumieniowanie klienta i dwukierunkowe” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Strumieniowanie klienta i dwukierunkowe”?

Twórz kanały strumieniowania po stronie klienta oraz pełnego dupleksu dla scenariuszy wymagających dużej przepustowości. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć C# Academy?

Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Strumieniowanie klienta i dwukierunkowe”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?

Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Podstawy gRPC i Protobuf
  2. Jednoargumentowe i strumieniowane RPC po stronie serwera
  3. Strumieniowanie klienta i dwukierunkowe
  4. Limity czasu, anulowanie i interceptory
← Powrót do C# Academy