一,簡介
Signal 是微軟支持的一個運行在 Dot NET 平臺上的 html websocket 框架。它出現的主要目的是實現服務器主動推送(Push)消息到客戶端頁面,這樣客戶端就沒必要從新發送請求或使用輪詢技術來獲取消息。javascript
二,實現機制
SignalR 的實現機制與 .NET WCF 或 Remoting 是類似的,都是使用遠程代理來實現。在具體使用上,有兩種不一樣目的的接口:PersistentConnection 和 Hubs,其中 PersistentConnection 是實現了長時間的 Javascript 輪詢(相似於 Comet),Hub 是用來解決實時信息交換問題,它是利用 Javascript 動態載入執行方法實現的。SignalR 將整個鏈接,信息交換過程封裝得很是漂亮,客戶端與服務器端所有使用 JSON 來交換數據。css
下面就 Hubs 接口的使用來說講整個流程:html
1,在服務器端定義對應的 hub class;java
2,在客戶端定義 hub class 所對應的 proxy 類;jquery
3,在客戶端與服務器端創建鏈接(connection);web
4,而後客戶端就能夠調用 proxy 對象的方法來調用服務器端的方法,也就是發送 request 給服務器端;瀏覽器
5,服務器端接收到 request 以後,能夠針對某個/組客戶端或全部客戶端(廣播)發送消息。服務器
三,Hub 示例教程
1,工具準備
SignalR 運行在 .NET 4.5 平臺上,因此須要安裝 .NET 4.5。爲了方便演示,本示例使用 ASP.NET MVC 在 Win 7 系統來實現。這須要安裝 ASP.NET MVC 3 或 ASP.NET MVC 4。websocket
2,創建工程
打開 VS2010/VS2012 新建名爲 SignalRTutorial 的 ASP.NET MVC 3 Web Application 工程,選擇 Internet Application 模板, Razor 視圖引擎以及勾選 Use HTMl 5 標籤。框架
3,安裝 SignalR
打開 NuGet 的 package manager console(Tools->Library package manager),輸入:install-package SignalR.Sample,回車安裝。如圖所示:
4,實現 Hub 服務器端代碼
向工程中新建 SignalR 目錄,在其中添加 ChatHub.cs 文件,內容以下:
namespace SignalTutorial.SignalR
{
[HubName("chat")]
public class Chat : Hub
{
public void Send(string clientName, string message)
{
//var toSelfinfo = "You had sent message " + message;
//Caller.addSomeMessage(clientName, toSelfinfo);
// Call the addMessage method on all clients
Clients.addSomeMessage(clientName, message);
//Clients[Context.ConnectionId].addSomeMessage(clientName, data);
}
}
}
在上面的代碼中:
1),HubName 這個特性是爲了讓客戶端知道如何創建與服務器端對應服務的代理對象,若是沒有設定該屬性,則以服務器端的服務類名字做爲 HubName 的缺省值;
2),Chat 繼承自 Hub,從下面 Hub 的接口圖能夠看出:Hub 支持向發起請求者(Caller),全部客戶端(Clients),特定組(Group) 推送消息。
3),public void Send(string clientName, string message) 這個接口是被客戶端經過代理對象調用的;
4),Clients 是 Hub 的屬性,表示全部連接的客戶端頁面,它和 Caller 同樣是 dynamic,由於要直接對應到 Javascript 對象;
5),Clients.addSomeMessage(clientName, message); 表示服務器端調用客戶端的 addSomeMessage 方法,這是一個 Javascript 方法,從而給客戶端推送消息。
6),總結:這裏實現的服務很簡單,就是當一個客戶端調用 Send 方法向服務器發送 message 後,服務器端負責將該 message 廣播給全部的客戶端(也能夠給特定組或特定客戶端,見屏蔽代碼),以實現聊天室的功能。
5,實現 Hub 客戶端代碼
1),引用 SignalR Javascript
爲了簡化引用 SignalR 腳本操做,我直接在 View/Shared/_Layout.cshtml 中引入 SignalR 及其餘腳本:
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-1.6.4.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-ui-1.8.24.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.signalR-0.5.3.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/signalr/hubs")" type="text/javascript"></script>
</head>
注意:signalR 依賴於 jquery,因此 signalR 必須放在 jquery 以後,而 hubs 又必須放在 signalR 以後。
而後在 body 部分加入 HubChat Tab:
<li>@Html.ActionLink("HubChat", "HubChat", "Home")</li>
2),生成訪問頁面
在 HomeController 中添加以下方法:
public ActionResult HubChat()
{
ViewBag.ClientName = "用戶-" + Rnd.Next(10000, 99999);
return View();
}
這裏由服務器根據隨機數來設定客戶端的名字,不夠嚴謹,由於隨機數生成的名字不是惟一的的,在這裏僅爲簡化演示,實際應用中應該使用 GUID 。
而後生成對應的 View:HubChat.cshtml
@model dynamic
@{
ViewBag.Title = "title";
}
<script src="@Url.Content("~/Scripts/hubDemo.js")" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
});
</script>
<h2>Hub Chat</h2>
<div>
<input type="text" id="Placeholder" value="@ViewBag.ClientName" hidden="true"/>
<input type="text" id="msg" />
<input type="button" id="broadcast" value="廣播" />
<br />
<br />
<h3>
消息記錄: (你是:<span id="MyClientName">@ViewBag.ClientName</span>):
</h3>
<ul id="messages">
</ul>
</div>
在上面的頁面代碼中,我添加了名爲 hubDemo.js 的腳本,這將在下面介紹;此外還有一個id 爲 Placeholder 的隱藏 input 控件,這是爲了向 Javascript 中傳遞客戶端的名字。
3),編寫 Javascript
向 Scripts 目錄添加新的 Javescript 腳本:hubDemo.js。其內容以下:
$(function () {
var myClientName = $('#Placeholder').val();
// Proxy created on the fly
var chat = $.connection.chat;
// Declare a function on the chat hub so the server can invoke it
chat.addSomeMessage = function (clientName, message) {
writeEvent('<b>' + clientName + '</b> 對你們說: ' + message, 'event-message');
};
$("#broadcast").click(function () {
// Call the chat method on the server
chat.send(myClientName, $('#msg').val())
.done(function () {
console.log('Sent message success!');
})
.fail(function (e) {
console.warn(e);
});
});
// Start the connection
$.connection.hub.start();
//A function to write events to the page
function writeEvent(eventLog, logClass) {
var now = new Date();
var nowStr = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds();
$('#messages').prepend('<li class=" + logClass + "><b>' + nowStr + '</b> ' + eventLog + '.</li>');
}
});
上面代碼有詳細的註釋,下面再講講關鍵之處:
1,首先獲取客戶端頁面的名字;
2,而後經過 $.connection.chat 創建對應服務器端 Hub 類的代理對象 chat;
3,定義客戶端的 Javascript 方法 addSomeMessage ,服務器經過 dynamic 方式調用客戶端的該方法以實現推送功能。在這裏每當收到服務器推送來的消息,就在客戶端頁面的 messages 列表表頭插入該消息。
4,當點擊廣播按鈕時,客戶端經過代理對象調用服務器端的 send 方法以實現向服務器發送消息。
5,經過 $.connection.hub.start(); 語句打開連接。
6),編譯運行 Hub 示例
在多個瀏覽器窗口打開頁面,效果以下:
四,Persistent Connection 示例教程
1,實現服務器端代碼
1),編寫服務器 PersistentConnection 代碼
向工程中 SignalR 目錄中添加 PersistentConnection.cs 文件,內容以下:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using SignalR;
namespace SignalTutorial.SignalR
{
public class MyConnection : PersistentConnection
{
protected override Task OnConnectedAsync(IRequest request, string connectionId)
{
return Connection.Broadcast("Connection " + connectionId + " connected");
}
protected override Task OnReconnectedAsync(IRequest request, IEnumerable<string> groups, string clientId)
{
return Connection.Broadcast("Client " + clientId + " re-connected");
}
protected override Task OnReceivedAsync(IRequest request, string connectionId, string data)
{
var info = data + ". ConnectionId is [" + connectionId + "]";
// return Connection.Send(connectionId, info);
// Broadcast data to all clients
return Connection.Broadcast(info);
}
protected override Task OnDisconnectAsync(string connectionId)
{
return Connection.Broadcast("Connection " + connectionId + " disconncted");
}
protected override Task OnErrorAsync(Exception error)
{
return Connection.Broadcast("Error ocurred " + error);
}
}
}
在上面的代碼中:
1,MyConnection 繼承自 PersistentConnection,這樣咱們就能在客戶端鏈接,重鏈接,斷開鏈接,發送消息以及鏈接出錯的狀況下進行相關的處理。從下面的 PersistentConnection 接口中能夠看到,PersistentConnection 一樣支持組進行推送。
2,推送消息由 PersistentConnection 的屬性 Connection 來提供,它繼承自 IConnection 接口,該接口提供兩個函數來實現對特定客戶端的推送和廣播功能。
System.Threading.Tasks.Task Send(string signal, object value)
System.Threading.Tasks.Task Broadcast(object value)
2),配置訪問路由
爲了支持客戶端訪問,須要在路由表中進行配置。打開 Global.asax.cs ,修改 Application_Start() 函數以下:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteTable.Routes.MapConnection<MyConnection>("echo", "echo/{*operation}");
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
// Make connections wait 50s maximum for any response. After
// 50s are up, trigger a timeout command and make the client reconnect.
GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(50);
//DisconnectTimeout
//HeartBeatInterval
//KeepAlive
}
在上面的代碼中,我將 echo 及其子路徑的訪問映射到 MyConnection 上,並設置鏈接超時時間爲 50 s。在這裏還能夠設置其餘的一些參數,如斷連超時時間,心跳間隔等。
2,實現客戶端代碼
1),生成訪問頁面
在前面三 Hub 示例教程的基礎上,咱們向該工程加入使用 Persistent Connection 的演示。和前面同樣,向 _Layout.cshtml 中加入 PersistentChat Tab:
<li>@Html.ActionLink("PersistentChat", "PersistentChat", "Home")</li>
而後在 HomeController 中添加以下方法:
public ActionResult PersistentChat()
{
ViewBag.ClientName = "用戶-" + Rnd.Next(10000, 99999);
return View();
}
這裏由服務器根據隨機數來設定客戶端的名字,不夠嚴謹,由於隨機數生成的名字不是惟一的的,在這裏僅爲簡化演示,實際應用中應該使用 GUID 。
而後生成對應的 頁面: PersistentChat.cshtml:
@model dynamic
@{
ViewBag.Title = "title";
}
<script src="@Url.Content("~/Scripts/persistent.js")" type="text/javascript"></script>
<h2>Persistent Chat</h2>
<div>
<input type="text" id="Placeholder" value="@ViewBag.ClientName" hidden="true"/>
<input type="text" id="msg" />
<input type="button" id="broadcast" value="廣播" />
<br />
<br />
<h3>
消息記錄: (你是:<span id="MyClientName">@ViewBag.ClientName</span>):
</h3>
<ul id="messages">
</ul>
</div>
在上面的頁面代碼中,我添加了名爲 persistent.js 的腳本,這將在下面介紹;此外還有一個id 爲 Placeholder 的隱藏 input 控件,這是爲了向 Javascript 中傳遞客戶端的名字。
2),編寫 Javascript
向 Scripts 目錄添加新的 Javescript 腳本:persistent.js。其內容以下:
$(function () {
var myClientName = $('#Placeholder').val();
var connection = $.connection('/echo');
connection.received(function (data) {
var msg = new String(data);
var index = msg.indexOf("#");
var clientName = msg.substring(0, index);
var content = msg.substring(index + 1);
if (clientName == null || clientName == "") {
writeEvent('<b>' + "系統消息" + '</b>: ' + content, 'event-message');
}
else {
writeEvent('<b>' + clientName + '</b> 對你們說: ' + content, 'event-message');
}
});
connection.start();
$("#broadcast").click(function () {
var msg = myClientName + "#" + $('#msg').val();
connection.send(msg);
});
//A function to write events to the page
function writeEvent(eventLog, logClass) {
var now = new Date();
var nowStr = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds();
$('#messages').prepend('<li class=" + logClass + "><b>' + nowStr + '</b> ' + eventLog + '.</li>');
}
});
上面的代碼基本與前面的 Hub 實現相同,在此就再也不一一講述。有兩點值得說明:
1,建立鏈接時,指定路徑爲 "/echo",該路徑在服務器端的路由映射表被映射爲 MyConnection,於是這個鏈接就被指向前面提供的 MyConnection。
2,將 clientName 信息放入 message 中,並用 # 將 clientName 和消息內容鏈接成一個 msg。
3,編譯運行 Persistent 示例