第三节:SignalR之PersistentConnection模型详解(步骤、用法、分组、跨域、第三方调用)
一. 承上声明
几点介绍:
1. PersistentConnection(永久连接)相对于Hubs模式,更加偏向底层,它的编程模式与WebSocket的写法很类似,固定方法发送和接受,不能向Hub模式那样 客户端和服务端相互调用各自定义的方法。
2. 该模型主要用于:单个发件人、分组、广播消息的简单终结点。
二. 从零开始搭建
1. 新建MVC5项目,通过Nuget安装:Microsoft.AspNet.SignalR程序集,安装成功后如下图:


2. 新建一个永久连接模型类(MyPresitentConnection1),该类继承了PersistentConnection,并且override几个必要方法。

3. 新建一个OWIN Startup Class(Startup),并在Configuration方法中指定使用的通讯模型的URl, 如: app.MapSignalR<MyPresitentConnection1>("/myPreConnection1");
PS: 程序启动时候首先会找到该类,然后运行里面的Configuration方法,从而url和通讯模型的匹配将生效。

4. 在前端页面中书写SignalR的代码,与服务器端MyPresitentConnection1类进行连接,实现相应的通讯业务。

三. 核心方法介绍
1. 服务器端代码
(1). OWIN Startup Class即Startup中要配置url和通讯模型向匹配,这里的url在web前端页面的js中要使用,代码如下:
public class Startup
{
public void Configuration(IAppBuilder app)
{
// 有关如何配置应用程序的详细信息,请访问 https://go.microsoft.com/fwlink/?LinkID=316888
//1. 基本用法的配置
app.MapSignalR<MyPresitentConnection1>("/myPreConnection1");
}
}
(2). 永久连接模型类MyPresitentConnection1继承了PersistentConnection,并且可以override几个方法。
A. PersistentConnection中可以override的几个主要方法有:
①. OnConnected :连接成功后调用
②. OnReceived:接收到请求的时候调用
③. OnDisconnected:连接中断的时候调用
④. OnReconnected:连接超时重新连接的时候调用
B. 核心业务主要使用PersistentConnection类中的Connection属性,有两个核心方法
①. 1对1发送消息: public static Task Send(string connectionId, object value);
②. 1对多发送消息: public static Task Send(IList<string> connectionIds, object value);
③. 广播(群发,可以去掉不发送的人): public static Task Broadcast(object value, params string[] excludeConnectionIds);
PS:发现每个override里都有一个参数connectionId,它代表,每个客户端连接服务器成功后都会产生一个标记,这个标记是GUID产生的,它是唯一的, 不会重复, 在业务中可以通过该标记connectionId来区分客户端。
下面我的代码中书写的业务为:
①. OnConnected方法即连接成功后调用的方法,调用Send方法告诉自己登录成功(当然你也可以根据实际业务告诉指定的人)。
②. OnReceived方法即接受请求的方法,调用Send方法向指定人一对一发送消息。
③. OnDisconnected方法即连接中断的方法,调用Broadcast方法向所有人发送消息,某某已经退出。
④. OnReconnected方法即超时重新连接方法,执行重连业务。
分享代码:
public class TempData
{
/// <summary>
/// 接收人的connectionId
/// </summary>
public string receiveId { get; set; } /// <summary>
/// 发送内容
/// </summary>
public string msg { get; set; }
}
public class MyPresitentConnection1 : PersistentConnection
{
//下面的两个方法OnConnected 和 OnReceived默认带的 /// <summary>
/// 连接成功后的方法
/// </summary>
/// <param name="request"></param>
/// <param name="connectionId"></param>
/// <returns></returns>
protected override Task OnConnected(IRequest request, string connectionId)
{
//Send方法,向指定人发送消息
return Connection.Send(connectionId, $"用户:{connectionId}登录成功");
} /// <summary>
/// 接收请求的方法
/// </summary>
/// <param name="request"></param>
/// <param name="connectionId"></param>
/// <param name="data"></param>
/// <returns></returns>
protected override Task OnReceived(IRequest request, string connectionId, string data)
{
//一对一发送消息
//data是一个json对象 { receiveId: $("#j_receiveId").val(), msg: $("#j_content").val() }
var model = JsonConvert.DeserializeObject<TempData>(data); return Connection.Send(model.receiveId, model.msg);
} /// <summary>
/// 连接中断调用方法
/// </summary>
/// <param name="request"></param>
/// <param name="connectionId"></param>
/// <param name="stopCalled"></param>
/// <returns></returns>
protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)
{
//告诉所有人该用户退出了(包括自己,也可以配置排除一些用户)
Connection.Broadcast( $"有用户{connectionId}已经退出");
return base.OnDisconnected(request, connectionId, stopCalled);
} /// <summary>
/// 当连接在超时后重新连接时调用该方法
/// </summary>
/// <param name="request"></param>
/// <param name="connectionId"></param>
/// <returns></returns>
protected override Task OnReconnected(IRequest request, string connectionId)
{
return base.OnReconnected(request, connectionId);
}
}
2. 前端Html页面
(1). 引入JS库,这里包括JQuery库和SignalR库(JQuery最低版本为1.6.4)。

