結合實際需求,在webapi內利用WebSocket建立單向的消息推送平臺,讓A頁面和服務端建立WebSocket連接,讓其他頁面可以及時給A頁面推送消息

来源:https://www.cnblogs.com/tthjHiroki/archive/2020/07/07/13261763.html
-Advertisement-
Play Games

1.需求示意圖 2.需求描述 原本是為了給做unity3d客戶端開發的同事提供不定時的消息推送,比如商城購買道具後服務端將道具信息推送給客戶端。 本篇文章簡化理解,用“相關部門開展活動,向全市人民徵集社會服務改善意見”為例子。但核心想法一致:單向推送(指這個需求上只需要單向)。所以這個功能並不是聊天 ...


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類中建立了線程安全的單例模式,目的是讓所有用戶訪問到的字典集合對象唯一。

④案例缺點:核心頁面斷開連接時服務端沒有去監聽,因此服務端無法及時釋放對象,對性能不友好,需要進一步改進。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一. 安裝依賴包 yum install -y wget yum install -y gcc-c++ yum install -y zlib-devel perl-ExtUtils-MakeMaker yum -y install curl-devel expat-devel gettext-de ...
  • 解決辦法: https://tunatore.wordpress.com/2015/06/15/org-jboss-tools-vpe-xulrunner-xulrunnerbundlenotfoundexception-bundle-org-mozilla-xulrunner-win32-win3 ...
  • **前言** Topological sort 又稱 Topological order,這個名字有點迷惑性,因為拓撲排序並不是一個純粹的排序演算法,它只是針對某一類圖,找到一個可以執行的線性順序。 這個演算法聽起來高大上,如今的面試也很愛考,比如當時我在面我司時有整整一輪是基於拓撲排序的設計。 但它其 ...
  • 前言 面試總是會被問到有沒有用過分散式鎖、redis 鎖,大部分讀者平時很少接觸到,所以只能很無奈的回答 “沒有”。本文通過 Spring Boot 整合 redisson 來實現分散式鎖,並結合 demo 測試結果。 首先看下大佬總結的圖 正文 添加依賴 <!--redis--> <depende ...
  • 本文屬於OData系列 目錄 武裝你的WEBAPI-OData入門 武裝你的WEBAPI-OData便捷查詢 武裝你的WEBAPI-OData分頁查詢 武裝你的WEBAPI-OData資源更新Delta 武裝你的WEBAPI-OData之EDM 武裝你的WEBAPI-OData常見問題 武裝你的WE ...
  • abp版本5.9 概述 數據遷移無非就是兩件事情,1、創建資料庫,並根據實體創建對應的表;2、添加一些初始數據 abp的數據遷移也是完成這兩件事,比較特殊的是它是多租戶saas系統,而且支持不同的租戶有獨立的資料庫。所以abp中的遷移要先遷移戶主Host,再遷移租戶Tenant的資料庫 它的遷移定義 ...
  • 前言 上一篇文章主要介紹了IL的概念以及基礎的示例代碼,在這一篇文章中我們將通過對象調用看IL。 創建對象與調用方法 class Program { static void Main(string[] args) { var obj = new MyClass(); Console.WriteLin ...
  • 找到項目中ServiceStack.Text.dll文件的版本,比如我的版本是5.0.0,到GitHub上下載對應的源碼(https://github.com/ServiceStack/ServiceStack.Text/tags) 打開解決方案,重新生成ServiceStack.Text項目,如果 ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...