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

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

一、SignalR基礎知識

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

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

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

RPC框架

RPC (Remote Procedure Call). 它的優勢就是能夠像調用本地方法同樣調用遠程服務.異步

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

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

 

Hub函數

Hub是SignalR的一個組件, 它運行在ASP.NET Core應用裏. 因此它是服務器端的一個類.ui

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

在ASP.NET Core裏, 本身建立的Hub類須要繼承於基類Hub。在Hub類裏面, 咱們就能夠調用全部客戶端上的方法了. 一樣客戶端也能夠調用Hub類裏的方法.

 

SignalR能夠將參數序列化和反序列化. 這些參數被序列化的格式叫作Hub 協議, 因此Hub協議就是一種用來序列化和反序列化的格式.

Hub協議的默認協議是JSON, 還支持另一個協議是MessagePack。MessagePack是二進制格式的, 它比JSON更緊湊, 並且處理起來更簡單快速, 由於它是二進制的.

此外, SignalR也能夠擴展使用其它協議。

 

二、基於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的服務端和客戶端的相互配合,相互通信過程。

相關文章
相關標籤/搜索