基於SignalR的服務端和客戶端通訊處理

来源:https://www.cnblogs.com/wuhuacong/archive/2019/10/26/11738974.html
-Advertisement-
Play Games

SignalR是一個.NET Core/.NET Framework的實時通訊的框架,一般應用在ASP.NET上,當然也可以應用在Winform上實現服務端和客戶端的消息通訊,本篇隨筆主要基於SignalR的構建一個基於Winform的服務端和客戶端的通訊處理案例,介紹其中的處理過程。 ...


SignalR是一個.NET Core/.NET Framework的實時通訊的框架,一般應用在ASP.NET上,當然也可以應用在Winform上實現服務端和客戶端的消息通訊,本篇隨筆主要基於SignalR的構建一個基於Winform的服務端和客戶端的通訊處理案例,介紹其中的處理過程。

1、SignalR基礎知識

SignalR是一個.NET Core/.NET Framework的開源實時框架. SignalR的可使用Web Socket, Server Sent Events 和 Long Polling作為底層傳輸方式。

SignalR基於這三種技術構建, 抽象於它們之上, 它讓你更好的關註業務問題而不是底層傳輸技術問題。

SignalR將整個信息的交換封裝起來,客戶端和伺服器都是使用JSON來溝通的,在服務端聲明的所有Hub信息,都會生成JavaScript輸出到客戶端,.NET則依賴Proxy來生成代理對象,而Proxy的內部則是將JSON轉換成對象。

RPC

RPC (Remote Procedure Call). 它的優點就是可以像調用本地方法一樣調用遠程服務.

SignalR採用RPC範式來進行客戶端與伺服器端之間的通信.

SignalR利用底層傳輸來讓伺服器可以調用客戶端的方法, 反之亦然, 這些方法可以帶參數, 參數也可以是複雜對象, SignalR負責序列化和反序列化.

 

Hub

Hub是SignalR的一個組件, 它運行在ASP.NET Core應用里. 所以它是伺服器端的一個類.

Hub使用RPC接受從客戶端發來的消息, 也能把消息發送給客戶端. 所以它就是一個通信用的Hub.

在ASP.NET Core里, 自己創建的Hub類需要繼承於基類Hub。在Hub類裡面, 我們就可以調用所有客戶端上的方法了. 同樣客戶端也可以調用Hub類里的方法.

 

SignalR可以將參數序列化和反序列化. 這些參數被序列化的格式叫做Hub 協議, 所以Hub協議就是一種用來序列化和反序列化的格式.

Hub協議的預設協議是JSON, 還支持另外一個協議是MessagePack。MessagePack是二進位格式的, 它比JSON更緊湊, 而且處理起來更簡單快速, 因為它是二進位的.

此外, SignalR也可以擴展使用其它協議。

 

2、基於SignalR構建的Winform服務端和客戶端案例

服務單界面效果如下所示,主要功能為啟動服務、停止服務,廣播消息和查看連接客戶端信息。

 客戶端主要就是實時獲取線上用戶列表,以及發送、應答消息,消息可以群發,也可以針對特定的客戶端進行消息一對一發送。

 客戶端1:

客戶端2:

構建的項目工程,包括服務端、客戶端和兩個之間的通訊對象類,如下所示。

服務端引用

客戶端引用

服務端啟動代碼,想要定義一個Startup類,用來承載SignalR的入口處理。

[assembly: OwinStartup(typeof(SignalRServer.Startup))]
namespace SignalRServer
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var config = new HubConfiguration();
            config.EnableDetailedErrors = true;

            //設置可以跨域訪問
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
            //映射到預設的管理
            app.MapSignalR(config);
        }
    }
}

 我們前面介紹過,服務端使用Winform程式來處理它的啟動,停止的,如下所示。

因此界面上通過按鈕事件進行啟動,啟動服務的代碼如下所示。

        private void btnStart_Click(object sender, EventArgs e)
        {
            this.btnStart.Enabled = false;
            WriteToInfo("正在連接中....");

            Task.Run(() =>
            {
                ServerStart();
            });
        }

 這裡通過啟動另外一個線程的處理,通過WebApp.Start啟動入口類,並傳入配置好的埠連接地址。

        /// <summary>
        /// 開啟服務
        /// </summary>
        private void ServerStart()
        {
            try
            {
                //開啟服務
                signalR = WebApp.Start<Startup>(serverUrl);

                InitControlState(true);
            }
            catch (Exception ex)
            {
                //服務失敗時的處理
                WriteToInfo("服務開啟失敗,原因:" + ex.Message);
                InitControlState(false);
                return;
            }

            WriteToInfo("服務開啟成功 : " + serverUrl);
        }

連接地址我們配置在xml文件裡面,其中的 serverUrl 就是指向下麵的鍵url, 配置的url如下所示:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
    </startup>
  <appSettings>
    <add key="url" value="http://localhost:17284"/>
  </appSettings>

