SignalR的三个Demo
一、理解SignalR
ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信(即:客户端(Web页面)和服务器端可以互相实时的通知消息及调用方法),SignalR有三种传输模式:LongLooping(长轮询)、WebSocket(HTML5的WEB套接字)、Forever Frame(隐藏框架的长请求连接),可以在WEB客户端显式指定一种或几种,也可以采取默认(推荐),若采取默认,SignalR会根据浏览器的环境自动选择合适的传输方式。
二、SignalR的三种实现方式
第一种:采用集线器类(Hub)+非自动生成代理模式:服务端与客户端分别定义的相对应的方法,客户端通过代理对象调用服务端的方法,服务端通过IHubConnectionContext回调客户端的方法,客户端通过回调方法接收结果。
之前我写过一篇文章《分享一个基于长连接+长轮询+原生的JS及AJAX实现的多人在线即时交流聊天室》,是通过长轮询+长连接的方式来实现的在线多人聊天室功能,从代码量来看就知道实现起来并不简单,而如今有了SignalR,会简单很多,我这里使用SignalR再来写一个简单的在线多人聊天室示例,以便大家快速掌握SignalR。
DEMO - 1 示例代码如下:
服务端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
//Startup类文件 using System; using System.Threading.Tasks; using Microsoft.Owin; using Owin; using Microsoft.AspNet.SignalR; [assembly: OwinStartup( typeof (TestWebApp.Models.Startup))] namespace TestWebApp.Models { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR(); } } } //ChatHub类文件 using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Web; namespace TestWebApp.Models { [HubName( "chat" )] public class ChatHub : Hub { public static ConcurrentDictionary< string , string > OnLineUsers = new ConcurrentDictionary< string , string >(); [HubMethodName( "send" )] public void Send( string message) { string clientName = OnLineUsers[Context.ConnectionId]; message = HttpUtility.HtmlEncode(message).Replace( "\r\n" , "<br/>" ).Replace( "\n" , "<br/>" ); Clients.All.receiveMessage(DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ), clientName, message); } [HubMethodName( "sendOne" )] public void Send( string toUserId, string message) { string clientName = OnLineUsers[Context.ConnectionId]; message = HttpUtility.HtmlEncode(message).Replace( "\r\n" , "<br/>" ).Replace( "\n" , "<br/>" ); Clients.Caller.receiveMessage(DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ), string .Format( "您对 {1}" , clientName, OnLineUsers[toUserId]), message); Clients.Client(toUserId).receiveMessage(DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ), string .Format( "{0} 对您" , clientName), message); } public override System.Threading.Tasks.Task OnConnected() { string clientName = Context.QueryString[ "clientName" ].ToString(); OnLineUsers.AddOrUpdate(Context.ConnectionId, clientName, (key, value) => clientName); Clients.All.userChange(DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ), string .Format( "{0} 加入了。" , clientName), OnLineUsers.ToArray()); return base .OnConnected(); } public override System.Threading.Tasks.Task OnDisconnected( bool stopCalled) { string clientName = Context.QueryString[ "clientName" ].ToString(); Clients.All.userChange(DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ), string .Format( "{0} 离开了。" , clientName), OnLineUsers.ToArray()); OnLineUsers.TryRemove(Context.ConnectionId, out clientName); return base .OnDisconnected(stopCalled); } } } |
1
2
3
4
5
6
7
8
|
public ActionResult Index() { ViewBag.ClientName = "聊客-" + Guid.NewGuid().ToString( "N" ); var onLineUserList = ChatHub.OnLineUsers.Select(u => new SelectListItem() { Text = u.Value, Value = u.Key }).ToList(); onLineUserList.Insert(0, new SelectListItem() { Text = "-所有人-" , Value = "" }); ViewBag.OnLineUsers = onLineUserList; return View(); } |
WEB客户端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
<!DOCTYPE html> <html> <head> <meta name= "viewport" content= "width=device-width" /> <meta charset= "utf-8" /> <title>聊天室</title> <script src= "~/Scripts/jquery-1.6.4.min.js" type= "text/javascript" ></script> <script src= "~/Scripts/jquery.signalR-2.2.0.min.js" type= "text/javascript" ></script> <style type= "text/css" > #chatbox { width: 100%; height: 500px; border: 2px solid blue; padding: 5px; margin: 5px 0px; overflow-x: hidden; overflow-y: auto; } .linfo { } .rinfo { text-align: right; } </style> <script type= "text/javascript" > $(function () { var clientName = $( "#clientname" ).val(); var eChatBox = $( "#chatbox" ); var eUsers = $( "#users" ); var conn = $.hubConnection(); conn.qs = { "clientName" : clientName }; conn.start().done(function () { $( "#btnSend" ).click(function () { var toUserId = eUsers.val(); if (toUserId != "" ) { chat.invoke( "sendOne" , toUserId, $( "#message" ).val()) .done(function () { //alert("发送成功!"); $( "#message" ).val( "" ).focus(); }) .fail(function (e) { alert(e); $( "#message" ).focus(); }); } else { chat.invoke( "send" , $( "#message" ).val()) .done(function () { //alert("发送成功!"); $( "#message" ).val( "" ).focus(); }) .fail(function (e) { alert(e); $( "#message" ).focus(); }); } }); }); var chat = conn.createHubProxy( "chat" ); chat. on ( "receiveMessage" , function (dt, cn, msg) { var clsName = "linfo" ; if (cn == clientName || cn.indexOf( "您对" ) >= 0) clsName = "rinfo" ; eChatBox.append( "<p class='" + clsName + "'>" + dt + " <strong>" + cn + "</strong> 说:<br/>" + msg + "</p>" ); eChatBox.scrollTop(eChatBox[0].scrollHeight); }); chat. on ( "userChange" , function (dt, msg, users) { eChatBox.append( "<p>" + dt + " " + msg + "</p>" ); eUsers.find( "option[value!='']" ).remove(); for ( var i = 0; i < users.length; i++) { if (users[i].Value == clientName) continue ; eUsers.append( "<option value='" + users[i].Key + "'>" + users[i].Value + "</option>" ) } }); }); </script> </head> <body> <h3>大众聊天室</h3> <div id= "chatbox" > </div> <div> <span>聊天名称:</span> @Html.TextBox( "clientname" , ViewBag.ClientName as string , new { @ readonly = "readonly" , style = "width:300px;" }) <span>聊天对象:</span> @Html.DropDownList( "users" , ViewBag.OnLineUsers as IEnumerable<SelectListItem>) </div> <div> @Html.TextArea( "message" , new { rows = 5, style = "width:500px;" }) <input type= "button" value= "发送消息" id= "btnSend" /> </div> </body> </html> |
服务端与客户端代码都比较简单,网上相关的说明也有,这里就不再解说了,只说一下这种方式JS端调用服务端方法采用:chat.invoke,而被服务端回调的方法则采用:chat.on (这里的chat是createHubProxy创建得来的)
第二种:采用集线器类(Hub)+自动生成代理模式
DEMO - 2 示例代码如下:
服务端与DEMO 1相同,无需改变
客户端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
<! DOCTYPE html> < html > < head > < meta name="viewport" content="width=device-width" /> < meta charset="utf-8" /> < title >聊天室</ title > < script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></ script > < script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></ script > < script src="~/signalr/hubs" type="text/javascript"></ script > < style type="text/css"> #chatbox { width: 100%; height: 500px; border: 2px solid blue; padding: 5px; margin: 5px 0px; overflow-x: hidden; overflow-y: auto; } .linfo { } .rinfo { text-align: right; } </ style > < script type="text/javascript"> $(function () { var clientName = $("#clientname").val(); var eChatBox = $("#chatbox"); var eUsers = $("#users"); var chat = $.connection.chat; $.connection.hub.qs = { "clientName": clientName }; chat.state.test = "test"; chat.client.receiveMessage = function (dt, cn, msg) { var clsName = "linfo"; if (cn == clientName || cn.indexOf("您对")>=0) clsName = "rinfo"; eChatBox.append("< p class='" + clsName + "'>" + dt + " < strong >" + cn + "</ strong > 说:< br />" + msg + "</ p >"); eChatBox.scrollTop(eChatBox[0].scrollHeight); } chat.client.userChange = function (dt, msg, users) { eChatBox.append("< p >" + dt + " " + msg + "</ p >"); eUsers.find("option[value!='']").remove(); for (var i = 0; i < users.length ; i++) { if (users[i].Value == clientName) continue; eUsers.append("<option value='" + users[i].Key + "'>" + users[i].Value + "</ option >") } } $.connection.hub.start().done(function () { $("#btnSend").click(function () { var toUserId = eUsers.val(); if (toUserId != "") { chat.server.sendOne(toUserId, $("#message").val()) .done(function () { //alert("发送成功!"); $("#message").val("").focus(); }) .fail(function (e) { alert(e); $("#message").focus(); }); } else { chat.server.send($("#message").val()) .done(function () { //alert("发送成功!"); $("#message").val("").focus(); }) .fail(function (e) { alert(e); $("#message").focus(); }); } }); }); }); </ script > </ head > < body > < h3 >大众聊天室</ h3 > < div id="chatbox"> </ div > < div > < span >聊天名称:</ span > @Html.TextBox("clientname", ViewBag.ClientName as string, new { @readonly = "readonly", style = "width:300px;" }) < span >聊天对象:</ span > @Html.DropDownList("users", ViewBag.OnLineUsers as IEnumerable< SelectListItem >) </ div > < div > @Html.TextArea("message", new { rows = 5, style = "width:500px;" }) < input type="button" value="发送消息" id="btnSend" /> </ div > </ body > </ html > |
上述代码中特别需要注意的是,需要引用一个“不存在的JS目录”:<script src="~/signalr/hubs" type="text/javascript"></script>,为什么要打引号,是因为我们在写代码的时候是不存在的,而当运行后就会自动生成signalr的代理脚本,这就是与非自动生成代理脚本最根本的区别,也正是因为这个自动生成的脚本,我们可以在JS中更加方便的调用服务端方法及定义回调方法,调用服务端方法采用:chat.server.XXX,而被服务端回调的客户端方法则采用:chat.client.XXX
看一下上述两种的运行效果截图吧:
第三种:采用持久化连接类(PersistentConnection)
DEMO - 3 示例代码如下:
服务端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
//Startup类: using System; using System.Threading.Tasks; using Microsoft.Owin; using Owin; using Microsoft.AspNet.SignalR; [assembly: OwinStartup( typeof (TestWebApp.Models.Startup))] namespace TestWebApp.Models { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR<MyConnection>( "/MyConnection" ); } } } //MyConnection类: using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Web; using Microsoft.AspNet.SignalR; namespace TestWebApp.Models { public class MyConnection : PersistentConnection { private static List< string > monitoringIdList = new List< string >(); protected override Task OnConnected(IRequest request, string connectionId) { bool IsMonitoring = (request.QueryString[ "Monitoring" ] ?? "" ).ToString() == "Y" ; if (IsMonitoring) { if (!monitoringIdList.Contains(connectionId)) { monitoringIdList.Add(connectionId); } return Connection.Send(connectionId, "ready" ); } else { if (monitoringIdList.Count > 0) { return Connection.Send(monitoringIdList, "in_" + connectionId); } else { return Connection.Send(connectionId, "nobody" ); } } } protected override Task OnReceived(IRequest request, string connectionId, string data) { if (monitoringIdList.Contains(connectionId)) { return Connection.Send(data, "pass" ); } return null ; } protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled) { if (!monitoringIdList.Contains(connectionId)) { return Connection.Send(monitoringIdList, "out_" + connectionId); } return null ; } } } |
WEB客户端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
<!-- MonitoringPage.cshtml 监控管理页面--> <! DOCTYPE html> < html > < head > < meta name="viewport" content="width=device-width" /> < title >MonitoringPage</ title > < script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></ script > < script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></ script > < style type="text/css"> table { border:1px solid #808080; width:600px; } td { border:1px solid #808080; padding:3px; } .odd{ } .even{ } .non-temptr { display:none; } </ style > < script type="text/javascript"> $(function () { $("#userstable tbody tr:odd").addClass("odd"); $("#userstable tbody tr:even").addClass("even"); var conn = $.connection("/MyConnection", {"Monitoring":"Y"}); conn.start().done(function () { $("#userstable").delegate("button.pass", "click", function () { var rid = $(this).parent("td").prev().attr("data-rid"); conn.send(rid); var tr = $(this).parents("tr"); tr.remove(); }); }).fail(function (msg) { alert(msg); }); conn.received(function (msg) { if (msg == "ready") { $("#spstatus").html("监控服务已就绪"); return; } else if (msg.indexOf("in_") == 0) { var tr = $(".non-temptr").clone(true); tr.removeClass("non-temptr"); var td = tr.children().first(); var rid = msg.toString().substr("in_".length); td.html(rid + "进入被监控页面,是否允许?"); td.attr("data-rid", rid); $("#userstable tbody").append(tr); } else { var rid = msg.toString().substr("out_".length); $("td[data-rid=" + rid + "]").parent("tr").remove(); } }); }); </ script > </ head > < body > < div > 以下是实时监控到进入EnterPage页面的用户情况:(服务状况:< strong >< span id="spstatus"></ span ></ strong >) </ div > < table id="userstable"> < tr > < td >用户进入消息</ td > < td >授 权</ td > </ tr > < tr class="non-temptr"> < td ></ td > < td style="width:100px">< button class="pass">允许</ button ></ td > </ tr > </ table > </ body > </ html > <!-- EnterPage.cshtml 监控受限页面--> <! DOCTYPE html> < html > < head > < meta name="viewport" content="width=device-width" /> < title >EnterPage</ title > < script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></ script > < script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></ script > </ head > < body > < script type="text/javascript"> $(function () { var conn = $.connection("/MyConnection"); conn.start().fail(function (msg) { alert(msg); }); conn.received(function (data) { if (data == "pass") { $("#msg").html("管理员已审核通过,可以进入浏览详情。"); setTimeout(function () { self.location = "http://www.zuowenjun.cn"; }, 3000); } else { $("#msg").html("无管理员在线,请稍候再重新进入该页面。"); } }); }); </ script > < div id="msg"> 该页面浏览受限,已自动将您的浏览请求发给管理员,请稍候。。。 </ div > </ body > </ html > |
上述代码可以看出与采用Hub(集线器类)的不同之处,一是:Startup.Configuration中是需要指定app.MapSignalR<MyConnection>("/MyConnection"),二是需实现继承自PersistentConnection类的自定义的持久化连接类,在这个连接中可以重写:OnConnected、OnDisconnected、OnReceived、OnReconnected、ProcessRequest方法,同时有几个重要的属性成员Connection、Groups,服务端发消息给客户端采用:Connection.Broadcast(广播,所有客户端都可以收到消息),Connection.Send(发送给指定的客户端)
运行效果如下截图示:
SignalR支持额外附加:QueryString、Cookie、State,具体的客户端设置与服务端接收请见上面的代码,同时也可以参见如下其它博主总结的表格(SignalR的Javascript客户端API使用方式整理):
SignalR的三个Demo的更多相关文章
- 通过三个DEMO学会SignalR的三种实现方式
一.理解SignalR ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信(即:客户端(Web页面)和服务器端可以互相实时的通知消息 ...
- SignalR代理对象异常:Uncaught TypeError: Cannot read property 'client' of undefined 推出的结论 SignalR 简单示例 通过三个DEMO学会SignalR的三种实现方式 SignalR推送框架两个项目永久连接通讯使用 SignalR 集线器简单实例2 用SignalR创建实时永久长连接异步网络应用程序
SignalR代理对象异常:Uncaught TypeError: Cannot read property 'client' of undefined 推出的结论 异常汇总:http://www ...
- 通过三个DEMO学会SignalR的三种实现方式 转载https://www.cnblogs.com/zuowj/p/5674615.html
一.理解SignalR ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信(即:客户端(Web页面)和服务器端可以互相实时的通知消息 ...
- RobotFrameWork接口报文测试-----(三)demo的加强版(数据驱动测试)
在上一篇RobotFrameWork接口报文测试-----(二)demo的升级版基础上,将接口的xml的格式保存在xml文件中,然后程序如果增加一个接口,在xml文件里添加即可,无需修改自动化测试里的 ...
- vue学习-第三个DEMO(计算属性和监视) v-model基础用法
<div id="demo"> 姓:<input type="text" placeholder="First Name" ...
- SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)
SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...
- 【转】IOS AutoLayout详解(三)用代码实现(附Demo下载)
转载自:blog.csdn.net/hello_hwc IOS SDK详解 前言: 在开发的过程中,有时候创建View没办法通过Storyboard来进行,又需要AutoLayout,这时候用代码创建 ...
- 实现服务器端与客户端的高频实时通信 SignalR(2)
说明:本篇文章与上篇文章 实现服务器端与客户端的实时通信 SignalR(1) 基本代码类似,只是做了些处理 高频 的改动. 一.本文出处:SignalR 实例介绍 (建议看原著里面有DEMO下载) ...
- 实现服务器端与客户端的实时通信 SignalR(1)
一.本文出处:SignalR 实例介绍 (建议看原著里面有DEMO下载) 二.这篇文章介绍如何利用 VS2012 创建一个简单的实时聊天系统,建好后的样子如下(模拟三个在线用户): 三.Demo ...
随机推荐
- iOS技术面试06:应用程序
1.NSRunLoop的实现机制,及在多线程中如何使用 NSRunLoop是IOS消息机制的处理模式 >1.NSRunLoop的主要作用:控制NSRunLoop里面线程的执行和休眠,在有事情做的 ...
- iOS-UIAlertView与UIActionSheet
UIAlertView与UIActionSheet 6.11.1 常规调用 UIAlertView:调出一个模态对话框,屏幕居中显示 UIActionSheet:非模态对话框,屏幕下方弹出 Alert ...
- (1) laravel php artisan list make
php artisan list make Laravel Framework 5.4.36 Usage: command [options] [arguments] Options: -h, --h ...
- 简单介绍shell编程四剑客之sed
概要:分别的作用 grep:文本过滤(模式:pattern)工具,grep,egrep,fgrep,擅长过滤. sed:stream editor 文本编辑工具:(流编辑器),擅长取行.替换. awk ...
- php open_basedir的使用与性能分析
php open_basedir的使用与性能分析 使用方法 <pre>/*限制打开的目录*/ini_set('open_basedir', __DIR__.'/');</pre> ...
- 终端、虚拟终端、shell、控制台、tty的区别
终端与控制台的区别? 最近开始接触Linux,终端.虚拟终端.shell.控制台.tty等概念让我很混乱,有必要认识清楚. 今天看到有人问终端和控制台的区别,而且这个问题比较有普遍性,因此想抽出一点时 ...
- 题目21 包含Min函数的栈
///////////////////////////////////////////////////////////////////////////////////// // 3. 题目21 包含 ...
- 100天搞定机器学习|Day4-6 逻辑回归
逻辑回归avik-jain介绍的不是特别详细,下面再唠叨一遍这个算法. 1.模型 在分类问题中,比如判断邮件是否为垃圾邮件,判断肿瘤是否为阳性,目标变量是离散的,只有两种取值,通常会编码为0和1.假设 ...
- 机器学习-EM算法的收敛证明
上一篇开头说过1983年,美国数学家吴建福(C.F. Jeff Wu)给出了EM算法在指数族分布以外的收敛性证明. EM算法的收敛性只要我们能够证明对数似然函数的值在迭代的过程中是增加的 即可: 证明 ...
- python2.7 编码问题
python 2.7编码问题,着实令人头疼不已,这两天抽闲想真正弄明白.需要弄清楚这个问题,首先需要明白ASCII,Unicode 和 UTF-8之间的关系. 进行对上述几种概念进行描述之前,先进行简 ...