iT邦幫忙

2025 iThome 鐵人賽

DAY 11
1
Software Development

30 天的 .Net gRPC 迷途系列 第 11

Day11 gRPC Bi-directional streaming

  • 分享至 

  • xImage
  •  

前面的章節我們介紹了 Server/Client Stream ,接續著介紹雙向的部份

下面就用情境最常見的「聊天室」來介紹

.proto檔如下

syntax = "proto3";

// 記得根據 Server/Client 專案變更
option csharp_namespace = "GrpcServer";

package chat;

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

message ChatMessage {
  string user = 1;
  string text = 2;
  int64 timestamp = 3;
}

Server 端

public class ChatService : MyGrpcServer.ChatService.ChatServiceBase
{
    // 線上用戶清單 (保持每個 client 的 stream)
    private static readonly ConcurrentDictionary<string, IServerStreamWriter<ChatMessage>> _clients = new();

    public override async Task Chat(
        IAsyncStreamReader<ChatMessage> requestStream,
        IServerStreamWriter<ChatMessage> responseStream,
        ServerCallContext context)
    {
        string userName = null!;
        try
        {
            await foreach (var message in requestStream.ReadAllAsync(context.CancellationToken))
            {
                if (userName == null) // 第一次訊息 = 使用者登入
                {
                    userName = message.User;
                    _clients[userName] = responseStream;

                    // 廣播加入訊息
                    await BroadcastAsync(new ChatMessage
                    {
                        User = "System",
                        Text = $"{userName} 已加入聊天室",
                        Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
                    });
                }

                Console.WriteLine($"[{message.User}] {message.Text}");

                // 廣播使用者訊息
                await BroadcastAsync(message);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error from {userName}: {ex.Message}");
        }
        finally
        {
            if (userName != null)
            {
                _clients.TryRemove(userName, out _);

                // 廣播離線訊息
                await BroadcastAsync(new ChatMessage
                {
                    User = "System",
                    Text = $"{userName} 已離線",
                    Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
                });
            }
        }
    }

    private static async Task BroadcastAsync(ChatMessage message)
    {
        foreach (var client in _clients.Values)
        {
            try
            {
                await client.WriteAsync(message);
            }
            catch
            {
                // 找到對應的 username 並移除
                var disconnectedClient = _clients.FirstOrDefault(x => x.Value == client).Key;
                if (disconnectedClient != null)
                {
                    _clients.TryRemove(disconnectedClient, out _);
                }
            }
        }
    }

Client 端

static async Task Main(string[] args)
{
    Console.Write("請輸入你的暱稱: ");
    var user = Console.ReadLine() ?? "Anonymous";

    var channel = GrpcChannel.ForAddress("https://localhost:7204");
    var client = new ChatService.ChatServiceClient(channel);

    using var call = client.Chat();

    // 啟動接收伺服器訊息
    var readTask = Task.Run(async () =>
    {
        await foreach (var response in call.ResponseStream.ReadAllAsync())
        {
            Console.WriteLine($"[{response.User}] {response.Text}");
        }
    });

    // 持續輸入訊息直到 exit
    while (true)
    {
        var text = Console.ReadLine();
        if (string.Equals(text, "exit", StringComparison.OrdinalIgnoreCase))
        {
            break;
        }

        await call.RequestStream.WriteAsync(new ChatMessage
        {
            User = user,
            Text = text ?? "",
            Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
        });
    }

    await call.RequestStream.CompleteAsync();
    await readTask;

    Console.ReadKey();
}

以上就是一個很簡易的聊天室

參考:
https://behdadk.medium.com/net-grpc-simple-chat-application-with-grpc-6b65cdb7651
https://blog.darkthread.net/blog/grpc-chatroom-example/


上一篇
Day10 gRPC Client Stream 建立
下一篇
Day12 gRPC 驗證及授權
系列文
30 天的 .Net gRPC 迷途13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言