停止服務代碼如下所示,通過一個非同步操作停止服務。

        /// <summary>
        /// 停止服務
        /// </summary>
        /// <returns></returns>
        private async Task StopServer()
        {
            if (signalR != null)
            {
                //向客戶端廣播消息
                hubContext = GlobalHost.ConnectionManager.GetHubContext<SignalRHub>();
                await hubContext.Clients.All.SendClose("服務端已關閉");

                //釋放對象
                signalR.Dispose();
                signalR = null;

                WriteToInfo("服務端已關閉");
            }
        }

服務端對SignalR客戶端的管理是通過一個繼承於Hub的類SignalRHub進行管理,這個就是整個SignalR的核心了,它主要有幾個函數需要重寫,如OnConnected、OnDisconnected、OnReconnected、以及一個通用的消息發送AddMessage函數。

 

 客戶端有接入的時候,我們會通過參數獲取連接客戶端的信息,並統一廣播當前客戶的狀態信息,如下所示是服務端對於接入客戶端的管理代碼。

        /// <summary>
        /// 在連接上時
        /// </summary>
        public override Task OnConnected()
        {
            var client = JsonConvert.DeserializeObject<ClientModel>(Context.QueryString.Get("Param"));
            if (client != null)
            {
                client.ConnId = Context.ConnectionId;
                //將客戶端連接加入列表
                if (!Portal.gc.ClientList.Exists(e => e.ConnId == client.ConnId))
                {
                    Portal.gc.ClientList.Add(client);
                }
                Groups.Add(client.ConnId, "Client");

                //向服務端寫入一些數據
                Portal.gc.MainForm.WriteToInfo("客戶端連接ID:" + Context.ConnectionId);
                Portal.gc.MainForm.WriteToInfo(string.Format("客戶端 【{0}】接入: {1} ,  IP地址: {2} \n 客戶端總數: {3}", client.Name, Context.ConnectionId, client.IPAddress, Portal.gc.ClientList.Count));

                //先所有連接客戶端廣播連接客戶狀態
                var imcp = new StateMessage()
                {
                    Client = client,
                    MsgType = MsgType.State,
                    FromConnId = client.ConnId,
                    Success = true
                };
                var jsonStr = JsonConvert.SerializeObject(imcp);
                Clients.Group("Client", new string[0]).addMessage(jsonStr);

                return base.OnConnected();

            }
            return Task.FromResult(0);
        }

客戶端的接入,需要對相應的HubConnection事件進行處理,並初始化相關信息,如下代碼所示。

        /// <summary>
        /// 初始化服務連接
        /// </summary>
        private void InitHub()
        {
            。。。。。。

            //連接的時候傳遞參數Param
            var param = new Dictionary<string, string> {
                { "Param", JsonConvert.SerializeObject(client) }
            };
            //創建連接對象,並實現相關事件
            Connection = new HubConnection(serverUrl, param);

            。。。。。。//實現相關事件
            Connection.Closed += HubConnection_Closed;
            Connection.Received += HubConnection_Received;
            Connection.Reconnected += HubConnection_Succeed;
            Connection.TransportConnectTimeout = new TimeSpan(3000);

            //綁定一個集線器
            hubProxy = Connection.CreateHubProxy("SignalRHub");
            AddProtocal();
        }
        private async Task StartConnect()
        {
            try
            {
                //開始連接
                await Connection.Start();
                await hubProxy.Invoke<CommonResult>("CheckLogin", this.txtUser.Text);

                HubConnection_Succeed();//處理連接後的初始化

                。。。。。。
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.StackTrace);
                this.richTextBox.AppendText("伺服器連接失敗:" + ex.Message);

                InitControlStatus(false);
                return;
            }
        }

客戶端根據收到的不同協議信息,進行不同的事件處理,如下代碼所示。

        /// <summary>
        /// 對各種協議的事件進行處理
        /// </summary>
        private void AddProtocal()
        {
            //接收實時信息
            hubProxy.On<string>("AddMessage", DealMessage);

            //連接上觸發connected處理
            hubProxy.On("logined", () =>
                this.Invoke((Action)(() =>
                {
                    this.Text = string.Format("當前用戶:{0}", this.txtUser.Text);
                    richTextBox.AppendText(string.Format("以名稱【" + this.txtUser.Text + "】連接成功!" + Environment.NewLine));
                    InitControlStatus(true);
                }))
            );

            //服務端拒絕的處理
            hubProxy.On("rejected", () =>
                this.Invoke((Action)(() =>
                {
                    richTextBox.AppendText(string.Format("無法使用名稱【" + this.txtUser.Text + "】進行連接!" + Environment.NewLine));
                    InitControlStatus(false);
                    CloseHub();
                }))
            );

            //客戶端收到服務關閉消息
            hubProxy.On("SendClose", () =>
            {
                CloseHub();
            });
        }

