ibcadmin 发表于 2019-11-8 09:56:09

基于SignalR的服务端和客户端通讯处理

<p>SignalR是一个.NET Core/.NET Framework的实时通讯的框架,一样平常应用在ASP.NET上,固然也可以应用在Winform上实现服务端和客户端的消息通讯,本篇随笔重要基于SignalR的构建一个基于Winform的服务端和客户端的通讯处理案例,先容此中的处理过程。</p>
<h3>1、SignalR底子知识</h3>
<p>SignalR是一个.NET Core/.NET Framework的开源实时框架. SignalR的可利用Web Socket, Server Sent Events 和 Long Polling作为底层传输方式。</p>
<p>SignalR基于这三种技能构建, 抽象于它们之上, 它让你更好的关注业务标题而不是底层传输技能标题。</p>
<p>SignalR将整个信息的交换封装起来,客户端和服务器都是利用JSON来沟通的,在服务端声明的全部Hub信息,都会生成JavaScript输出到客户端,.NET则依赖Proxy来生成署理对象,而Proxy的内部则是将JSON转换成对象。</p>
<p><strong>RPC</strong></p>
<p>RPC (Remote Procedure Call). 它的优点就是可以像调用当地方法一样调用长途服务.</p>
<p>SignalR接纳RPC范式来举行客户端与服务器端之间的通讯.</p>
<p>SignalR利用底层传输来让服务器可以调用客户端的方法, 反之亦然, 这些方法可以带参数, 参数也可以是复杂对象, SignalR负责序列化和反序列化.</p>
<p> </p>
<p><strong>Hub</strong></p>
<p>Hub是SignalR的一个组件, 它运行在ASP.NET Core应用里. 以是它是服务器端的一个类.</p>
<p>Hub利用RPC担当从客户端发来的消息, 也能把消息发送给客户端. 以是它就是一个通讯用的Hub.</p>
<p>在ASP.NET Core里, 本身创建的Hub类必要继承于基类Hub。在Hub类内里, 我们就可以调用全部客户端上的方法了. 同样客户端也可以调用Hub类里的方法.</p>
<p></p>
<p> </p>
<p>SignalR可以将参数序列化和反序列化. 这些参数被序列化的格式叫做Hub 协议, 以是Hub协议就是一种用来序列化和反序列化的格式.</p>
<p>Hub协议的默认协议是JSON, 还支持别的一个协议是MessagePack。MessagePack是二进制格式的, 它比JSON更紧凑, 而且处理起来更简朴快速, 由于它是二进制的.</p>
<p>别的, SignalR也可以扩展利用别的协议。</p>
<p> </p>
<h3>2、基于SignalR构建的Winform服务端和客户端案例</h3>
<p>服务单界面结果如下所示,重要功能为启动服务、制止服务,广播消息和检察毗连客户端信息。</p>
<p></p>
<p> 客户端重要就是实时获取在线用户列表,以及发送、应答消息,消息可以群发,也可以针对特定的客户端举行消息一对一发送。</p>
<p> 客户端1:</p>
<p></p>
<p>客户端2:</p>
<p></p>
<p>构建的项目工程,包罗服务端、客户端和两个之间的通讯对象类,如下所示。</p>
<p></p>
<p>服务端引用</p>
<p></p>
<p>客户端引用</p>
<p></p>
<p>服务端启动代码,想要界说一个Startup类,用来承载SignalR的入口处理。</p>


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);
      }
    }
}

<p> 我们前面先容过,服务端利用Winform步伐来处理它的启动,制止的,如下所示。</p>
<p></p>
<p>因此界面上通过按钮变乱举行启动,启动服务的代码如下所示。</p>

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

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

<p> 这里通过启动别的一个线程的处理,通过WebApp.Start启动入口类,并传入设置好的端口毗连地点。</p>

      /// <summary>
      /// 开启服务
      /// </summary>
      private void ServerStart()
      {
            try
            {
                //开启服务
                signalR = WebApp.Start<Startup>(serverUrl);

                InitControlState(true);
            }
            catch (Exception ex)
            {
                //服务失败时的处理
                WriteToInfo("服务开启失败,缘故因由:" + ex.Message);
                InitControlState(false);
                return;
            }

            WriteToInfo("服务开启乐成 : " + serverUrl);
      }

<p>毗连地点我们设置在xml文件内里,此中的 serverUrl 就是指向下面的键url, 设置的url如下所示:</p>

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

<p>制止服务代码如下所示,通过一个异步操纵制止服务。</p>

      /// <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("服务端已关闭");
            }
      }

<p>服务端对SignalR客户端的管理是通过一个继承于Hub的类SignalRHub举行管理,这个就是整个SignalR的核心了,它重要有几个函数必要重写,如OnConnected、OnDisconnected、OnReconnected、以及一个通用的消息发送AddMessage函数。</p>
<p></p>
<p> </p>
<p> 客户端有接入的时候,我们会通过参数获取毗连客户端的信息,并同一广播当前客户的状态信息,如下所示是服务端对于接入客户端的管理代码。</p>

      /// <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).addMessage(jsonStr);

                return base.OnConnected();

            }
            return Task.FromResult(0);
      }

<p>客户端的接入,必要对相应的HubConnection变乱举行处理,并初始化干系信息,如下代码所示。</p>

      /// <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;
            }
      }

<p>客户端根据收到的差别协议信息,举行差别的变乱处理,如下代码所示。</p>

      /// <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();
            });
      }

<p>例如我们对收到的文本信息,如一对一的发送消息大概广播消息,同一举行展示处理。</p>

      /// <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();
            }));
      }

<p>客户端对消息的处理界面</p>
<p></p>
<p>而客户端发送消息,则是同一通过调用Hub的AddMessage方法举行发送即可,如下代码所示。</p>

      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();
      }

<p>此中的hubProxy是我们前面毗连服务端的时候,构造出的一个署理对象</p>

hubProxy = Connection.CreateHubProxy("SignalRHub");

<p>客户端关闭的时候,我们烧毁干系的对象即可。</p>

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

<p>以上就是SignalR的服务端和客户端的相互共同,相互通讯过程。</p>
页: [1]
查看完整版本: 基于SignalR的服务端和客户端通讯处理