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[]).addMessage(jsonStr); return base.OnConnected(); }
return Task.FromResult();
}

客户端的接入,需要对相应的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(); //绑定一个集线器
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 == )
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的服务端和客户端的相互配合,相互通讯过程。

基于SignalR的服务端和客户端通讯处理的更多相关文章

  1. Unity使用C#实现简单Scoket连接及服务端与客户端通讯

    简介: 网络编程是个很有意思的事情,偶然翻出来很久之前刚开始看Socket的时候写的一个实例,贴出来吧 Unity中实现简单的Socket连接,c#中提供了丰富的API,直接上代码. 服务端代码: [ ...

  2. Socket 学习(三).2 udp 穿透 服务端 与 客户端 通讯

    之前演示的 是 局域网通讯,也可以用作服务器之间的通讯,不能穿透. 想要穿透就要用 udp 了, 后续再讲解 udp 打洞 . 客户端: using System; using System.Wind ...

  3. vue +signalR 实现服务端到客户端消息发送

    承接上一篇 上一篇博客实现是了消息的实时通信,这一篇博客主要讲如何从中心服务内部向客户端发送消息. 先看下最终效果: 在core应用程序里加一个控制器TestController 注入控制器中的IHu ...

  4. 基于socket.io客户端与服务端的相互通讯

    socket.io是对websocket的封装,用于客户端与服务端的相互通讯.官网:https://socket.io/. 下面是socket.io的用法: 1.由于使用express开的本地服务,先 ...

  5. QTcpSocket-Qt使用Tcp通讯实现服务端和客户端

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QTcpSocket-Qt使用Tcp通讯实现服务端和客户端     本文地址:https:// ...

  6. QUdpSocket-Qt使用Udp通讯实现服务端和客户端

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QUdpSocket-Qt使用Udp通讯实现服务端和客户端     本文地址:https:// ...

  7. TCP/IP网络编程之基于UDP的服务端/客户端

    理解UDP 在之前学习TCP的过程中,我们还了解了TCP/IP协议栈.在四层TCP/IP模型中,传输层分为TCP和UDP这两种.数据交换过程可以分为通过TCP套接字完成的TCP方式和通过UDP套接字完 ...

  8. TCP/IP网络编程之基于TCP的服务端/客户端(二)

    回声客户端问题 上一章TCP/IP网络编程之基于TCP的服务端/客户端(一)中,我们解释了回声客户端所存在的问题,那么单单是客户端的问题,服务端没有任何问题?是的,服务端没有问题,现在先让我们回顾下服 ...

  9. TCP/IP网络编程之基于TCP的服务端/客户端(一)

    理解TCP和UDP 根据数据传输方式的不同,基于网络协议的套接字一般分为TCP套接字和UDP套接字.因为TCP套接字是面向连接的,因此又称为基于流(stream)的套接字.TCP是Transmissi ...

随机推荐

  1. 豆瓣电影TOP250和书籍TOP250爬虫

    豆瓣电影 TOP250 和书籍 TOP250 爬虫 最近开始玩 Python , 学习爬虫相关知识的时候,心血来潮,爬取了豆瓣电影TOP250 和书籍TOP250, 这里记录一下自己玩的过程. 电影 ...

  2. vue-router之路由元信息

    路由元信息?(黑人问号脸???)是不是这么官方的解释很多人都会一脸懵?那么我们说meta,是不是很多人恍然大悟,因为在项目中用到或者看到过呢? 是的,路由元信息就是我们定义路由时配置的meta字段:那 ...

  3. MySQL 相关规约(v1.0)

    0)前言 a. 基本规约 [强制]表存储引擎必须使用InnoDB(针对主库一般是强制要求的) [强制]表字符集默认使用utf8,必要时候使用utf8mb4(个人踩坑:emoji表情存储问题) 说明: ...

  4. Redis 集群(三)

    为什么为有集群 在 Redis3 版本之前,每台 Redis 机器需要存储所有 Redis key ,这要求每台 Redis 机器有足够大的内存 而且只能是主节点写,从节点读,对于高并发情况下会有性能 ...

  5. opencv目标检测之canny算法

    canny canny的目标有3个 低错误率 检测出的边缘都是真正的边缘 定位良好 边缘上的像素点与真正的边缘上的像素点距离应该最小 最小响应 边缘只能标识一次,噪声不应该标注为边缘 canny分几步 ...

  6. 五 mysql之多表查询

    目录 一 介绍 二 多表连接查询 1.交叉连接:不适用任何匹配条件.生成笛卡尔积 2.内连接:只连接匹配的行 3 .外链接之左连接:优先显示左表全部记录 4 .外链接之右连接:优先显示右表全部记录 5 ...

  7. Android 手机端自动化测试框架

    前言: 大概有4个月没有更新了,因项目和工作原因,忙的手忙脚乱,趁十一假期好好休息一下,年龄大了身体还是扛不住啊,哈哈.这次更新Android端自动化测试框架,也想开源到github,这样有人使用才能 ...

  8. [Note] Clipboard.js 使用

    clipboard.js是一个用来设置剪切板的库,小巧无依赖,但用法有点诡异,必须依赖一个DOM元素 据作者说,由于浏览器相关安全策略的缘故,无法使用下面这种方式来设置剪切板 clipboard.co ...

  9. 虚拟机安装Centos7系统后优化操作

    重点说明 以下操作针对于VMware软件上新创建的Centos7的虚拟机的优化,当需要多台虚拟机的实验环境时,可通过以下需求先操作配置出一台优化机(也可称为模板机),并创建快照记录,以后的多台虚拟机环 ...

  10. 微人事 star 数超 10k,如何打造一个 star 数超 10k 的开源项目

    看了下,微人事(https://github.com/lenve/vhr)项目 star 数超 10k 啦,松哥第一个 star 数过万的开源项目就这样诞生了. 两年前差不多就是现在这个时候,松哥所在 ...