Asp.net通過SignalR2進行實時聊天
一:什么是signalR
Asp.net SignalR是微軟為實現(xiàn)實時通信的一個類庫。
一般情況下,signalR會使用JavaScript的長輪詢(long polling)的方式來實現(xiàn)客戶端和服務(wù)器通信,隨著Html5中WebSockets出現(xiàn),SignalR也支持WebSockets通信。
另外SignalR開發(fā)的程序不僅僅限制于宿主在IIS中,也可以宿主在任何應(yīng)用程序,包括控制臺,客戶端程序和Windows服務(wù)等,另外還支持Mono,這意味著它可以實現(xiàn)跨平臺部署在Linux環(huán)境下。
SignalR將整個信息的交換封裝起來,客戶端和服務(wù)器都是使用JSON來溝通的,在服務(wù)端聲明的所有Hub信息,都會生成JavaScript輸出到客戶端,.NET則依賴Proxy來生成代理對象,而Proxy的內(nèi)部則是將JSON轉(zhuǎn)換成對象。
客戶端和服務(wù)端的具體交互情況如下圖所示:

1、SignalR傳輸方式
1、HTML5 傳輸
這種傳輸方式當(dāng)然是需要對HTML5兼容支持。如果客戶端不支持HTML5 標(biāo)準(zhǔn),那么你可以使用其他方式。
- WebSocket: (前提是服務(wù)器和客戶端都可以支持WebSocket )。WebSocket 是唯一的能夠支持服務(wù)器和客戶端建立真正持久的雙向連接的傳輸方式,但是就是WebSocket要求的條件比較苛刻。
WebSocket真正能夠支持只是在最新的IE 瀏覽器、Chrome、Firefox和一些其他的瀏覽器比如Opera 和 Safari。 - Server Sent Events (SSE)服務(wù)端發(fā)送事件 :就是我們常說的EventSource (除了IE的其他瀏覽器都支持支持服務(wù)器發(fā)送事件)。
2、Comet 傳輸
下面的傳輸方式都是建立在Comet Web應(yīng)用模型上。這種傳輸方式會在瀏覽器或者其他客戶端保持一個長連接的HTTP請求,服務(wù)器可以利用這個請求將數(shù)據(jù)推送到客戶端,而不用等客戶端單獨去請求數(shù)據(jù)。
- Forever Frame:(只有IE瀏覽器支持)Forever Frame 會創(chuàng)建一個隱藏的IFrame ,這個IFrame 會向服務(wù)器發(fā)起一個端點請求,但是這個請求不會結(jié)束。服務(wù)器會不停的發(fā)送腳本到客戶端馬上執(zhí)行,提供一個從服務(wù)器端到客戶端的單向?qū)崟r連接。服務(wù)端到客戶端和客戶端到服務(wù)端分別是兩個不同的連接,就像一個標(biāo)準(zhǔn)的HTTP請求,一旦有數(shù)據(jù)需要發(fā)送就會創(chuàng)建一個新的連接。
- Ajax Long Polling長輪詢:長輪詢不會創(chuàng)建一個持久的連接,它是會保持一個客戶端與服務(wù)器的連接,直到服務(wù)器做出應(yīng)答就會立即關(guān)閉,然后創(chuàng)建一個新的連接。當(dāng)連接重置的時候這會導(dǎo)致一些延遲。
2、SignalR連接模式
- Http Persisten Connection(持久連接)對象:用來解決長時間連接的功能。
還可以由客戶端主動向服務(wù)器要求數(shù)據(jù),而服務(wù)器端不需要實現(xiàn)太多細節(jié),只需要處理PersistentConnection 內(nèi)所提供的五個事件:OnConnected, OnReconnected, OnReceived, OnError 和 OnDisconnect 即可。 - Hub(集線器)對象:用來解決實時(realtime)信息交換的功能,服務(wù)端可以利用URL來注冊一個或多個Hub,只要連接到這個Hub,就能與所有的客戶端共享發(fā)送到服務(wù)器上的信息,同時服務(wù)端可以調(diào)用客戶端的腳本。
SignalR將整個信息的交換封裝起來,客戶端和服務(wù)器都是使用JSON來溝通的,在服務(wù)端聲明的所有Hub信息,都會生成JavaScript輸出到客戶端,.NET則依賴Proxy來生成代理對象,而Proxy的內(nèi)部則是將JSON轉(zhuǎn)換成對象。
下面的圖展示了Hub和持久連接的關(guān)系,讓你對底層的傳輸技術(shù)一目了然。

