NET7下用WebSocket做简易聊天室
步骤:
- 建立NET7的MVC视图模型控制器项目
- 创建websocket之间通信的JSON字符串对应的实体类
- 一个房间用同一个Websocket
- websocket集合类,N个房间
- 创建websocket中间件代码
- Program.cs中的核心代码,使用Websocket
- 聊天室HTML页面代码
参考文章:.NET Core中WebSocket的使用详解_.net websocket-CSDN博客
GIT源码地址:公开仓库 (里面还有以前做的.NET FRAMEWORK的websocket示例代码)
- 建立NET7的MVC视图模型控制器项目
- 创建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; } } }
- 一个房间用同一个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; } } } }
- 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(); } } }
- 创建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; } } } }
- Program.cs中的核心代码,使用Websocket
var app = builder.Build(); #region 配置中间件,使用websocket app.UseWebSockets(new WebSocketOptions { KeepAliveInterval = TimeSpan.FromSeconds(60), ReceiveBufferSize = 1 * 1024 }); app.UseWebsocketHandlerMiddleware(); #endregion
- 聊天室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>