(2). 配置路径:$.connection("/myPreConnection1");需要与Startup中的对应
(3). 常用的几个方法有:
① start:开启连接
② received:接受服务器发送来的消息
③ disconnected:连接中断时调用
④ error:连接发生错误的时嗲用
④ stop:断开连接
⑤ send:发送消息
另外还有:connectionSlow、stateChanged、reconnecting、reconnected等等
(4). 当前连接状态有4种
connecting: 0(正在连接), connected: 1(正常连接,连接成功中), reconnecting: 2(正在重连), disconnected: 4 (掉线了)
PS: 以上代码和WebSocket确实很像,下图为WebSocket相关方法。

(5). 下面我的代码中的业务

分享代码:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
@*
Web客户端用法说明
1. 配置路径:$.connection("/myPreConnection1");需要与Startup中的对应
2. 常用的几个方法有:
① start:开启连接
② received:接受服务器发送来的消息
③ disconnected:连接中断时调用
④ error:连接发生错误的时嗲用
④ stop:断开连接
⑤ send:发送消息
另外还有:connectionSlow、stateChanged、reconnecting、reconnected等等
3. 当前连接状态有4种
connecting: 0(正在连接), connected: 1(正常连接), reconnecting: 2(正在重连), disconnected: 4 (掉线了)
*@
<meta name="viewport" content="width=device-width" />
<title>Index</title>
<script src="~/Scripts/jquery-3.3.1.min.js"></script>
<script src="~/Scripts/jquery.signalR-2.3.0.js"></script>
<script type="text/javascript">
$(function () {
var conn = $.connection("/myPreConnection1");
//一. 监控
//1. 接受服务器发来的消息
conn.received(function (data) {
$("#j_Msg").append("<li>" + data + "</li>");
});
//2. 连接断开的方法
conn.disconnected(function () {
$("#j_notice").html("连接中断");
});
//3. 连接发生错误时候触发
conn.error(function (data) {
$("#j_notice").html(data);
});
//二. 主动事件
//1.建立连接
$("#j_connect").click(function () {
conn.start(function () {
$("#j_notice").html("连接成功");
});
});
//2.断开连接
$("#j_close").click(function () {
conn.stop();
});
//3.发送消息
$("#j_send").click(function () {
//发送消息之前要判断连接状态,conn.state有4中状态
//connecting: 0(正在连接), connected: 1(正常连接), reconnecting: 2(正在重连), disconnected: 4 (掉线了)
console.log(conn.state);
if (conn.state == 1) {
conn.send({ receiveId: $("#j_receiveId").val(), msg: $("#j_content").val() });
} else if (conn.state == 0) {
$("#j_notice").html("正在连接中,请稍等");
} else if (conn.state == 2) {
$("#j_notice").html("正在重连,请稍等");
} else if (conn.state == 4) {
$("#j_notice").html("掉线了,请重新连接");
}
});
});
</script>
</head>
<body>
<div>
<div><span>提示:</span><span id="j_notice"></span></div>
<div style="margin-top:20px">
<button id="j_connect">建立连接</button>
<button id="j_close">关闭连接</button>
</div>
<div style="margin-top:20px">
<input type="text" value="" placeholder="请输入接收人的标记" id="j_receiveId" />
<input type="text" value="" placeholder="请输入发送内容" id="j_content" />
<button id="j_send">发送消息</button>
</div>
<div>
<ul id="j_Msg"></ul>
</div>
</div>
</body>
</html>
(6). 运行效果

