NET7下用WebSocket做简易聊天室

发布时间 2023-10-20 11:49:46作者: 牛腩

NET7下用WebSocket做简易聊天室

 

步骤:

 

  1. 建立NET7的MVC视图模型控制器项目
  2. 创建websocket之间通信的JSON字符串对应的实体类
  3. 一个房间用同一个Websocket
  4. websocket集合类,N个房间
  5. 创建websocket中间件代码
  6. Program.cs中的核心代码,使用Websocket
  7. 聊天室HTML页面代码

 

 

参考文章:https://blog.csdn.net/xiaouncle/article/details/109509860
GIT源码地址:https://niunan.coding.net/public/WebSocketDemo/WebSocketDemo/git/files (里面还有以前做的.NET FRAMEWORK的websocket示例代码)
 

 

  1. 建立NET7的MVC视图模型控制器项目
  2. 创建websocket之间通信的JSON字符串对应的实体类
    namespace NetCore.WebSocketDemo.Models
    {
        /// <summary>
        /// websocket之间通信的JSON字符串转的实体类
        /// </summary>
        public class Message
        {
            /// <summary>
            /// websocket对应的ID
            /// </summary>
            public string SendClientId { set; get; }
            /// <summary>
            /// 加入房间join 发送消息send_to_room 离开房间levea
            /// </summary>
            public string action { set; get; }
            /// <summary>
            /// 房间号
            /// </summary>
            public string roomNo { set; get; }
            /// <summary>
            /// 昵称
            /// </summary>
            public string nick { set; get; }
            /// <summary>
            /// 发送的消息内容 
            /// </summary>
            public string msg { set; get; }
        }
    }

     



  3. 一个房间用同一个Websocket
    using System.Net.Sockets;
    using System.Net.WebSockets;
    using System.Text;
    
    namespace NetCore.WebSocketDemo.Models
    {
        /// <summary>
        /// 一个房间里的都用这个websocket
        /// </summary>
        public class WebsocketClient
        {
            public string Id { set; get; }
            public string RoomNo { set; get; }
            public WebSocket WebSocket { set; get; }
    
            public async Task SendMessageAsync(string text)
            {
                var recvBytes = Encoding.UTF8.GetBytes(text);
                var sendBuffer = new ArraySegment<byte>(recvBytes);
    
                try
                {
                    await WebSocket.SendAsync(sendBuffer, WebSocketMessageType.Text, true, CancellationToken.None);
                }
                catch (Exception ex)
                {
                    throw ex;
                } 
            }
        }
    }
    

      

  4. websocket集合类,N个房间
    namespace NetCore.WebSocketDemo.Models
    {
        /// <summary>
        /// websocket集合类,N个房间
        /// </summary>
        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> GetAll()
            {
                return _clients;
            }
    
            public static List<WebsocketClient> GetClientsByRoomNo(string roomNo)
            {
                var client = _clients.Where(c => c.RoomNo == roomNo);
                return client.ToList();
            }
        }
    }

     

  5. 创建websocket中间件代码
    using Newtonsoft.Json;
    using System.Net.WebSockets;
    using System.Text;
    
    namespace NetCore.WebSocketDemo.Models
    {
        /// <summary>
        /// programe里用 app.UseWebsocketHandlerMiddleware();
        /// </summary>
        public static class WebsocketHandlerMiddlewareExtensions
        {
            public static IApplicationBuilder UseWebsocketHandlerMiddleware(
                this IApplicationBuilder builder)
            {
                return builder.UseMiddleware<WebsocketHandlerMiddleware>();
            }
        }
        /// <summary>
        /// websocket中间件
        /// </summary>
        public class WebsocketHandlerMiddleware  
        {
    
            private readonly RequestDelegate _next;
     
     
    
            public WebsocketHandlerMiddleware(  RequestDelegate next)
            {
              
                _next = next;
            } 
    
            public async Task InvokeAsync(HttpContext context)
            {
                if (context.Request.Path == "/ws")
                {
                    //仅当网页执行new WebSocket("ws://localhost:5000/ws")时,后台会执行此逻辑
                    if (context.WebSockets.IsWebSocketRequest)
                    {
                        //后台成功接收到连接请求并建立连接后,前台的webSocket.onopen = function (event){}才执行
                        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)
                        {
                     
                            await context.Response.WriteAsync("closed");
                        }
                    }
                    else
                    {
                        context.Response.StatusCode = 404;
                    }
                }
                else
                {
                    await _next(context);
                }
            }
    
            private async Task Handle(WebsocketClient websocketClient)
            {
                WebsocketClientCollection.Add(websocketClient);
               
    
                WebSocketReceiveResult clientData = null;
                do
                {
                    var buffer = new byte[1024 * 1];
                    //客户端与服务器成功建立连接后,服务器会循环异步接收客户端发送的消息,收到消息后就会执行Handle(WebsocketClient websocketClient)中的do{}while;直到客户端断开连接
                    //不同的客户端向服务器发送消息后台执行do{}while;时,websocketClient实参是不同的,它与客户端一一对应
                    //同一个客户端向服务器多次发送消息后台执行do{}while;时,websocketClient实参是相同的
                    clientData = await websocketClient.WebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                    if (clientData.MessageType == WebSocketMessageType.Text && !clientData.CloseStatus.HasValue)
                    {
                        var msgString = Encoding.UTF8.GetString(buffer);
                      
                        var message = JsonConvert.DeserializeObject<Message>(msgString);
                        message.SendClientId = websocketClient.Id;
                        HandleMessage(message);
                    }
                } while (!clientData.CloseStatus.HasValue);
                //关掉使用WebSocket连接的网页/调用webSocket.close()后,与之对应的后台会跳出循环
                WebsocketClientCollection.Remove(websocketClient);
                
            }
    
            private void HandleMessage(Message message)
            {
                var client = WebsocketClientCollection.Get(message.SendClientId);
                switch (message.action)
                {
                    case "join":
                        client.RoomNo = message.roomNo;
                        client.SendMessageAsync($"{message.nick} join room {client.RoomNo} success .");
                        break;
                    case "send_to_room":
                        if (string.IsNullOrEmpty(client.RoomNo))
                        {
                            break;
                        }
                        var clients = WebsocketClientCollection.GetClientsByRoomNo(client.RoomNo);
                        clients.ForEach(c =>
                        {
                            c.SendMessageAsync(message.nick + " : " + message.msg);
                        });
                        break;
                    case "leave":
                        #region 通过把连接的RoomNo置空模拟关闭连接
                        var roomNo = client.RoomNo;
                        client.RoomNo = "";
                        #endregion
    
                        #region 后台关闭连接
                        //client.WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                        //WebsocketClientCollection.Remove(client); 
                        #endregion
    
                        client.SendMessageAsync($"{message.nick} leave room {roomNo} success .");
                        break;
                    default:
                        break;
                }
            }
        }
    }

     

  6. Program.cs中的核心代码,使用Websocket
    var app = builder.Build();
    
                #region 配置中间件,使用websocket
                app.UseWebSockets(new WebSocketOptions
                {
                    KeepAliveInterval = TimeSpan.FromSeconds(60),
                    ReceiveBufferSize = 1 * 1024
                });
                app.UseWebsocketHandlerMiddleware(); 
                #endregion

     

  7. 聊天室HTML页面代码
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>简易websocket聊天室应用</title>
    </head>
    <body>
        <div style="margin-bottom:5px;">
            房间号: <input type="text" id="txtRoomNo" value="99999" />
            <button id="btnJoin">加入房间</button>
            <button id="btnLeave">离开房间</button>
            <button id="btnDisConnect">断开链接</button>
        </div>
        <div style="margin-bottom:5px;">
            我的昵称: <input type="text" id="txtNickName" value="niunan" />
        </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="" placeholder="请输入您要发送的文本消息" />  <button id="btnSend">发送</button>
            </div>
        </div>
        <script src="lib/jquery/dist/jquery.min.js"></script>
        <script>
            var webSocket = new WebSocket("ws://localhost:5160/ws");
            //前台向后台发送连接请求,后台成功接收并建立连接后才会触发此事件
            webSocket.onopen = function (event) {
                console.log("Connection opened...");
                $("#msgList").val("WebSocket connection opened");
            };
    
            //后台向前台发送消息,前台成功接收后会触发此事件
            webSocket.onmessage = function (event) {
                console.log("Received message: " + event.data);
                if (event.data) {
                    var content = $('#msgList').val();
                    content = content + '\r\n' + event.data;
                    $('#msgList').val(content);
                }
            };
    
            //后台关闭连接后/前台关闭连接后都会触发此事件
            webSocket.onclose = function (event) {
                console.log("Connection closed...");
                var content = $('#msgList').val();
                content = content + '\r\nWebSocket connection closed';
                $('#msgList').val(content);
            };
    
            $('#btnJoin').on('click', function () {
                var roomNo = $('#txtRoomNo').val();
                var nick = $('#txtNickName').val();
                if (!roomNo) {
                    alert("请输入RoomNo");
                    return;
                }
                var msg = {
                    action: 'join',
                    roomNo: roomNo,
                    nick: nick
                };
                if (CheckWebSocketConnected(webSocket)) {
                    webSocket.send(JSON.stringify(msg));
                }
            });
    
            $('#btnSend').on('click', function () {
                var message = $('#txtMsg').val();
                var nick = $('#txtNickName').val();
                if (!message) {
                    alert("请输入发生的内容");
                    return;
                }
                if (CheckWebSocketConnected(webSocket)) {
                    webSocket.send(JSON.stringify({
                        action: 'send_to_room',
                        msg: message,
                        nick: nick
                    }));
                }
            });
    
            $('#btnLeave').on('click', function () {
                var nick = $('#txtNickName').val();
                var msg = {
                    action: 'leave',
                    roomNo: '',
                    nick: nick
                };
                if (CheckWebSocketConnected(webSocket)) {
                    webSocket.send(JSON.stringify(msg));
                }
            });
    
            $("#btnDisConnect").on("click", function () {
                if (CheckWebSocketConnected(webSocket)) {
                    //部分浏览器调用close()方法关闭WebSocket时不支持传参
                    //webSocket.close(001, "closeReason");
                    webSocket.close();
                }
            });
    
            //判断当前websocket的状态
            /*
            CONNECTING:值为0,表示正在连接。
            OPEN:值为1,表示连接成功,可以通信。
            CLOSING:值为2,表示连接正在关闭。
            CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
            */
            function CheckWebSocketConnected(ws) {
                var b = false;
                switch (ws.readyState) {
                    case WebSocket.CONNECTING:    // 也可以用0
                        // do something
                        break;
                    case WebSocket.OPEN:            // 也可以用1
                        // do something
                        b = true;
                        break;
                    case WebSocket.CLOSING:        // 也可以用2
                        // do something
                        break;
                    case WebSocket.CLOSED:        // 也可以用3
                        // do something
                        break;
                    default:
                        // this never happens
                        break;
                }
                return b;
            }
        </script>
    </body>
    </html>