通过三个DEMO学会SignalR的三种实现方式

简介:

一、理解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{ background-color: #bbf;}
         .even{ background-color:#ffc; }
         .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使用方式整理):

本文转自 梦在旅途 博客园博客,原文链接:http://www.cnblogs.com/zuowj/p/5674615.html   ,如需转载请自行联系原作者
相关文章
|
存储 前端开发 JavaScript
【一步步一起学DApp开发】(四)web3.js 基本使用 | 连接geth | 创建web客户端
【一步步一起学DApp开发】(四)web3.js 基本使用 | 连接geth | 创建web客户端
601 0
❤️一个聊天室案例带你了解Node.js+ws模块是如何实现websocket通信的!
❤️一个聊天室案例带你了解Node.js+ws模块是如何实现websocket通信的!
180 0
❤️一个聊天室案例带你了解Node.js+ws模块是如何实现websocket通信的!
|
开发框架 .NET 开发工具
SignalR 2.x入门(一):SignalR简单例子
SignalR 2.x入门(一):SignalR简单例子
156 0
SignalR 2.x入门(一):SignalR简单例子
|
网络协议 监控 API
ESFramework Demo -- P2P通信Demo(附源码)
现在我们将在ESFramework Demo -- 文件传送Demo 的基础上,使用ESPlus提供的第四个武器,为其增加P2P通信的功能。在阅读本文之前,请务必先掌握ESFramework 开发手册(04) -- 可靠的P2P 一文中介绍的P2P的基础知识以及相关API的用法。
1117 0
|
JavaScript
SignalR简单Demo
  我们实现一个简单的消息通知的Demo    在NuGet中添加SignalR引用 install-package Microsoft.AspNet.SignalR  然后我们创建一个类来引用Hub类 namespace SignalRDemo.
1248 0
|
监控 JavaScript 前端开发
通过三个DEMO学会SignalR的三种实现方式
原文:通过三个DEMO学会SignalR的三种实现方式 一、理解SignalR ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信(即:客户端(Web页面)和服务器端可以互相实时的通知消息及调用方法),SignalR有三种传输模式:LongLooping(长轮询)、WebSocket(HTML5的WEB套接字)、Forever Frame(隐藏框架的长请求连接),可以在WEB客户端显式指定一种或几种,也可以采取默认(推荐),若采取默认,SignalR会根据浏览器的环境自动选择合适的传输方式。
1312 0
|
JavaScript C#
SignalR的简单使用(二)
原文:SignalR的简单使用(二)   之前提到SignalR代理在网页,通过生成的Js来完成相关的功能。但我不禁想一个问题, 难到SignalR的服务端就能寄存在web端吗,通过访问网页能方式才能启动服务,还有客户端 也只能在web端吗?经过看官网的教程得到 了结论,两者的答案都是否定的。
1142 0
|
JavaScript .NET 开发框架
SignalR的简单实现(一)
原文:SignalR的简单实现(一)    ASP.NET SignalR是ASP.NET开发人员的一个新库,它使您的应用程序添加实时Web功能变得非常简单。什么是“实时网络”功能?能够实时地将服务器端代码推送到连接的客户端的能力。
1325 0
|
前端开发 C#
SignalR---DOTNET客户端
原文:SignalR---DOTNET客户端 这里面有用到异步的相关知识,本人前几篇文章也简单的提到。 SignalR客户端要寄宿在.NET的客户端,必须安装Microsoft.AspNet.SignalR.Client。
1153 0