四. 分组的概念
1. PersistentConnection类中提供了一个 IConnectionGroupManager Groups的概念,即可以将不同用户分到不同组里,就好比QQ的中的讨论组, 在这个组里发信息,该组里的所有人都能看到,但别的组是看不到的。并提供了两个方法分别是
①. 加入组:Task Add(string connectionId, string groupName)
②. 移除组:Task Remove(string connectionId, string groupName)
IConnectionGroupManager下提供两个针对组进行发送消息的方法
①. 针对单个组(可以去掉不发送的人):Task Send(string groupName, object value, params string[] excludeConnectionIds);
②. 针对多个组(可以去掉不发送的人):Task Send(IList<string> groupNames, object value, params string[] excludeConnectionIds);
注:一个客户端可以同时加入多个组的,就好比qq,一个用户你可以同时在多个讨论组里讨论,相互不影响。
2. 需求背景:
有两个房间,分别是room1和room2,将2个人加入到room1里,2两个人加入到room2里,1个既加入room1且加入room2,测试向指定组发送消息和普通的群发消息。
测试页面如下图:

3. 先贴代码后分析
实体类代码
public class RoomData
{
/// <summary>
/// 房间名称
/// </summary>
public string roomName { get; set; } /// <summary>
/// 发送的消息
/// </summary>
public string msg { get; set; } /// <summary>
/// 用来区分是进入房间,还是普通的发送消息
/// "enter":表示进入房间
/// "sendRoom":表示向某个组发送信息
/// "":表示普通的消息发送,不区分组的概念
/// </summary>
public string action { get; set; }
}
服务器端代码
public class MyPresitentConnection2 : PersistentConnection
{
protected override Task OnConnected(IRequest request, string connectionId)
{
//提示自己进入成功
return Connection.Send(connectionId, "Welcome!");
} protected override Task OnReceived(IRequest request, string connectionId, string data)
{
//data是一个json对象 { roomName: "room2", action: "enter", msg: "" }
var model = JsonConvert.DeserializeObject<RoomData>(data);
if (model.action == "enter")
{
//表示建立组关系
this.Groups.Add(connectionId, model.roomName);
//提示自己进入房间成功
Connection.Send(connectionId, $"进入{model.roomName}房间成功");
//向该组中除了当前人外,均发送欢迎消息
return this.Groups.Send(model.roomName, $"欢迎{connectionId}进入{model.roomName}房间", connectionId);
}
else if (model.action == "sendRoom")
{
//表示普通的按组发送信息(除了自己以外)
return this.Groups.Send(model.roomName, string.Format("用户 {0} 发来消息: {1}", connectionId, model.msg), connectionId);
}
else
{
//表示普通的群发,不分组
return Connection.Broadcast(string.Format("用户 {0} 发来消息: {1}", connectionId, model.msg), connectionId);
}
}
}
Html代码
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
<script src="~/Scripts/jquery-3.3.1.min.js"></script>
<script src="~/Scripts/jquery.signalR-2.3.0.js"></script>
<script type="text/javascript">
$(function () {
var conn = $.connection("/myPreConnection2");
//一. 监控
//1. 接受服务器发来的消息
conn.received(function (data) {
$("#j_Msg").append("<li>" + data + "</li>");
});
//2. 连接断开的方法
conn.disconnected(function () {
$("#j_notice").html("连接中断");
});
//二. 主动事件
//1.建立连接
$("#j_connect").click(function () {
conn.start().done(function () {
$("#j_notice").html("连接成功");
});
});
//2.断开连接
$("#j_close").click(function () {
conn.stop();
});
//3.进入room1
$("#j_room1").click(function () {
conn.send({ roomName: "room1", action: "enter",msg:"" });
});
//4.进入room2
$("#j_room2").click(function () {
conn.send({ roomName: "room2", action: "enter", msg: "" });
});
//5. 给room1中的用户发送消息
$("#j_sendRoom1").click(function () {
conn.send({ roomName: "room1", action: "sendRoom", msg: $('#j_content').val() });
});
//6. 给room2中的用户发送消息
$("#j_sendRoom2").click(function () {
conn.send({ roomName: "room2", action: "sendRoom", msg: $('#j_content').val() });
});
//7. 普通群发消息
$("#j_sendAll").click(function () {
conn.send({ roomName: "", action: "", msg: $('#j_content').val() });
});
});
</script>
</head>
<body>
<div>
<div><span>提示:</span><span id="j_notice"></span></div>
<div style="margin-top:20px">
<button id="j_connect">建立连接</button>
<button id="j_close">关闭连接</button>
</div>
<div style="margin-top:20px">
<button id="j_room1">进入room1</button>
<button id="j_room2">进入room2</button>
</div>
<div style="margin-top:20px">
<input type="text" value="" placeholder="请输入发送内容" id="j_content" />
<button id="j_sendRoom1">给room1发送消息</button>
<button id="j_sendRoom2">给room2发送消息</button>
<button id="j_sendAll">普通群发</button>
</div>
<div>
<ul id="j_Msg"></ul>
</div>
</div>
</body>
</html>
代码分析:
通过客户端发送过来的action字段来区分几种情况。
① 当为“enter”时,表示建立组关系,并提示自己进入房间成功,通知其他人欢迎信息。
② 当为“sendRoom”时,表示向指定组发送消息
③ 当为空时,表示普通的向所有人发送消息,不区分组的概念
4. 效果展示(实在是难截图啊)

