.NET Core Websocket-based online chat room

.NET Core Websocket-based online chat room

What is Websocket

The first technology we think of in the traditional client program to achieve real-time duplex communication is socket communication, but socket communication technology cannot be used in the web system, because http is designed to be stateless, and every time it communicates with the server, it Will be disconnected. Before there was no websocket, the web system often used http long polling technology to do duplex communication. http long polling Every time a request is sent to the server, the server will not immediately return information to end the request, but will hang until there is data to return or wait for the timeout to return. The client sends another request immediately after finishing the last request, and so on. Although http long polling can realize the duplex communication of the web system, there is a big problem, that is, the client based on the http protocol needs to carry a huge header every time it sends a request. It is not cost-effective when a small amount of data is exchanged concurrently, and the consumption of server resources is also huge. websocket improves the above problems very well. It has redesigned a set of protocols based on tcp and is compatible with http. It uses port 80/443 by default like http. The establishment of a websocket link is essentially an http request. The upgrade header of the http protocol is used directly to identify that this is a websocket request. The server responds with a 101 status code to indicate that the "handshake" is successful.

//Client request
GET/HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

//Server response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/

Use asp.net core to handle websocket

Above we briefly understood websocket, then how to use asp.net core to handle websocket? Because the websocket handshake is an http request, then we can use a middleware to intercept the websocket request and manage the established links uniformly. In fact, Microsoft has already helped us simply encapsulate it.

Create a new asp.net core website

New WebsocketHandlerMiddleware middleware

This middleware is the entry point for us to manage the websocket link. We call the context.WebSockets.AcceptWebSocketAsync() method to convert the request into a websocket link.

Receive the websocket link in the Invoke method

      public async Task Invoke(HttpContext context)
        {
            if (context.Request.Path == "/ws")
            {
                if (context.WebSockets.IsWebSocketRequest)
                {
                    WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
                    string clientId = Guid.NewGuid().ToString();;
                    var wsClient = new WebsocketClient
                    {
                        Id = clientId,
                        WebSocket = webSocket
                    };
                    try
                    {
                        await Handle(wsClient);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Echo websocket client {0} err .", clientId);
                        await context.Response.WriteAsync("closed");
                    }
                }
                else
                {
                    context.Response.StatusCode = 404;
                }
            }
            else
            {
                await _next(context);
            }
        }

在Hanle方法等待客户端的消息

        private async Task Handle(WebsocketClient webSocket)
        {
            WebsocketClientCollection.Add(webSocket);
            _logger.LogInformation($"Websocket client added.");
           
            WebSocketReceiveResult result = null;
            do
            {
                var buffer = new byte[1024 * 1];
                result = await webSocket.WebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                if (result.MessageType == WebSocketMessageType.Text && !result.CloseStatus.HasValue)
                {
                    var msgString = Encoding.UTF8.GetString(buffer);
                    _logger.LogInformation($"Websocket client ReceiveAsync message {msgString}.");
                    var message = JsonConvert.DeserializeObject<Message>(msgString);
                    message.SendClientId = webSocket.Id;
                    MessageRoute(message);
                }
            }
            while (!result.CloseStatus.HasValue);
            WebsocketClientCollection.Remove(webSocket);
            _logger.LogInformation($"Websocket client closed.");
        }

Forward the client's message in the MessageRoute method

Define several standard actions for client messages, and perform specific processing on different actions, such as joining a room, leaving the room, broadcasting a message in the room, and so on.

private void MessageRoute(Message message)
        {
            var client = WebsocketClientCollection.Get(message.SendClientId);
            switch (message.action)
            {
                case "join":
                    client.RoomNo = message.msg;
                    client.SendMessageAsync($"{message.nick} join room {client.RoomNo} success .");
                    _logger.LogInformation($"Websocket client {message.SendClientId} join room {client.RoomNo}.");
                    break;
                case "send_to_room":
                    if (string.IsNullOrEmpty(client.RoomNo))
                    {
                        break;
                    }
                    var clients = WebsocketClientCollection.GetRoomClients(client.RoomNo);
                    clients.ForEach(c =>
                    {
                        c.SendMessageAsync(message.nick + ":" + message.msg);
                    });
                    _logger.LogInformation($"Websocket client {message.SendClientId} send message {message.msg} to room {client.RoomNo}");

                    break;
                case "leave":
                    var roomNo = client.RoomNo;
                    client.RoomNo = "";
                    client.SendMessageAsync($"{message.nick} leave room {roomNo} success .");
                    _logger.LogInformation($"Websocket client {message.SendClientId} leave room {roomNo}");
                    break;
                default:
                    break;
            }
        }

