0Pricing
C# Academy · 강의

클라이언트와 양방향 스트리밍

처리량이 높은 시나리오를 위해 클라이언트 스트리밍과 전이중 양방향 스트리밍 채널을 구축합니다.

클라이언트와 양방향 스트리밍은(는) CoddyKit의 무료 C# Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C# Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

Client 스트리밍 및 양방향 스트리밍

단항 및 서버 스트리밍 외에도 gRPC는 client 스트리밍(client가 여러 개를 보내고 서버가 한 번 응답)과 양방향 스트리밍(양쪽이 하나의 connection에서 동시에 여러 메시지를 보냄)을 지원합니다.

Client 스트리밍: 프로토콜 정의

요청 형식 앞에 stream 키워드를 추가합니다. client는 메시지 시퀀스를 보내고, 완료되면 서버가 단일 응답을 반환합니다.

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

서버에서 Client 스트리밍 구현하기

도착하는 client 메시지를 읽으려면 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 };
}

Client에서 Client 스트리밍 호출하기

스트리밍 호출을 열고 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을 추가합니다. client와 서버는 서로 독립적으로 언제든지 메시지를 보낼 수 있습니다.

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

Client에서 양방향 스트리밍 사용하기

두 개의 작업을 시작하여 동시에 보내고 받으십시오. 하나는 메시지를 작성하고 다른 하나는 응답을 읽습니다.

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가 sender를 자동으로 일시 중지하여 메모리 초과를 방지합니다. 메시지를 수동으로 버퍼링하지 말고 스트림이 처리하도록 하십시오.

// 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는 서버 응답을 계속 기다리면서 "전송 완료"를 알릴 수 있습니다.

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

스레드 안전한 양방향 스트리밍에 Channels 사용하기

System.Threading.Channels을 양방향 스트리밍과 결합하면 여러 listener에 메시지를 안전하게 분배하는 깔끔한 생산자-소비자 패턴을 구현할 수 있습니다.

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 장치에서 telemetry 이벤트 스트림을 받아 집계된 통계를 실시간으로 다시 스트리밍합니다. 양방향 스트리밍을 사용하기에 매우 적합한 사례입니다.

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

빠른 확인

Client 스트리밍에서 서버는 언제 단일 응답을 보냅니까?

복습: Client 및 양방향 스트리밍

핵심 요점:

  • Client 스트리밍: client가 여러 메시지를 보내고 서버가 하나의 응답을 보냅니다 — 파일 업로드에 적합합니다
  • 양방향 스트리밍: 양쪽이 독립적으로 보냅니다 — 채팅과 실시간 데이터 피드에 적합합니다
  • await foreach와 함께 ReadAllAsync()를 사용하여 스트림을 소비합니다
  • RequestStream.CompleteAsync()를 호출하여 client 쓰기 측을 반쪽 닫기 상태로 만듭니다
  • HTTP/2 흐름 제어가 백프레셔를 자동으로 처리하므로 전체 스트림을 버퍼링하지 마십시오
  • System.Threading.Channels는 메시지 분배 시나리오에서 양방향 스트리밍과 잘 결합됩니다

자주 묻는 질문

“클라이언트와 양방향 스트리밍” 강의는 무료인가요?

네 — “클라이언트와 양방향 스트리밍” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C# Academy 강의 전체를 잠금 해제할 수 있습니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“클라이언트와 양방향 스트리밍”에서 뭘 배우나요?

처리량이 높은 시나리오를 위해 클라이언트 스트리밍과 전이중 양방향 스트리밍 채널을 구축합니다. 브라우저에서 직접 실행하는 실습 코드로 C# Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

C# Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 C# Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“클라이언트와 양방향 스트리밍” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 C# Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 C# Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. gRPC와 Protobuf 기초
  2. 단항 및 서버 스트리밍 RPC
  3. 클라이언트와 양방향 스트리밍
  4. 기한, 취소와 인터셉터
← C# Academy(으)로 돌아가기