5. 开始吐槽
本来框架默认提供一个组的概念,方便了我们对一些业务的开发,是一好事,但是竟然不能获取每个组内的connectionId列表,这。。。太坑了,不伦不类的,还得自己记录一下哪个组中有哪些connectionId,坑啊,微软baba真不知道你是怎么想的。
五. 跨域请求
1. SignalR跨域请求的默认是关闭的,我们可以自行开启,SignalR支持的跨域请求有两种:
①:JSONP的模式,仅支持Get请求,需要服务器端配合,传输数据大小有限制
②:Cors模式,支持Post、Get等请求,需要在浏览器中加 【Access-Control-Allow-Origin:*】类似的配置
2. 开启跨域请求的方式,详见下面代码:
public class Startup
{
public void Configuration(IAppBuilder app)
{
//1. JSONP的模式
//app.MapSignalR<MyPresitentConnection1>("/myPreConnection1", new Microsoft.AspNet.SignalR.ConnectionConfiguration()
//{
// EnableJSONP = true
//}); //2. Cors的模式(需要Nuget安装:Microsoft.Owin.Cors程序集)
//app.Map("/myPreConnection1", (map) =>
//{
// map.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
//}); //3. JSONP和Cors同时开启
//app.Map("/myPreConnection1", (map) =>
//{
// //1. 开启 jsonp
// map.RunSignalR<MyPresitentConnection1>(new Microsoft.AspNet.SignalR.HubConfiguration() { EnableJSONP = true });
// //2. 开启cors
// map.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
//}); }
}
3. 跨域请求的作用是什么,在后面章节和Hubs模型一起介绍
六. 第三方调用
以上所有的代码与通信相关的代码都写在永久连接类中,但在开发中经常会遇到,我要在控制器中的某个方法中调用相应的方法,发给用户信息,这个时候怎么办呢?
可以通过:GlobalHost.ConnectionManager.GetConnectionContext<MyPresitentConnection1>();来获取永久连接类,然后调用相应的方法。
代码如下:
/// <summary>
/// 向所有人发送消息
/// </summary>
/// <param name="msg">发送的信息</param>
public string MySendAll(string msg)
{
string myConnectionId = Session["connectionId"].ToString();
//PersistentConnection模式
var perConnection = GlobalHost.ConnectionManager.GetConnectionContext<MyPresitentConnection1>();
perConnection.Connection.Broadcast(msg);
return "ok";
}
关于具体结合业务的样例在下一节的Hub的例子详细编写,原理都一样。
七. 总结
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,如需代码请在评论处留下你的邮箱
第三节:SignalR之PersistentConnection模型详解(步骤、用法、分组、跨域、第三方调用)的更多相关文章
- 跨域共享CORS详解及Gin配置跨域
跨域简介 当两个域具有相同的协议(如http), 相同的端口(如80),相同的host,那么我们就可以认为它们是相同的域(协议,域名,端口都必须相同). 跨域就指着协议,域名,端口不一致,出于安全考虑 ...
- ASP.NET Core的配置(2):配置模型详解
在上面一章我们以实例演示的方式介绍了几种读取配置的几种方式,其中涉及到三个重要的对象,它们分别是承载结构化配置信息的Configuration,提供原始配置源数据的ConfigurationProvi ...
- ISO七层模型详解
ISO七层模型详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在我刚刚接触运维这个行业的时候,去面试时总是会做一些面试题,笔试题就是看一个运维工程师的专业技能的掌握情况,这个很 ...
- 28、vSocket模型详解及select应用详解
在上片文章已经讲过了TCP协议的基本结构和构成并举例,也粗略的讲过了SOCKET,但是讲解的并不完善,这里详细讲解下关于SOCKET的编程的I/O复用函数. 1.I/O复用:selec函数 在介绍so ...
- 第94天:CSS3 盒模型详解
CSS3盒模型详解 盒模型设定为border-box时 width = border + padding + content 盒模型设定为content-box时 width = content所谓定 ...
- JVM的类加载过程以及双亲委派模型详解
JVM的类加载过程以及双亲委派模型详解 这篇文章主要介绍了JVM的类加载过程以及双亲委派模型详解,类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象 ...
- 云时代架构阅读笔记六——Java内存模型详解(二)
承接上文:云时代架构阅读笔记五——Java内存模型详解(一) 原子性.可见性.有序性 Java内存模型围绕着并发过程中如何处理原子性.可见性和有序性这三个特征来建立的,来逐个看一下: 1.原子性(At ...
- SignalR新手系列教程详解总结(转)
SignalR新手系列教程详解总结 GlobalHost.ConnectionManager.GetHubContext<TodoListHub>() .Clients.Clients(l ...
- css 06-CSS盒模型详解
06-CSS盒模型详解 #盒子模型 #前言 盒子模型,英文即box model.无论是div.span.还是a都是盒子. 但是,图片.表单元素一律看作是文本,它们并不是盒子.这个很好理解,比如说,一张 ...
随机推荐
- Centos7安装搭建NTP服务器和NTP客户端同步时间
NTP简介: NTP是网络时间协议(Network Time Protocol),它是用来同步网络中各个计算机的时间的协议. 在计算机的世界里,时间非常地重要 例如:对于火箭发射这种科研活动,对时间的 ...
- mysql的函数与储存过程与pymysql的配合使用
现在mysql上定义一个函数,一个储存过程 函数: delimiter \\ CREATE FUNCTION f2 ( num2 INT, num1 INT ) RETURNS INT BEGIN D ...
- css_属性
老师的博客:https://www.cnblogs.com/liwenzhou/p/7999532.htm css的属性 整体属性的:作用于全局 width:表示宽 height:表示长 color: ...
- 【Python 23】52周存钱挑战3.0(循环计数for与range)
1.案例描述 按照52周存钱法,存钱人必须在一年52周内,每周递存10元.例如,第一周存10元,第二周存20元,第三周存30元,直到第52周存520元. 记录52周后能存多少钱?即10+20+30+. ...
- python import详解
1.import作用 引入模块 2.import的特点 一个程序中,import的模块不会重复被引用,如: # test1.py import test2 print test2.attr # tes ...
- linux vi粘贴格式易错乱
对于一些冗长的代码完全可以粘贴的时候,vi粘贴所有格式全部错乱,完全无法阅读. 解决办法:esc进入命令行模式后,输入 :set paste,然后再i进入粘贴编辑模式,即可正常复制并保留原有格式-
- NetSec2019 20165327 Exp7 网络欺诈防范
NetSec2019 Exp7 网络欺诈防范 一.本实践的目标理解常用网络欺诈背后的原理,以提高防范意识,并提出具体防范方法.具体实践有 (1)简单应用SET工具建立冒名网站 (1分) (2)ette ...
- PyInstaller Extractor安装和使用方法
PyInstaller Extractor是可以提取出PyInstaller所创建的windows可执行文件的资源内容. 关于PyInstaller的介绍:PyInstaller安装使用方法 使用Py ...
- linux下开启、关闭、重启mysql服务命令
一. 启动1.使用 service 启动:service mysql start2.使用 mysqld 脚本启动:/etc/inint.d/mysql start3.使用 safe_mysqld 启动 ...
- Pandas基本操作
pandas:数据分析 pandas是一个强大的Python数据分析的工具包. pandas是基于NumPy构建的. pandas的主要功能 具备对其功能的数据结构DataFrame.Series 集 ...