New WebsocketClientCollection management class

This class is a container used to store all websocket links for unified management.

    public class WebsocketClientCollection
    {
        private static List<WebsocketClient> _clients = new List<WebsocketClient>();

        public static void Add(WebsocketClient client)
        {
            _clients.Add(client);
        }

        public static void Remove(WebsocketClient client)
        {
            _clients.Remove(client);
        }

        public static WebsocketClient Get(string clientId)
        {
            var client = _clients.FirstOrDefault(c=>c.Id == clientId);

            return client;
        }

        public static List<WebsocketClient> GetRoomClients(string roomNo)
        {
            var client = _clients.Where(c => c.RoomNo == roomNo);
            return client.ToList();
        }
    }

Use middleware in Startup

With the above middleware, we need to use it.

  app.UseWebSockets(new WebSocketOptions
            {
                KeepAliveInterval = TimeSpan.FromSeconds(60),
                ReceiveBufferSize = 1* 1024
            });
  app.UseMiddleware<WebsocketHandlerMiddleware>();

At this point, our server is basically completed, let's write the client html and JavaScript.

Write the client interface

Modify index.cshtml to implement a simple chat room ui.

<div style="margin-bottom:5px;">
    room no: <input type="text" id="txtRoomNo" value="8888"/> <button id="btnJoin">join room</button> <button id="btnLeave">leave room</button>
</div>
<div style="margin-bottom:5px;">
    nick name: <input type="text" id="txtNickName" value="batman"/> 
</div>
<div style="height:300px;width:600px">
    <textarea style="height:100%;width:100%" id="msgList"></textarea>
    <div style="text-align: right">
        <input type="text" id="txtMsg" value=""/> <button id="btnSend">send</button>
    </div>
</div>

Use JavaScript to process websocket links and messages

Modern browsers already support the websocket protocol, and the JavaScript runtime has a built-in WebSocket class. We only need to create a new Websocket object to operate on the websocket.

var server ='ws://localhost:5000';//If https is turned on, this is wss

var WEB_SOCKET = new WebSocket(server +'/ws');

WEB_SOCKET.onopen = function (evt) {
    console.log('Connection open ...');
    $('#msgList').val('websocket connection opened .');
};

WEB_SOCKET.onmessage = function (evt) {
    console.log('Received Message: '+ evt.data);
    if (evt.data) {
        var content = $('#msgList').val();
        content = content +'\r\n' + evt.data;

        $('#msgList').val(content);
    }
};

WEB_SOCKET.onclose = function (evt) {
    console.log('Connection closed.');
};

$('#btnJoin').on('click', function () {
    var roomNo = $('#txtRoomNo').val();
    var nick = $('#txtNickName').val();
    if (roomNo) {
        var msg = {
            action:'join',
            msg: roomNo,
            nick: nick
        };
        WEB_SOCKET.send(JSON.stringify(msg));
    }
});

$('#btnSend').on('click', function () {
    var message = $('#txtMsg').val();
    var nick = $('#txtNickName').val();
    if (message) {
        WEB_SOCKET.send(JSON.stringify({
            action:'send_to_room',
            msg: message,
            nick: nick
        }));
    }
});

$('#btnLeave').on('click', function () {
    var nick = $('#txtNickName').val();
    var msg = {
        action:'leave',
        msg:'',
        nick: nick
    };
    WEB_SOCKET.send(JSON.stringify(msg));
});

run

So far our chat room has been set up, let's run it to see the effect. We launch two pages to chat. You can see that our message is forwarded in real time, good job!

Source code

The source code has been uploaded to github CoreWebsocketChatRoom

Reference: https://cloud.tencent.com/developer/article/1601605 .NET Core Websocket based online chat room-Cloud + Community-Tencent Cloud