Construct a Easy Chat Server With gRPC in .Internet Core

On this article, we are going to create a easy concurrent gRPC chat server software. We’ll use .NET Core, a cross-platform, open-source, and modular framework, to construct our chat server software. We’ll cowl the next matters:
- A short introduction to gRPC.
- Establishing the gRPC setting and defining the service contract.
- Implementing the chat service and dealing with consumer requests.
- Dealing with a number of purchasers concurrently utilizing asynchronous programming
- Broadcasting chat messages to all related purchasers in the identical room.
By the tip of this tutorial, you should have an understanding of methods to use gRPC to construct a chat server.
What Is gRPC?
gRPC is an acronym that stands for Google Distant Process Calls. It was initially developed by Google and is now maintained by the Cloud Native Computing Basis (CNCF). gRPC permits you to join, invoke, function, and debug distributed heterogeneous purposes as simply as making a neighborhood perform name.
gRPC makes use of HTTP/2 for transport, a contract-first method to API improvement, protocol Buffers (protobuf) because the interface definition language in addition to its underlying message interchange format. It could actually help 4 forms of API (Unary RPC, Server streaming RPC, Consumer streaming RPC, and Bidirectional streaming RPC). You may learn extra about gRPC here.
Getting Began:
Earlier than we begin to write code, an set up of .NET core must be finished, and be sure you have the next stipulations in place:
- Visible Studio Code, Visible Studio, or JetBrains Rider IDE.
- .NET Core.
- gRPC .NET
- Protobuf
Step 1: Create a gRPC Challenge From the Visible Studio or Command Line
- You need to use the next command to create a brand new venture. If profitable, it is best to have it created within the listing you specify with the title ‘ChatServer.’
dotnet new grpc -n ChatServerApp
- Open the venture together with your chosen editor. I’m utilizing visible studio for Mac.
Step 2: Outline the Protobuf Messages in a Proto File
Protobuf Contract:
- Create .proto file named server.proto inside the protos folder. The proto file is used to outline the construction of the service, together with the message varieties and the strategies that the service helps.
syntax = "proto3";
choice csharp_namespace = "ChatServerApp.Protos";
package deal chat;
service ChatServer
// Bidirectional communication stream between consumer and server
rpc HandleCommunication(stream ClientMessage) returns (stream ServerMessage);
//Consumer Messages:
message ClientMessage
oneof content material
ClientMessageLogin login = 1;
ClientMessageChat chat = 2;
message ClientMessageLogin
string chat_room_id = 1;
string user_name = 2;
message ClientMessageChat
string textual content = 1;
//Server Messages
message ServerMessage
oneof content material
ServerMessageLoginSuccess login_success = 1;
ServerMessageLoginFailure login_failure = 2;
ServerMessageUserJoined user_joined = 3;
ServerMessageChat chat = 4;
message ServerMessageLoginFailure
string cause = 1;
message ServerMessageLoginSuccess
message ServerMessageUserJoined
string user_name = 1;
message ServerMessageChat
string textual content = 1;
string user_name = 2;
ChatServer
defines the principle service of our chat software, which features a single RPC technique known asHandleCommunication
. The tactic is used for bidirectional streaming between the consumer and the server. It takes a stream ofClientMessage
as enter and returns a stream ofServerMessage
as output.
service ChatServer
// Bidirectional communication stream between consumer and server
rpc HandleCommunication(stream ClientMessage) returns (stream ServerMessage);
ClientMessageLogin
, which might be despatched by the consumer, has two fields known as chat_room_id and user_name. This message sort is used to ship login info from the consumer to the server. Thechat_room_id
subject specifies the chat room that the consumer needs to affix, whereas theuser_name
subject specifies the username that the consumer needs to make use of within the chat room
message ClientMessageLogin
string chat_room_id = 1;
string user_name = 2;
ClientMessageChat
which might be used to ship chat messages from the consumer to the server. It comprises a single subjecttextual content
.
message ClientMessageChat
string textual content = 1;
ClientMessage
defines the various kinds of messages {that a} consumer can ship to the server. It comprises a oneof subject, which signifies that solely one of many fields will be set at a time. if you happen to useoneof
, the generated C# code will include an enumeration indicating which fields have been set. The sphere names are “login
” and “chat
“which corresponds to theClientMessageLogin
andClientMessageChat
messages respectively
message ClientMessage
oneof content material
ClientMessageLogin login = 1;
ClientMessageChat chat = 2;
ServerMessageLoginFailure
defines the message despatched by the server to point {that a} consumer didn’t log in to the chat room. The explanation subject specifies the rationale for the failure.
message ServerMessageLoginFailure
string cause = 1;
-
ServerMessageLoginSuccess
defines the message despatched by the server to point {that a} consumer has efficiently logged in to the chat room. It comprises no fields and easily indicators that the login was profitable. When a consumer sends aClientMessageLogin
message, the server will reply with both aServerMessageLoginSuccess
message or aServerMessageLoginFailure
message, relying on whether or not the login was profitable or not. If the login was profitable, the consumer can then begin to shipClientMessageChat
messages to begin chat messages.
message ServerMessageLoginSuccess
- Message
ServerMessageUserJoined
defines the message despatched by the server to the consumer when a brand new person joins the chat room.
message ServerMessageUserJoined
string user_name = 1;
- Message
ServerMessageChat
defines the message despatched by the server to point {that a} new chat message has been obtained. Thetextual content
subject specifies the content material of the chat message, and theuser_name
subject specifies the username of the person who despatched the message.
message ServerMessageChat
string textual content = 1;
string user_name = 2;
- Message
ServerMessage
defines the various kinds of messages that may be despatched from the server to the consumer. It comprises aoneof
subject named content material with a number of choices. The sphere names are “login_success
,” “login_failure
,” “user_joined
,” and “chat
,” which correspond to theServerMessageLoginSuccess
,ServerMessageLoginFailure
,ServerMessageUserJoined
, andServerMessageChat
messages, respectively.
message ServerMessage
oneof content material
ServerMessageLoginSuccess login_success = 1;
ServerMessageLoginFailure login_failure = 2;
ServerMessageUserJoined user_joined = 3;
ServerMessageChat chat = 4;
Step 3: Add a ChatService
Class
Add a ChatService
class that’s derived from ChatServerBase
(generated from the server.proto file utilizing the gRPC codegen protoc). We then override the HandleCommunication
technique. The implementation of the HandleCommunication
technique might be chargeable for dealing with the communication between the consumer and the server.
public class ChatService : ChatServerBase
personal readonly ILogger<ChatService> _logger;
public ChatService(ILogger<ChatService> logger)
_logger = logger;
public override Process HandleCommunication(IAsyncStreamReader<ClientMessage> requestStream, IServerStreamWriter<ServerMessage> responseStream, ServerCallContext context)
return base.HandleCommunication(requestStream, responseStream, context);
Step 4: Configure gRPC
In program.cs file:
utilizing ChatServer.Providers;
utilizing Microsoft.AspNetCore.Server.Kestrel.Core;
var builder = WebApplication.CreateBuilder(args);
/*
// Further configuration is required to efficiently run gRPC on macOS.
// For directions on methods to configure Kestrel and gRPC purchasers on macOS,
// go to https://go.microsoft.com/fwlink/?linkid=2099682
To keep away from lacking ALPN help situation on Mac. To work round this situation, configure Kestrel and the gRPC consumer to make use of HTTP/2 with out TLS.
It is best to solely do that throughout improvement. Not utilizing TLS will end in gRPC messages being despatched with out encryption.
https://be taught.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-7.0
*/
builder.WebHost.ConfigureKestrel(choices =>
// Setup a HTTP/2 endpoint with out TLS.
choices.ListenLocalhost(50051, o => o.Protocols =
HttpProtocols.Http2);
);
// Add companies to the container.
builder.Providers.AddGrpc();
builder.Providers.AddSingleton<ChatRoomService>();
var app = builder.Construct();
// Configure the HTTP request pipeline.
app.MapGrpcService<ChatService>();
app.MapGet("https://dzone.com/", () => "Communication with gRPC endpoints have to be made by a gRPC consumer. To discover ways to create a consumer, go to: https://go.microsoft.com/fwlink/?linkid=2086909");
Console.WriteLine($"gRPC server about to listening on port:50051");
app.Run();
Be aware: ASP.NET Core gRPC template and samples use TLS by default. However for improvement functions, we configure Kestrel and the gRPC consumer to make use of HTTP/2 with out TLS.
Step 5: Create a ChatRoomService
and Implement Numerous Strategies Wanted in HandleCommunication
The ChatRoomService
class is chargeable for managing chat rooms and purchasers, in addition to dealing with messages despatched between purchasers. It makes use of a ConcurrentDictionary
to retailer chat rooms and a listing of ChatClient
objects for every room. The AddClientToChatRoom
technique provides a brand new consumer to a chat room, and the BroadcastClientJoinedRoomMessage
technique sends a message to all purchasers within the room when a brand new consumer joins. The BroadcastMessageToChatRoom
technique sends a message to all purchasers in a room apart from the sender of the message.
The ChatClient
class comprises a StreamWriter
object for writing messages to the consumer, in addition to a UserName property for figuring out the consumer.
utilizing System;
utilizing ChatServer;
utilizing Grpc.Core;
utilizing System.Collections.Concurrent;
namespace ChatServer.Providers
{
public class ChatRoomService
{
personal static readonly ConcurrentDictionary<string, Record<ChatClient>> _chatRooms = new ConcurrentDictionary<string, Record<ChatClient>>();
/// <abstract>
/// Learn a single message from the consumer.
/// </abstract>
/// <exception cref="ConnectionLostException"></exception>
/// <exception cref="TimeoutException"></exception>
public async Process<ClientMessage> ReadMessageWithTimeoutAsync(IAsyncStreamReader<ClientMessage> requestStream, TimeSpan timeout)
CancellationTokenSource cancellationTokenSource = new();
cancellationTokenSource.CancelAfter(timeout);
attempt
bool moveNext = await requestStream.MoveNext(cancellationTokenSource.Token);
if (moveNext == false)
throw new Exception("connection dropped exception");
return requestStream.Present;
catch (RpcException ex) when (ex.StatusCode == StatusCode.Cancelled)
throw new TimeoutException();
/// <abstract>
/// <abstract>
/// </abstract>
/// <param title="chatRoomId"></param>
/// <param title="person"></param>
/// <returns></returns>
public async Process AddClientToChatRoom(string chatRoomId, ChatClient chatClient)
if (!_chatRooms.ContainsKey(chatRoomId))
_chatRooms[chatRoomId] = new Record<ChatClient> chatClient ;
else
var existingUser = _chatRooms[chatRoomId].FirstOrDefault(c => c.UserName == chatClient.UserName);
if (existingUser != null)
// A person with the identical person title already exists within the chat room
throw new InvalidOperationException("Consumer with the identical title already exists within the chat room");
_chatRooms[chatRoomId].Add(chatClient);
await Process.CompletedTask;
/// <abstract>
/// Broad consumer joined the room message.
/// </abstract>
/// <param title="userName"></param>
/// <param title="chatRoomId"></param>
/// <returns></returns>
public async Process BroadcastClientJoinedRoomMessage(string userName, string chatRoomId)
if (_chatRooms.ContainsKey(chatRoomId))
var message = new ServerMessage UserJoined = new ServerMessageUserJoined UserName = userName ;
var duties = new Record<Process>();
foreach (var stream in _chatRooms[chatRoomId])
if (stream != null && stream != default)
duties.Add(stream.StreamWriter.WriteAsync(message));
await Process.WhenAll(duties);
/// <abstract>
/// </abstract>
/// <param title="chatRoomId"></param>
/// <param title="senderName"></param>
/// <param title="textual content"></param>
/// <returns></returns>
public async Process BroadcastMessageToChatRoom(string chatRoomId, string senderName, string textual content)
if (_chatRooms.ContainsKey(chatRoomId))
var message = new ServerMessage Chat = new ServerMessageChat UserName = senderName, Textual content = textual content ;
var duties = new Record<Process>();
var streamList = _chatRooms[chatRoomId];
foreach (var stream in _chatRooms[chatRoomId])
//This senderName will be one thing of distinctive Id for every person.
if (stream != null && stream != default && stream.UserName != senderName)
duties.Add(stream.StreamWriter.WriteAsync(message));
await Process.WhenAll(duties);
}
public class ChatClient
public IServerStreamWriter<ServerMessage> StreamWriter get; set;
public string UserName get; set;
}
Step 6: Lastly, Implement the gRPC HandleCommunication
Technique in Step 3
The HandleCommunication
receives a requestStream
from the consumer and sends a responseStream
again to the consumer. The tactic reads a message from the consumer, extracts the username and chatRoomId
, and handles two instances: a login case and a chat case.
- Within the login case, the strategy checks if the username and
chatRoomId
are legitimate and sends a response message to the consumer accordingly. If the login is profitable, the consumer is added to the chat room, and a broadcast message is shipped to all purchasers within the chat room. - Within the chat case, the strategy broadcasts the message to all purchasers within the chat room.
utilizing System;
utilizing ChatServer;
utilizing Grpc.Core;
namespace ChatServer.Providers
{
public class ChatService : ChatServer.ChatServerBase
{
personal readonly ILogger<ChatService> _logger;
personal readonly ChatRoomService _chatRoomService;
public ChatService(ChatRoomService chatRoomService, ILogger<ChatService> logger)
_chatRoomService = chatRoomService;
_logger = logger;
public override async Process HandleCommunication(IAsyncStreamReader<ClientMessage> requestStream, IServerStreamWriter<ServerMessage> responseStream, ServerCallContext context)
{
var userName = string.Empty;
var chatRoomId = string.Empty;
whereas (true)
{
//Learn a message from the consumer.
var clientMessage = await _chatRoomService.ReadMessageWithTimeoutAsync(requestStream, Timeout.InfiniteTimeSpan);
swap (clientMessage.ContentCase)
case ClientMessage.ContentOneofCase.Login:
var loginMessage = clientMessage.Login;
//get username and chatRoom Id from clientMessage.
chatRoomId = loginMessage.ChatRoomId;
userName = loginMessage.UserName;
if (string.IsNullOrEmpty(userName)
}
}
}
}
Full venture listing:
That’s all for half 1. Within the subsequent half 2, I’ll create a consumer venture with the consumer implementation to finish this chat software.