例如我們對收到的文本信息,如一對一的發送消息或者廣播消息,統一進行展示處理。

        /// <summary>
        /// 處理文本消息
        /// </summary>
        /// <param name="data"></param>
        /// <param name="basemsg"></param>
        private void DealText(string data, BaseMessage basemsg)
        {
            //JSON轉換為文本消息
            var msg = JsonConvert.DeserializeObject<TextMessage>(data);
            var ownerClient = ClientList.FirstOrDefault(f => f.ConnId == basemsg.FromConnId);
            var ownerName = ownerClient == null ? "系統廣播" : ownerClient.Name;

            this.Invoke(new Action(() =>
            {
                richTextBox.AppendText(string.Format("{0} - {1}:\n {2}" + Environment.NewLine, DateTime.Now, ownerName, msg.Message));
                richTextBox.ScrollToCaret();
            }));
        }

客戶端對消息的處理界面

而客戶端發送消息,則是統一通過調用Hub的AddMessage方法進行發送即可,如下代碼所示。

        private void BtnSendMessage_Click(object sender, EventArgs e)
        {
            if (txtMessage.Text.Length == 0)
                return;

            var message = new TextMessage() {
                MsgType = MsgType.Text,
                FromConnId = client.ConnId,
                ToConnId = this.toId,
                Message = txtMessage.Text,
                Success = true };

            hubProxy.Invoke("AddMessage", JsonConvert.SerializeObject(message));
            txtMessage.Text = string.Empty;
            txtMessage.Focus();
        }

其中的hubProxy是我們前面連接服務端的時候,構造出的一個代理對象

hubProxy = Connection.CreateHubProxy("SignalRHub");

客戶端關閉的時候,我們銷毀相關的對象即可。

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (Connection != null)
            {
                Connection.Stop();
                Connection.Dispose();
            }
        }

以上就是SignalR的服務端和客戶端的相互配合,相互通訊過程。


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

-Advertisement-
Play Games
更多相關文章
  • 1、搭建一個eureka-server註冊中心工程 該工程比較簡潔,沒有太多配置,不在描述,單節點,服務埠:8888 2、創建zuul-gateway網關工程 2.1、工程pom依賴 2.2、工程配置文件:zuul-gateway\src\main\resources\bootstrap.yml ...
  • [TOC] 原文鏈接: "QCustomplot使用分享(九) 繪製圖表 多功能游標" 一、概述 上一篇文章 "QCustomplot使用分享(八) 層(完結)" 講述了第一篇QCustomPlot控制項的使用,主要是展示了多維度折線圖,並且有一個簡單的游標展示效果。本篇文章是在上一篇文章的基礎上進行 ...
  • 以下是我近些年收集的一些Python實用技巧和工具,希望能對你有所幫助。 交換變數 if 語句在行內 連接 下麵的最後一種方式在綁定兩個不同類型的對象時顯得很cool。 數字技巧 註意浮點數的除法 數值比較 這是我見過諸多語言中很少有的如此棒的簡便法 同時迭代兩個列表 帶索引的列表迭代 列表推導式 ...
  • 前言 web漏洞之首莫過於sql了,不管使用哪種語言進行web後端開發,只要使用了關係型資料庫,可能都會遇到sql註入攻擊問題。那麼在Python web開發的過程中sql註入是怎麼出現的呢,又是怎麼去解決這個問題的? 當然,我這裡並不想討論其他語言是如何避免sql註入的,網上關於PHP防註入的各種 ...
  • 1 #include 2 #include 3 typedef struct Lnode{ 4 int num; 5 struct Lnode * next; 6 }Lnode,*LinkList; 7 8 typedef struct Link{ 9 LinkList data; 10 struc... ...
  • 字元串拼接 實際場景:把列表中的數據拼接成一個字元串 解決方案:使用 str.join() 方法 推薦使用生成器表達式,如果列表很大,可以節省很多記憶體空間 拆分含有多種分隔符的字元串 實際場景:把某個字元串依據分割符號拆分不同的欄位,該字元串包含多種不同的分隔符 1.使用 python 中的 spl ...
  • Python 入門 之 print帶顏色輸出 1、print帶顏色輸出書寫格式: 開頭部分: \033[顯示方式; 前景色 ; 背景色 m 結尾部分: \033[0m 詳解: 開頭部分的三個參數: 顯示方式 字體顏色 背景色 ​ 這三個參數是可選參數,可以只寫其中的某一個,另外由於表示三個參數不同含 ...
  • #!/usr/bin/env python# -*- coding: utf-8 -*-# @Time : ${DATE} ${TIME}# @Author : Aries# @Site : ${SITE}# @File : ${NAME}.py# @Software: ${PRODUCT_NAME ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...