前面的章節我們介紹了 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;
}
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 _);
}
}
}
}
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/