分享

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

 丹枫无迹 2021-03-24

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-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-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

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多