一.背景
很多时候,当我们需要在 web 端需要实时获取 API 传过来的数据的时候(如统计数据的及时更新,后台消息推送以及即时通讯等),我们一般可以使用轮询的机制,但轮询
机制有一个比较大的问题就是,一个是会消耗更多的程序性能,二是无法真的实时(即使是1s一次的调用api获取数据,那样也会对系统造成很大压力)
为了保证系统性能和实时性,更多时候我们都是使用 websocket

二.后端实现
以下的代码是基于实时聊天的功能方式进行实现

    [RoutePrefix("api/home")]
    public class HomeController : ApiController
    {
        //websocket列表,用于记录在线websocket链接
        private static List webSockets = 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方法),同样,前端亦可做断线重连的处理