一.背景
很多时候,当我们需要在 web 端需要实时获取 API 传过来的数据的时候(如统计数据的及时更新,后台消息推送以及即时通讯等),我们一般可以使用轮询的机制,但轮询
机制有一个比较大的问题就是,一个是会消耗更多的程序性能,二是无法真的实时(即使是1s一次的调用api获取数据,那样也会对系统造成很大压力)
为了保证系统性能和实时性,更多时候我们都是使用 websocket
二.后端实现
以下的代码是基于实时聊天的功能方式进行实现
[RoutePrefix("api/home")] public class HomeController : ApiController { //websocket列表,用于记录在线websocket链接 private static ListwebSockets = new List (); [HttpGet] [Route("Connect")] public HttpResponseMessage Connect() { //在服务器端接受Web Socket请求,传入的函数作为Web Socket的处理函数,待Web Socket建立后该函数会被调用,在该函数中可以对Web Socket进行消息收发 HttpContext.Current.AcceptWebSocketRequest(ProcessRequest); return Request.CreateResponse(HttpStatusCode.SwitchingProtocols); //构造同意切换至Web Socket的Response. } public async Task ProcessRequest(AspNetWebSocketContext context) { var socket = context.WebSocket; //传入的context中有当前的web socket对象 //进入一个无限循环,当web socket close是循环结束 while (true) { var buffer = new ArraySegment (new byte[1024]); //对websocket进行异步接收数据 var receivedResult = await socket.ReceiveAsync(buffer, CancellationToken.None); //信息接收,我这边定义的消息结构为 userid:toUserid:content string recvMsg = Encoding.UTF8.GetString(buffer.Array, 0, receivedResult.Count); string userid = recvMsg.Split(':')[0]; string toUserid = recvMsg.Split(':')[1]; string content= recvMsg.Split(':')[2]; if (receivedResult.MessageType == WebSocketMessageType.Close) { //如果连接已关闭,则删除记录 await socket.CloseAsync(WebSocketCloseStatus.Empty, string.Empty, CancellationToken.None); //如果client发起close请求,对client进行ack webSockets.Remove(webSockets.FirstOrDefault(p => p.UserId == userid)); break; } if (socket.State == WebSocketState.Open) { string message =""; //此处将web socket对象加入一个静态列表中 var newWebsocket = webSockets.FirstOrDefault(p=>p.UserId==userid); if (newWebsocket==null) { webSockets.Add(new UserWebSocket { UserId = userid, WebSocket = socket }); } //组装返回的消息 message = userid + ":" + content; var recvBytes = Encoding.UTF8.GetBytes(message); var sendBuffer = new ArraySegment (recvBytes); //消息处理判断,也就是一开始进行连接的时候数据为空,我们不会发送数据到目标WebSocket if (content != "") { var toWebSocket = webSockets.FirstOrDefault(p => p.UserId == toUserid); if (toWebSocket != null) { try { //消息发送给目标websocket await toWebSocket.WebSocket.SendAsync(sendBuffer, WebSocketMessageType.Text, true, CancellationToken.None); } catch (WebSocketException e) { throw; } } } } } } }
三.使用
前端进行 ws 连接(/api/home/connect,可用在线的websocket网站进行测试),并根据规定的数据格式进行发送即可
如果需要后端主动发送数据,则直接获取目标 WebSocket连接对象 进行发送即可(SendAsync方法),同样,前端亦可做断线重连的处理