zoukankan      html  css  js  c++  java
  • 结合实际需求,在webapi内利用WebSocket建立单向的消息推送平台,让A页面和服务端建立WebSocket连接,让其他页面可以及时给A页面推送消息

    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类中建立了线程安全的单例模式,目的是让所有用户访问到的字典集合对象唯一。

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

  • 相关阅读:
    记录C#开发遇到的问题和应用经验
    HttpApplication,HttpModule,HttpContext及Asp.Net页生命周期
    itextsharp.dll(4.0.8.0)完整示例PDF
    步步为营 .NET 设计模式学习笔记 二十三、Interpreter(解释器模式)
    .net简谈分层架构思想
    FusionCharts Free
    表解锁的方法
    步步为营 .NET 设计模式学习笔记 二十一、Visitor(访问者模式)
    步步为营 .NET 设计模式学习笔记 二十二、Memento(备望录模式)
    再论抽象
  • 原文地址:https://www.cnblogs.com/tthjHiroki/p/13261763.html
Copyright © 2011-2022 走看看