1:WebSocket简介
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
大家可以查看菜鸟中的说明 https://www.runoob.com/html/html5-websocket.html
WebSocket可以通过JS与服务器端建立实时的双向通信,它主要包括:2个核心方法、4个监听函数、1个核心属性。
2个核心方法:
① send() 向远程服务器发送信息
② close() 关闭与远程服务器的连接
4个监听函数:
① onopen 当网络连接建立成功时触发该事件
② onerror 当网络发生错误时触发该事件
③ onclose 当网络连接被关闭时触发该事件
④ onmessage 当websocket接收到服务器发来的消息的时触发该事件
1个核心属性:readyState,它有四种状态
① WebSocket.OPEN:表示与服务器已经建立好连接
② WebSocket.CLOSED:表示与服务器连接已经断开
③ WebSocket.CONNECTING:表示正在尝试与服务建立连接
④ WebSocket.CLOSING:表示正在关闭与服务器连接
服务器端主要用到两个方法:
① SendAsync() 发送信息给客户端
② CloseAsync() 关闭该Socket连接:
2:主要code
js部分
@page @model IndexModel @{ ViewData["Title"] = "Home page"; } <div class="text-center"> <h1 class="display-4">Welcome</h1> <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p> </div> <div class="container"> <div> <table> <tr><td>ID:</td><td><input id="useid" type="text" placeholder="请输入人员ID" /></td></tr> <tr><td>昵称:</td><td><input id="usenike" type="text" placeholder="请输入人员昵称" /></td></tr> <tr><td><button id="btnJoin">join room</button> </td><td><button id="btnLeave">leave room</button></td></tr> <tr> <td> <input id="touid" type="text" placeholder="请输入接收人员ID" /> 消息:<input type="text" id="txtMsg" value="" /> </td> <td> <button id="btnSend">单聊</button> </td> </tr> <tr><td><button id="btnAllsend">群聊</button></td></tr> </table> <div> <textarea style="height:300px;width:400px" id="msgList"></textarea> </div> </div> </div> @section Scripts{ <script type="text/javascript"> $(function () { var server = 'ws://localhost:5000'; //如果开启了https则这里是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.onerror = function (evt) { if (evt) { console.log(evt.data); } }; WEB_SOCKET.onclose = function (evt) { console.log('Connection closed.'); console.log("=======" + evt); var content = $('#msgList').val(); content = content + '\r\n' + evt; $('#msgList').val(content); WEB_SOCKET.readyState = closed; }; //加入聊天室 $('#btnJoin').on('click', function () { var useid = $('#useid').val(); var nick = $('#usenike').val(); if (useid) { var msg = { Action: 7, RoomId: 0, Uid: useid, MsgInfo: nick + "加入到群聊" }; if (WEB_SOCKET == null || !WEB_SOCKET) { WEB_SOCKET.readyState = open(); //WEB_SOCKET = new WebSocket(server + '/ws'); } WEB_SOCKET.send(JSON.stringify(msg)); } }); //发送消息 $('#btnSend').on('click', function () { var useid = $('#useid').val(); var touid = $("#touid").val(); var usenike = $('#usenike').val(); var message = $('#txtMsg').val(); if (message) { WEB_SOCKET.send(JSON.stringify({ Action: 8,//8:单聊 MsgInfo: message, nick: usenike, ToUid: touid, Uid: useid })); } }); //群聊 $('#btnAllsend').on("click", function () { var useid = $('#useid').val(); var touid = $("#touid").val(); var usenike = $('#usenike').val(); var message = $('#txtMsg').val(); if (message) { WEB_SOCKET.send(JSON.stringify({ Action: 9,//9:群聊 MsgInfo: message, nick: usenike, //ToUid: touid, Uid: useid, RoomId: 0//群ID })); } }); $('#btnLeave').on('click', function () { var nick = $('#usenike').val(); var useid = $('#useid').val(); var msg = { Action: 10,//10:退出群聊 MsgInfo: nick + ",退出聊天室", nick: usenike, Uid: useid, RoomId: 0//群ID }; WEB_SOCKET.send(JSON.stringify(msg)); }); }) </script> }
中间件主要code
using System.Net.WebSockets; using System.Net.Http; using System.Text; using Newtonsoft.Json; namespace WebApp { public class WebSocketHandlerMiddleware : IMiddleware { /// <summary> /// /// </summary> /// <param name="context"></param> /// <param name="next"></param> /// <returns></returns> public async Task InvokeAsync(HttpContext context, RequestDelegate next) { if (context.Request.Path == "/ws") { if (context.WebSockets.IsWebSocketRequest) { try { WebSocket websocket = await context.WebSockets.AcceptWebSocketAsync(); if (websocket != null) { var wsclient = new WebSocketClient { Uid = "",// clientId, WebSocket = websocket }; await Handle(wsclient); } } catch (Exception ex) { await context.Response.WriteAsync(ex.Message); } } else { context.Response.StatusCode = 404; } } else { await next(context); } } private async Task Handle(WebSocketClient wsclient) { WebSocketClientCollection.AddClient(wsclient); WebSocketReceiveResult clientData = null; do { var buffer = new byte[1024 * 10]; clientData = await wsclient.WebSocket.ReceiveAsync(new ArraySegment<byte>(buffer, 0, buffer.Length), CancellationToken.None); if (clientData.MessageType == WebSocketMessageType.Text) { string msgStr = Encoding.UTF8.GetString(buffer, 0, buffer.Length); var useMsg = JsonConvert.DeserializeObject<UseMsg>(msgStr); useMsg.Uid = useMsg.Uid;//这里暂时使用系统的 guid wsclient.Uid = useMsg.Uid; await HandlessMessageAsync(useMsg);//除了具体的消息 } } while (!clientData.CloseStatus.HasValue); WebSocketClientCollection.RemoveClient(wsclient.Uid); //log一下 } private async Task HandlessMessageAsync(UseMsg useMsg) { switch (useMsg.Action) { case 0:; break; case 1:; break; case 2:; break; case 3:; break; case 4:; break; case 5:; break; case 6:; break; case 7://加入到房间 //写入到用户的房间表中,一个房间一行主数据 var client = WebSocketClientCollection.GetCLient(useMsg.Uid); await JoinToRoom(client, useMsg); ; break; case 8://单聊 //var buffer = new ArraySegment<byte>(); //await client.WebSocket.SendAsync(buffer, WebSocketMessageType.Text, WebSocketMessageFlags.EndOfMessage, CancellationToken.None) // ; break; var receClient = WebSocketClientCollection.GetCLient(useMsg.ToUid.ToString()); if (receClient != null) { string msgStr = useMsg.MsgInfo; byte[] by = Encoding.UTF8.GetBytes(msgStr); var buffer = new ArraySegment<byte>(by, 0, by.Length); await receClient.WebSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); } break; case 9://群聊 var receClients = WebSocketClientCollection.GetAllClient(); if (receClients != null) { string msgStr9 = useMsg.MsgInfo; byte[] by9 = Encoding.UTF8.GetBytes(msgStr9); var buffer9 = new ArraySegment<byte>(by9, 0, by9.Length); foreach (var item in receClients)//也可以排除发送者自己 { await item.WebSocket.SendAsync(buffer9, WebSocketMessageType.Text, true, CancellationToken.None); } } break; case 10://离开聊天室 string get_uid = useMsg.Uid.ToString(); var receClient10 = WebSocketClientCollection.GetCLient(get_uid); if (receClient10 != null) { //string msgStr10 = useMsg.MsgInfo; //byte[] by10 = Encoding.UTF8.GetBytes(msgStr10); //var buffer10 = new ArraySegment<byte>(by10, 0, by10.Length); //await receClient10.WebSocket.SendAsync(buffer10, WebSocketMessageType.Text, true, CancellationToken.None); WebSocketClientCollection.RemoveClient(receClient10.Uid); await receClient10.WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure,string.Empty, CancellationToken.None); } ; break; default: break; } } private async Task<bool> JoinToRoom(WebSocketClient client, UseMsg msg) { string uid = msg.Uid; if (client!=null) client.Uid = uid; if (WebSocketClientCollection.GetCLient(uid) == null) WebSocketClientCollection.AddClient(client); string msgStr = msg.MsgInfo; byte[] by = Encoding.UTF8.GetBytes(msgStr); var buffer = new ArraySegment<byte>(by, 0, by.Length); //所有人收到消息 var clients = WebSocketClientCollection.GetAllClient(); if (clients != null) { clients = clients.Where(c => !string.IsNullOrEmpty(c.Uid)).ToList(); foreach (var c in clients) { await c.WebSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); } } return true; } } }
入口处相关code
using WebApp; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddScoped<WebSocketHandlerMiddleware>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseWebSockets(); app.UseMiddleware<WebSocketHandlerMiddleware>(); app.UseAuthorization(); app.MapControllers(); app.Run();
相关模型实体
namespace WebApp { public class UseMsg { /// <summary> /// 系统当前聊天用户的ID /// </summary> public string Uid { get; set; } /// <summary> ///默认:人员ID /// </summary> public int ToUid { get; set; } /// <summary> ///当前 选择群或者叫房间的ID /// </summary> public int RoomId { get; set; } /// <summary> /// 消息类型,默认0:文字,1:图片,2:视频;3:音频;4:震动;5:离线消息;6:系统表情包(如图片,gif等);7:加入聊天室;8:单聊;9:群聊;10:离开 /// </summary> public int Action { get; set; } public string MsgInfo { get; set; } } }
using System.Net.WebSockets; namespace WebApp { public class WebSocketClient { public string Uid { get; set; } /// <summary> /// 当时链接的socket /// </summary> public WebSocket WebSocket { get; set; } } }
using System.Linq; namespace WebApp { public class WebSocketClientCollection { private static List<WebSocketClient> _client = new List<WebSocketClient>(); public static void AddClient(WebSocketClient client) { var clientNow = _client.Where(c => c.Uid == client.Uid).FirstOrDefault(); if (clientNow != null) _client.Remove(client); _client.Add(client); } public static void RemoveClient(string clientId) { var clientNow = _client.Where(c => c.Uid == clientId).FirstOrDefault(); if (clientNow != null) _client.Remove(clientNow); } public static WebSocketClient GetCLient(string clientId) { return _client.FirstOrDefault(c => c.Uid == clientId); } public static List<WebSocketClient> GetAllClient() { return _client; } //public static List<WebSocketClient> GetAllGroupClient(List<int> groupIds) //{ // var clientsNow = _client.Where(c =>c.ro); // return clientsNow; //} } }
最后附上测试效果
到此先告一个段落