基于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]