结合实际需求,在webapi内利用WebSocket建立单向的消息推送平台

1.需求示意图

2.需求描述

原本是为了给做unity3d客户端开发的同事提供不定时的消息推送,比如商城购买道具后服务端将道具信息推送给客户端。

本篇文章简化理解,用“相关部门开展活动,向全市人民征集社会服务改善意见”为例子。但核心想法一致:单向推送(指这个需求上只需要单向)。所以这个功能并不是聊天室,即便websocket技术是做双向通信的,但在本需求中不需要核心页面和客户端之间互相通信。核心界面只和服务端建立WebSocket连接,推送消息全部来自其他地方。

只有核心页面和服务端建立WebSocket连接,其他市民们都是通过web开发者耳熟能详的http协议在发送消息,不是市民们和部门公告栏玩WebSocket互动

3.代码如下,复制即可使用(webapi跨域的代码不演示)

①WebSocket帮助类,负责建立连接和推送消息

using System;using System.Collections.Generic;using System.Linq;using System.Net.WebSockets;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Web;using System.Web.WebSockets;namespace WSTest{    public class WSHelper    {        /// <summary>        /// 保存客户端的WebSocket对象        /// </summary>        private static readonly Dictionary<string, WebSocket> dicSockets = new Dictionary<string, WebSocket>();        #region 构建线程安全的单例模式        private static WSHelper _instance;        private WSHelper()        {        }        public static WSHelper GetInstance()        {            if (_instance == null)            {                lock (dicSockets)                {                    if (_instance == null)                    {                        _instance = new WSHelper();                    }                }            }            return _instance;        }        #endregion        /// <summary>        /// 和客户端建立WebSocket连接        /// </summary>        /// <param name="arg">客户端发送的WebSocket相关信息</param>        /// <returns></returns>        public async Task ProcessWSChat(AspNetWebSocketContext arg)        {            // 1.获取请求的客户端WebSocket对象            WebSocket socket = arg.WebSocket;            // 2.获取自定义的参数            string adminUserKey = arg.QueryString["adminUserKey"];            if (string.IsNullOrEmpty(adminUserKey)) return;            // 3.将用户编号作为标识客户端唯一性的Key,保存客户端的WebSocket对象            dicSockets[adminUserKey] = socket;            while (true)            {                ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[1024 * 10]);                WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None);                try                {                    if (socket.State != WebSocketState.Open)                    {                        dicSockets.Remove(adminUserKey);                        break;                    }                }                catch                {                    break;                }            }        }        /// <summary>        /// 服务端向客户端推送消息        /// </summary>        public bool SendMsg(string message, string adminUserKey)        {            WebSocket socket = null;            if (dicSockets.ContainsKey(adminUserKey))            {                socket = dicSockets[adminUserKey];            }            else            {                return false;            }            //【重要】执行下面socket.State代码可能会抛异常"无法访问已经释放的对象",            // 因为客户端已经处于断电、断网、强制关闭、刷新等状态,当前的WebSocket对象已经失去价值,直接删除即可            try            {                if (socket.State == WebSocketState.Open)                {                    ArraySegment<byte> buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(message));                    socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);                    return true;                }            }            catch            {                dicSockets.Remove(adminUserKey);                return false;            }            return false;        }    }}

WSHelper

②webapi的控制器,负责建立WebSocket连接

using System.Net;using System.Net.Http;using System.Web;using System.Web.Http;namespace WSTest.Controllers{    [RoutePrefix("WebSocketConn")]    public class WebSocketConnController : ApiController    {        /// <summary>        /// 创建websocket连接        /// </summary>        [HttpGet]        [Route("GetConnect")]        public HttpResponseMessage GetConnect()        {            if (HttpContext.Current.IsWebSocketRequest)            {                HttpContext.Current.AcceptWebSocketRequest(WSHelper.GetInstance().ProcessWSChat);            }            return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);        }    }}

WebSocketConnController

③webapi的业务控制器,征集意见

using System;using System.Collections.Generic;using System.Linq;using System.Net;using System.Net.Http;using System.Web.Http;namespace WSTest.Controllers{    /// <summary>    /// 市民服务    /// </summary>    [RoutePrefix("CitizenService")]    public class CitizenServiceController : ApiController    {        /// <summary>        /// 市民意见征集        /// </summary>        [HttpGet]        [Route("GiveOpinion")]        public string GiveOpinion(string userName, string msg, string sendTo)        {            //1.发送消息给客户端            string sendMsg = string.Format("热心市民{0}有话要说:{1}", userName, msg);            bool result = WSHelper.GetInstance().SendMsg(sendMsg, sendTo);            //2.接收结果,若发送失败,可能客户端还未成功连接WebSocket            return result ? "已提交,您可以去相关部门的官网查看刚发送的信息了。" : "相关部门的平台还没开放,请耐心等待";        }    }}

CitizenServiceController

④测试用部门公告栏页面【核心页面】

<!DOCTYPE html><html><head>    <title>教育局的市民意见征集布告栏</title></head><script src="https://code.jquery.com/jquery-3.1.1.min.js"></script><body>    <div id="titleMsg"></div>    <div id="msgMenu">        来自市民的话:<br>    </div>    <script type="text/javascript">        var webSocket;        var msgCount = 1;        //HTTP处理程序的地址        var handlerUrl = "ws://localhost:2465/WebSocketConn/GetConnect?adminUserKey=adminA";        $(function(){            InitWebSocket();        });        function CloseWebSocket() {            webSocket.close();            webSocket = undefined;        }         function InitWebSocket() {            //如果WebSocket对象未初始化,则初始化            if (webSocket == undefined) {                webSocket = new WebSocket(handlerUrl);                 //打开连接处理程序                webSocket.onopen = function () {                    //WebSocket连接成功                    $("#titleMsg").text("平台已开放,欢迎大家留言");                };                 //消息数据处理程序                webSocket.onmessage = function (e) {                    updMsgMenu(e.data);                };                 //关闭事件处理程序                webSocket.onclose = function () {                    //WebSocket断开连接                };                 //错误事件处理程序                webSocket.onerror = function (e) {                    updMsgMenu(e.message);                };            }            else {                //webSocket.open();没有open方法            }        }         function updMsgMenu(str){            var tempStr = $("#msgMenu").html();            tempStr = tempStr + msgCount + "." + str + "</br>";            msgCount++;            $("#msgMenu").html(tempStr);        }        function Clear(){            msgCount = 1;            $("#msgMenu").html("消息列表:<br>");        }     </script></body></html>

部门公告栏页面

⑤测试用市民意见征集页面

<!DOCTYPE html><html><head>    <title>市民意见征集平台</title></head><body>    您的姓名:<input type="text" id="userName" /><br>    您的意见:<textarea type="text" id="msg"></textarea><br>    您想给哪个部门留言:<select id="sendTo">        <option value="adminA">教育局</option>        <option value="adminB">社保局</option>        <option value="adminC">劳动局</option>    </select>    <input type="button" value="提交" onclick="doSend()" />    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>    <script>        var msgCount = 1;        function doSend(){            $.ajax({            url: "http://localhost:2465/CitizenService/GiveOpinion",            type: "GET",            data:{                userName: $("#userName").val(),                msg: $("#msg").val(),                sendTo: $("#sendTo").val()            },            cache: false,            dataType: "json",            success: function (res) {                console.log(res);                alert("收到消息:"+ res);            },            error: function (error) {                alert("服务端繁忙");            }        });        }    </script></body></html>

市民意见征集页面

4.运行如下

①教育部门开放了自己的平台,准备接收市民意见

②有市民向教育部门反馈问题

③公告栏收到及时推送的消息

5.总结

①只要核心页面断开了WebSocket连接(断电、断网、重启、刷新页面等),这次的WebSocket对象都不再有效。

②本案例的需求是市民们向部门反应意见,不需要做成聊天室类型的客户端互动。

③WSHelper.cs类中建立了线程安全的单例模式,目的是让所有用户访问到的字典集合对象唯一。

④案例缺点:核心页面断开连接时服务端没有去监听,因此服务端无法及时释放对象,对性能不友好,需要进一步改进。

(0)

相关推荐