SignalR代理对象异常:Uncaught TypeError: Cannot read property 'client' of undefined 推出的结论
异常汇总:http://www.cnblogs.com/dunitian/p/4523006.html#signalR
后台创建了一个DntHub的集线器
前台在调用的时候出现了问题(经检查是代理对象创建失败)
于是到StackOverflow上面找了下:
http://stackoverflow.com/questions/14146913/signalr-cannot-read-property-client-of-undefined
上面说改成小写就ok了,很多人也解决成功了
逆天改成小写后也解决了,var chat = $.connection.dntHub
也许很多人就直接忽略了~~~~but,我为什么这样就解决了呢?C#的命名规则就是首字母大写啊?
逆天喜欢深究一下,于是打开其动态生成的js,发现了这么一句
so,原来默认生成了的就是小写开头的,,,,,,,,(⊙o⊙)… 很多人说结束了? NONONO
程序猿需要什么?想象力和反常规的想象力!
那么我就大胆设想,我们是不是可以指定名字呢?
上网搜了下,原来通过 HubName("xxx")可以设置名字
扩展一下,通过这个可以设置任意名字,不见得和类名相同
那么再试试?
看看动态生成的js,
嘿嘿,爽!
结论:
如果不自己设置HubName,那么SignalR会自动帮我们生成一个和类名相同并且以小写开头的HubName
这个问题有两种解决方法,一种js中用首字母小写的HubName,另一种自己指定。(前台建议都是小写)
SignalR 简单示例
一、什么是 SignalR
ASP.NET SignalR is a library for ASP.NET developers that simplifies the process of adding real-time web functionality to applications. Real-time web functionality is the ability to have server code push content to connected clients instantly as it becomes available, rather than having the server wait for a client to request new data.
二、简单示例
新建项目 SignalRChat
选择 Empty 模板
安装 SignalR
添加完查看 Scripts 文件夹
添加 Signal Hub Class (V2)
代码如下
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Microsoft.AspNet.SignalR; namespace SignalRChat { public class ChatHub : Hub { public void Send(string name, string message) { Clients.All.broadcastMessage(name, message); } } }
添加 OWIN Startup class
代码如下
using System; using System.Threading.Tasks; using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(SignalRChat.Startup))] namespace SignalRChat { public class Startup { public void Configuration(IAppBuilder app) { // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888 app.MapSignalR(); } } }
添加 HTML 页面
代码如下
SignalR Simple Chat
F5 运行,复制 URL 再打开一个新浏览器窗口同时运行,分别输入 NameOne 和 NameTwo
如果是 IE 浏览器,打开 Soultion Explorer,可以看到 hubs 文件
F12 打开控制台,发现使用的是 ForeverFrame (如果想在控制台查看日志,代码中必须包含 $.connection.hub.logging = true;)
Chrome 效果如下
参考文章:http://www.asp.net/signalr/overview/getting-started/tutorial-getting-started-with-signalr
代码下载
SignalRChat
通过三个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"
,
"
).Replace(
"\n"
,
"
);
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"
,
"
).Replace(
"\n"
,
"
);
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
|
|
服务端与客户端代码都比较简单,网上相关的说明也有,这里就不再解说了,只说一下这种方式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
|
<
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
>")
}
}
$.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目录”:,为什么要打引号,是因为我们在写代码的时候是不存在的,而当运行后就会自动生成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类:
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
|
<
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
>
<
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
运行效果如下截图示:
SignalR支持额外附加:QueryString、Cookie、State,具体的客户端设置与服务端接收请见上面的代码,同时也可以参见如下其它博主总结的表格(SignalR的Javascript客户端API使用方式整理):
SignalR是.net中一个不错的推送框架。它包含长连接跟集线器两种方式,我需要的是两个项目之间的通讯,也就是A控制台项目,向B web项目发送数据,然后B就推送到各个用户页面。
下面的代码是A控制台项目代码
class getData
{
//日志输出
private static ILog log = log4net.LogManager.GetLogger(typeof(getData));
//连接web public static Connection connection =new Connection("http://localhost:4669/webConnections"); static getData() //静态构造初始化长连接参数 { //添加头文件,这样B就知道是A传输数据而不是页面客户端,还可以使用connection.CookieContainer来区分 connection.Headers.Add("where", "windowserver"); try { connection.Received+=Console.WriteLine;//控制台输出 connection.Start().Wait(); //wait()方法十分重要,要不然就异步执行后面的语句了 } catch (System.Exception ex) { log.Error("无法连接web应用,Start方法不能使用" + ex.ToString()); throw ex; } } public void QueryDataAndToWeb(object sta) { string datajson = connection.JsonSerializeObject(sta);//序列化成JSON传送到web try { connection.Send(datajson).Wait(); } catch (System.Exception ex) { Console.Write(ex); log.Error("无法连接web应用,Send方法不能使用" + ex.ToString()); } } }
- 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
在上述代码中,我只是简单的写出使用方法,一些异常处理,还没处理完。
下面贴出B web项目
创建一个Startup.cs
[assembly: OwinStartup(typeof(Startup))]
namespace signalr
{
public class Startup
{
public void Configuration(IAppBuilder app) { // 有关如何配置应用程序的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkID=316888 // 配置上文实现的ChatConnections app.MapSignalR("/webConnections"); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
ChatConnection类:
public class ChatConnection : PersistentConnection
{
private static string _WinServiceID = "";//为空字符串表示断线,用于从内部触发推送 protected override Task OnConnected(IRequest request, string connectionId) { string where = request.Headers.Get("where"); if (where !=null&&where.Equals("windowserver")) _WinServiceID = connectionId; return Connection.Send(connectionId, "welcome!"); } protected override Task OnReceived(IRequest request, string connectionId, string data) { string where = request.Headers.Get("where"); if (where !=null&&where.Equals("windowserver")) Connection.Broadcast(Newtonsoft.Json.JsonConvert.DeserializeObject(data), connectionId); else {//说明页面需要一些请求比如添加删除修改之类东西,并且需要发送到A项目 Connection.Send(_WinServiceID, data); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
这个时候有个问题就是A项目接受B项目发送的数据,比如B项目客户端页面删除,增加,修改A项目的东西,那就不能用控制台输出,这时候需要对传送的数据进行分析是增加还是删除还是修改。
connection.Received += data => { connection_Received(data); };//用这个替换上面代码 connection.Received+=Console.WriteLine;
public void connection_Received(string obj) { //在这里面对web发送的数据进行解析然后进行操作 switch (obj) { case "delete": break; default: //包含添加跟更新两种命令 break; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
页面javaScript的客户端连接我就不贴出代码了,那些百度搜索资料很多也可以看下面给出的链接地址。
这样就完成一个简单的两个项目之间的通讯,两者之间可以相互利用JSON通讯。
SignalR参考链接:
https://www.asp.net/signalr/overview/getting-started/tutorial-getting-started-with-signalr#run
http://www.cnblogs.com/Wddpct/p/5650015.html#2.1
SignalR 集线器简单实例2
1.路由配置
//注册集线器路由 app.MapSignalR( "/realtime", new HubConfiguration() { });
2.服务端处理
/// /// 集线器定义 /// public class EchoHub : Hub { private static int _count = 0; /// /// 连接创建成功 /// /// public override async Task OnConnected() { Interlocked.Increment( ref _count); //通知其他连接 await Clients.Others.Message("新连接创建:" + Context.ConnectionId + ",已连接数量:" + _count); //通知当前连接 await Clients.Caller.Message("Hey,welcome!"); } /// /// 连接断开 /// /// /// public override Task OnDisconnected(bool stopCalled) { Interlocked.Decrement( ref _count); //通知所有连接 return Clients.All.Message(Context.ConnectionId + " 连接关闭,剩余连接数:" + _count); } /// /// 广播消息 /// public Task Broadcast(string message) { //通知所有连接 return Clients.All.Message(Context.ConnectionId + "> " + message); } /// /// 获取当前在线用户的连接ID /// /// public string GetCurrentID() { return Context.ConnectionId; } /// /// 获取当前请求的cookie列表 /// /// public object GetCurrentCookie() { return Context.RequestCookies.ToArray(); } /// /// 获取所有在的连接 /// /// public object GetAllID() { // GlobalHost.ConnectionManager.GetHubContext<>(); return Clients.All; } }
3.客户端处理
<h1>Echo hubh1> <div> <input type="text" id="text" /> <button id="send">Sendbutton> <button id="GetCurrentID">获取当前在线用户的IDbutton> <button id="GetAllID">获取所有在线用户的IDbutton> <button id="GetCookies">获取当前请求的cookiebutton> div> @*与服务器端集线器 建立连接*@ <script src="/realtime/js">script> <script type="text/javascript"> $( function () { //服务器端对应的集线器 var hub = $.connection.echoHub; $.connection.loggint = true; $.connection.url = '/realtime'; //客户端接收消息 hub.client.message = function (text) { $( "body").append(text + "
"); } //启动连接成功 $.connection.hub.start().done( function () { //广播消息 $( '#send').click(function () { var text = $('#text').val(); //调用服务器端,广播消息 hub.server.broadcast(text) }); //获取在线用户的ID $( '#GetCurrentID').click(function () { var deferred = hub.server.getCurrentID(); deferred.done( function (data) { console.info(data); alert(data); }); }); //获取所有用户在线ID,失败 $( '#GetAllID').click(function () { var deferred = hub.server.getAllID(); deferred.done( function (data) { alert(data); console.info(data); }); deferred.fail( function (err) { console.info(err) alert(err.message); }); }); //获取当前请求的cookie $( '#GetCookies').click(function () { var deferred = hub.server.getCurrentCookie(); deferred.done( function (data) { console.info(data); console.info(JSON.stringify(data)); alert(data); }).fail( function (err) { console.info(err); alert(err.message); }); }); }); }); script>
获取当前连接的ID
获取当前连接上下文中的cookie
获取所有在线连接的ID失败
用SignalR创建实时永久长连接异步网络应用程序
2012-11-06 13:44 by Zhuang miao, 7181 阅读, 0 评论, 收藏, 编辑
原文发表地址: Asynchronous scalable web applications with real-time persistent long-running connections with SignalR
原文发表时间: 2011-08-29 09:29
SignalRSample.zip
我最近在研究异步和衡量的问题。你可能看过我之前写的博文:我研究的 node.js和iisnode在Windows上运行。
每个应用程序都有不同的要求,“衡量”的规则不是对每一种应用程序都适用的。衡量一个获取数据和循环的网络应用和那些召集深度潜在的主框架应用,保持服务器永久连接的应用是不一样的。
古语说“当你手上只有榔头的时候,看什么都像是钉子”,这个在编程和网络世界里的确是真理。工具越多并且掌握使用它们的技能那么效果就会越好。那也是为什么我不仅仅宣扬多种语言编程,还希望大家深入研究自己的主要语言。比如当你真正学会了LINQ,并且很擅长使用dynamic,C#就变成了一种更加有趣和富有表现力的语言。
更新是用榔头锤钉子的常见例子。想做一个聊天程序?每隔5秒更新一次。处理时间很长?那就丢掉动画图片,不停地更新,我亲爱的朋友!
间隔长一段时间来更新是另一种方法。简单来说就是打开一个连接然后保持打开状态,强制客户端(浏览器)等待,假装需要很长时间才能返回结果。如果你的服务器端程序模型上有足够多的控件,这就能允许你按照你期望的来返回数据在打开的连接上。如果连接断开了,连接会无缝地被重新打开,断开信息会在两个端口都隐藏掉。在WebSockets将会是另一种解决这类问题的方法。
ASP.NET中的永恒连接
在聊天应用程序或者股票应用程序中用ASP.NET做这样的永久连接不是很容易。在服务器或客户库中还没有一个合适的概念来讨论它。
SignalR是为ASP.NET而设的一个异步信号库。我们团队正在研究这个项目,希望能创建实时的多用户网络应用。
这不就是Socket.IO或者nowjs么?
Socket.IO是一个客户端JavaScript库,能与node.js进行交流。Nowjs是能让你从服务器端调用客户的类库。这些和Signalr都很相似而且相关,只是同一个概念中的不同方面。这些JavaScript库都希望在服务器端有特定的东西和协定,这样就有可能让服务器端的显示如同客户希望看到的那样。
SignalR是一个完全基于客户及服务器端解决方案,它是以JS作为客户端和ASP.NET作为服务端来创建这类的应用。你可以去GitHub获取。
我能用12行代码创建一个聊天应用吗?
我想说
“在代码的世界中,简洁明了永远不是神话。”
换句话说,我希望我能这么说,当然可以!
1: Chat.DoItBaby()
但是那可能是一个谎言,下面是SignalR中的一个真实的聊天应用程序例子:
客户:
1: var chat = $.connection.chat;
2: chat.name = prompt("What's your name?", "");
3:
4: chat.receive = function(name, message){
5: $("#messages").append(""+name+": "+message);
6: }
7:
8: $("#send-button").click(function(){
9: chat.distribute($("#text-input").val());
10: });
服务器:
1: public class Chat : Hub {
2: public void Distribute(string message) {
3: Clients.receive(Caller.name, message);
4: }
5: }
那也许是12行,其实可以缩到9行的,如果你愿意的话。
有关SignalR的更多细节
SignalR在NuGet上被分成了几个包:
· SignalR – 主要的包,包括SignalR.Server和SignalR.Js(你应该安装这个)
· SignalR.Server – 服务器端组件用以创建SignalR端点
· SignalR.Js – SignalR的Javascript客户端
· SignalR.Client – SignalR的.NET客户端
· SignalR.Ninject - SignalR 的Ninject 相关解决方案
如果你只是想了解一下玩一玩,那就从Visual Studio 2010开始。
首先,创建一个空白的ASP.NET应用程序,用NuGet安装SignalR包,用NuGet的UI或者Package Console都可以。
其次,创建一个新的default.aspx页面,添加一个按钮,一个文本框,用以下脚本来引用jQuery和jQuery.signalR。
1:
2:
3:
4:
5:
6:
7:
8:
9:
10: $(function () {
11: var connection = $.connection('echo');
12: connection.received(function (data) {
13: $('#messages').append('
' + data + ' ');14: });
15: connection.start();
16: $("#broadcast").click(function () {
17: connection.send($('#msg').val());
18: });
19: });
20:
21:
22:
23:
24:
25:
26:
27:
底层连接
注意我们是从客户端调用/echo吗?这个在Global.asax中有所介绍:
1: RouteTable.Routes.MapConnection
("echo", "echo/{*operation}"); 现在,我们有两个SignalR模型来选择。我们先来看看底层的这个。
1: using SignalR;
2: using System.Threading.Tasks;
3:
4: public class MyConnection : PersistentConnection
5: {
6: protected override Task OnReceivedAsync(string clientId, string data)
7: {
8: // Broadcast data to all clients
9: return Connection.Broadcast(data);
10: }
11: }
我们继承PersistentConnection这个类,基本上可以在这层中做我们想做的任何事,这有很多个选择:
1: public abstract class PersistentConnection : HttpTaskAsyncHandler, IGroupManager
2: {
3: protected ITransport _transport;
4:
5: protected PersistentConnection();
6: protected PersistentConnection(Signaler signaler, IMessageStore store, IJsonStringifier jsonStringifier);
7:
8: public IConnection Connection { get; }
9: public override bool IsReusable { get; }
10:
11: public void AddToGroup(string clientId, string groupName);
12: protected virtual IConnection CreateConnection(string clientId, IEnumerable
groups, HttpContextBase context); 13: protected virtual void OnConnected(HttpContextBase context, string clientId);
14: protected virtual Task OnConnectedAsync(HttpContextBase context, string clientId);
15: protected virtual void OnDisconnect(string clientId);
16: protected virtual Task OnDisconnectAsync(string clientId);
17: protected virtual void OnError(Exception e);
18: protected virtual Task OnErrorAsync(Exception e);
19: protected virtual void OnReceived(string clientId, string data);
20: protected virtual Task OnReceivedAsync(string clientId, string data);
21: public override Task ProcessRequestAsync(HttpContext context);
22: public void RemoveFromGroup(string clientId, string groupName);
23: public void Send(object value);
24: public void Send(string clientId, object value);
25: public void SendToGroup(string groupName, object value);
26: }
高端中转站
或者我们可以提高一级,在添加
至页面后为我们的聊天客户做这个:
1: $(function () {
2: // Proxy created on the fly
3: var chat = $.connection.chat;
4:
5: // Declare a function on the chat hub so the server can invoke it
6: chat.addMessage = function (message) {
7: $('#messages').append('
' + message + ' ');8: };
9:
10: $("#broadcast").click(function () {
11: // Call the chat method on the server
12: chat.send($('#msg').val());
13: });
14:
15: // Start the connection
16: $.connection.hub.start();
17: });
然后就没有跟踪的必要了,连接聊天会映射到服务器上,然后服务器就能回调客户端了。
1: public class Chat : Hub
2: {
3: public void Send(string message)
4: {
5: // Call the addMessage method on all clients
6: Clients.addMessage(message);
7: }
8: }
我想你的脑子到现在应该要炸了。这是C#,服务器端代码,我们告诉所有的客户调用addMessage() JavaScript函数。我们通过永久连接,发送客户函数名称以获得服务器端回应,从而回调客户端。这和NowJS很像,但是没有很多人熟悉这个技术。
SignalR会处理所有客户端和服务器端的连接,确保它保持连接,运行正常。它会为你的浏览器选择正确的连接方式,用异步衡量async,await技术(就像我在node.js博文中展示过的asp.net上的可衡量异步事件 I/O )。
想亲眼看看这个样本的运行?
我们在http://chatapp.apphb.com上有一个小小的聊天应用程序,快去看看吧。那里有aspnet的同仁。试试粘贴到你的YouTube或相册上吧!
这是早期的,不过还是一个很有趣的.NET新组件,以前也从没出现过。你可以随意去GitHub看看,和SignalR的作者David Fowler和Damian Edwards交流下。希望你们喜欢。