1、理解SignalRjavascript
ASP .NET SignalR 是一個ASP .NET 下的類庫,能夠在ASP .NET 的Web項目中實現實時通訊(即:客戶端(Web頁面)和服務器端能夠互相實時的通知消息及調用方法),SignalR有三種傳輸模式:LongLooping(長輪詢)、WebSocket(HTML5的WEB套接字)、Forever Frame(隱藏框架的長請求鏈接),能夠在WEB客戶端顯式指定一種或幾種,也能夠採起默認(推薦),若採起默認,SignalR會根據瀏覽器的環境自動選擇合適的傳輸方式。css
2、SignalR的三種實現方式html
第一種:採用集線器類(Hub)+非自動生成代理模式:服務端與客戶端分別定義的相對應的方法,客戶端經過代理對象調用服務端的方法,服務端經過IHubConnectionContext回調客戶端的方法,客戶端經過回調方法接收結果。java
以前我寫過一篇文章《分享一個基於長鏈接+長輪詢+原生的JS及AJAX實現的多人在線即時交流聊天室》,是經過長輪詢+長鏈接的方式來實現的在線多人聊天室功能,從代碼量來看就知道實現起來並不簡單,而現在有了SignalR,會簡單不少,我這裏使用SignalR再來寫一個簡單的在線多人聊天室示例,以便你們快速掌握SignalR。jquery
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客戶端:app
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)+自動生成代理模式ide
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使用方式整理):