二、服務(wù)端
使用vs2012創(chuàng)建一個新的ASP.NET Web Application項目。

1、添加服務(wù)端Hub
在解決方案中添加SignalR Hub類選項,然后創(chuàng)建文件ChatHub.cs。此步驟將創(chuàng)建ChatHub.cs類文件并添加一組腳本文件和 SignalR 支持到項目的程序集引用。
注意:可以使用NuGet命令 install-package Microsoft.AspNet.SignalR 來引用SignalR到項目中

然后在ChatHub類中輸入下面這段代碼。
using Microsoft.AspNet.SignalR;
namespace WebApplication2
{
public class ChatHub : Hub
{
//定義可以被網(wǎng)頁腳本訪問的公共方法
public void Send(string name, string message) //在客戶端可以使用hub.server.send()方法調(diào)用。
{
// 客戶端通過調(diào)用broadcastMessage來獲取服務(wù)端數(shù)據(jù)
Clients.All.broadcastMessage(name, message);//在客戶端通過hub.client.broadcastMessage=function(){…}定義這個回調(diào)函數(shù)。
}
}
}在ChatHub類中,可以看到它繼承自Microsoft.AspNet.SignalR.Hub類。從Hub類派生出類是構(gòu)建SignalR應(yīng)用程序的有用方式。在這個類(ChatHub)中定義的公共方法可以被網(wǎng)頁內(nèi)的腳本訪問。
在這段代碼中(ChatHub類),客戶端通過調(diào)用ChatHub.Send方法,并把名稱和消息內(nèi)容做為參數(shù)傳遞給ChatHub。而ChatHub則通過Clients.All.broadcastMessage方法把該消息廣播給所有客戶端。
Send方法演示了幾個Hub概念:
- 在hub上聲明一些公共方法,以便客戶端可以調(diào)用它們。
- 使用Microsoft.AspNet.SignalR.Hub??蛻舳藙討B(tài)屬性用于與連接到此中心的所有客戶端進行通信。
- 調(diào)用客戶機上的函數(shù)(如broadcastMessage函數(shù))來更新客戶機。
2、添加OWIN 啟動類
在Solution Explorer中,右擊項目,然后添加一個名為Startup的OWIN 啟動類,然后輸入下面這段代碼。
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(WebApplication2.Startup))]
namespace WebApplication2
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}三、客戶端
在項目中添加一個靜態(tài)網(wǎng)頁文件,并命名為index.html,然后右擊該網(wǎng)頁文件,并選擇Set As Start Page,在Index.html中輸入下面的代碼,注意引用正確的文件名。
<!DOCTYPE html>
<html>
<head>
<title>SignalR Simple Chat</title>
<style type="text/css">
.container {
background-color: #99CCFF;
border: thick solid #808080;
padding: 20px;
margin: 20px;
}
</style>
</head>
<body>
<div class="container">
<input type="text" id="message" />
<input type="button" id="sendmessage" value="Send" />
<input type="hidden" id="displayname" />
<ul id="discussion"></ul>
</div>
Reference the jQuery library.
<script src="Scripts/jquery-1.12.4.js"></script>
Reference the SignalR library.
<script src="Scripts/jquery.signalR-2.4.1.js"></script>
Reference the autogenerated SignalR hub script.
<script src="signalr/hubs"></script>
Add script to update the page and send messages.
<script type="text/javascript">
$(function () {
// 1、聲明一個代理,用于引用服務(wù)端hub
var chat = $.connection.chatHub;
// 2、創(chuàng)建一個回調(diào)函數(shù),讓Hub調(diào)用它來廣播消息。
chat.client.broadcastMessage = function (name, message) {
// Html編碼形式顯示名稱和消息
var encodedName = $('
').text(name).html();
var encodedMsg = $('
').text(message).html();;
// 將消息添加到頁面。
$('#discussion').append('
' + encodedName
+ ': ' + encodedMsg + '
');
};
// 獲取用戶名
$('#displayname').val(prompt('Enter your name:', ''));
// 將初始焦點設(shè)置為消息輸入框。
$('#message').focus();
// 3、開始連接和發(fā)送消息
$.connection.hub.start().done(function () {
$('#sendmessage').click(function () {
// 在Hub上調(diào)用Send方法.
chat.server.send($('#displayname').val(), $('#message').val());
// 清除文本框并為下一個注釋重置焦點。
$('#message').val('').focus();
});
});
});
</script>
</body>
</html>客戶端通過jquery.signalR.js和signalr/hubs來與服務(wù)器進行通信,首先它要聲明一個代理來引用集線器。
var chat = $.connection.chatHub
請注意:在JavaScript中,對服務(wù)器類及其成員的引用必須是camelCase形式。本例中將C#服務(wù)端的ChatHub類在JavaScript中的引用為chatHub。
然后它要再定義一個回調(diào)函數(shù),這個回調(diào)函數(shù)主要是為了讓服務(wù)器進行調(diào)用,從而將數(shù)據(jù)推送到客戶端。
chat.client.broadcastMessage = function (name, message) {
//TODO:接收服務(wù)器推送的消息
};而下面的代碼則是為了確保在將消息發(fā)送到服務(wù)器之前已經(jīng)與服務(wù)器建立了連接。.done 函數(shù)表示連接成功后為發(fā)送的按鈕綁定一個單擊事件。
// 與服務(wù)器建立連接后才能發(fā)送消息到服務(wù)器
$.connection.hub.start().done(function () {
$('#sendmessage').click(function () {
// 調(diào)用服務(wù)器端的ChatHub.Send方法,把消息發(fā)送到服務(wù)器
chat.server.send("name", "message");
});
});保存項目,并按F5運行,就可以實現(xiàn)B/S模式下的即時通訊。
四、運行結(jié)果
項目運行起來后,同時用多個瀏覽器打開的,輸入各自的姓名之后,就能夠?qū)崿F(xiàn)即時通訊了。回到我們的vs,還能夠看到自動生成的hubs腳本文件,如下圖所示。


三個不同的瀏覽器中的運行方式。 當(dāng) Tom、 Anand 和 Susan 發(fā)送消息時,所有瀏覽器實時更新:

在ServerHub重寫一個 OnConnected 方法來監(jiān)控客戶端的連接情況,程序運行的時候web頁面就使用$.connection.hub.start() 與signalR服務(wù)開始建立連接了,在調(diào)試的時候可以在輸入中看到 "客戶端連接成功!"
public override Task OnConnected()
{
System.Diagnostics.Trace.WriteLine("客戶端連接成功!");
return base.OnConnected();
}
五、一對一聊天實例
Clients.Client(connectionId).addMessage() 此方法的作用就是客戶端注冊addMessage方法,向指定連接Id的客戶端發(fā)送消息。一對一的聊天發(fā)送的消息也必須回發(fā)給自己,所以連接的Id可以通過Context.ConnectionId來獲取。當(dāng)然不用Client.Client(Context.ConnectionId) ,也可以使用Client.Caller()方法直接發(fā)送。
Client.Clients(IList connectionIds) 這個方法的意思就是想一組string 的幾個ConnectionId發(fā)送消息。類似于QQ上@好友的那種功能。
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace WebApplication2
{
[HubName("UserHub")]
public class UserHub : Hub
{
#region 字段
public static List users = new List();
#endregion 字段
#region 方法
//獲取所有用戶在線列表
private void GetUsers()
{
var list = users.Select(s => new { s.Name, s.ConnectionID }).ToList();
string jsonList = JsonConvert.SerializeObject(list);
Clients.All.getUsers(jsonList);
}
//登記名字
public void LoginIn(string name)
{
//查詢用戶
var user = users.SingleOrDefault(u => u.ConnectionID == Context.ConnectionId);
if (user != null)
{
user.Name = name;//登記名字
Clients.Client(Context.ConnectionId).showId(Context.ConnectionId);
}
GetUsers();
}
//發(fā)送消息
[HubMethodName("sendMessage")]
public void SendMessage(string connectionId, string message)
{
Clients.All.hello();
var user = users.Where(s => s.ConnectionID == connectionId).FirstOrDefault();
if (user != null)
{
Clients.Client(connectionId).addMessage(message + "" + DateTime.Now, Context.ConnectionId);
//給自己發(fā)送,把用戶的ID傳給自己
Clients.Client(Context.ConnectionId).addMessage(message + "" + DateTime.Now, connectionId);
}
else
{
Clients.Client(Context.ConnectionId).showMessage("該用戶已離線");
}
}
///
/// 重寫連接事件
///
///
public override Task OnConnected()
{
//查詢用戶
var user = users.Where(w => w.ConnectionID == Context.ConnectionId).SingleOrDefault();
//判斷用戶是否存在,否則添加集合
if (user == null)
{
user = new User("", Context.ConnectionId);
users.Add(user);
}
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
var user = users.Where(p => p.ConnectionID == Context.ConnectionId).FirstOrDefault();
//判斷用戶是否存在,存在則刪除
if (user != null)
{
//刪除用戶
users.Remove(user);
}
GetUsers();//獲取所有用戶的列表
return base.OnDisconnected(stopCalled);
}
#endregion 方法
}
public class User
{
#region 構(gòu)造函數(shù)
public User(string name, string connectionId)
{
this.Name = name;
this.ConnectionID = connectionId;
}
#endregion 構(gòu)造函數(shù)
#region 屬性
[Key]
public string ConnectionID { get; set; }
public string Name { get; set; }
#endregion 屬性
}
}到此這篇關(guān)于Asp.net通過SignalR2進行實時聊天的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
asp.net頁面狀態(tài)管理cookie和服務(wù)器狀態(tài)管理Session
Session變量將在服務(wù)器為每個連接建立一個字典對象,使用的是服務(wù)端保存。Cookie可能會有一個按照年月日來判斷的作廢日期,而Session級別的變量在連接超時后就作廢2010-09-09
Asp.Net+XML操作基類(修改,刪除,新增,創(chuàng)建)
更新內(nèi)容: 1,根據(jù)父節(jié)點屬性讀取字節(jié)點值 2,根據(jù)節(jié)點屬性讀取子節(jié)點值(較省資源模式)2008-07-07
手把手教你AspNetCore WebApi認證與授權(quán)的方法
這篇文章主要介紹了手把手教你AspNetCore WebApi認證與授權(quán)的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
詳解高效而穩(wěn)定的企業(yè)級.NET Office 組件Spire(.NET組件介紹之二)
這篇文章主要介紹了詳解高效而穩(wěn)定的企業(yè)級.NET Office 組件Spire(.NET組件介紹之二),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。2016-12-12
.NET實現(xiàn)可交互的WINDOWS服務(wù)的實例代碼
那么來看一下如何才能實現(xiàn)一個可交互的服務(wù)呢。步驟與實現(xiàn)基本的服務(wù)一樣2013-03-03
Asp.net中安全退出時清空Session或Cookie的實例代碼
網(wǎng)站中點擊退出,如果僅僅是重定向到登錄/出頁面,此時在瀏覽器地址欄中輸入登錄后的某個頁面地址如主頁,你會發(fā)現(xiàn)不用登錄就能訪問,這種退出并不安全了,下面通過本文給大家介紹安全退出時清空Session或Cookie的實例代碼2016-11-11

