本文介紹在ASP.NET MVC框架中如何使用SignalR進行實時通訊
1.如何在Web中實現實時通訊
實時通訊:
例如“消息提示”、“web聊天室”等。由於web瀏覽器中使用的是http協議(大部分請求)進行通訊,http被稱為是無狀態,每次http請求和應答都是通過建立tcp連接,發送數據反饋應答,關閉tcp連接。而且必須是客戶端先請求服務器端,服務器端再反饋給客戶端消息。並不能實現服務器端主動給客戶端推送消息的功能。
實時通訊的傳統實現方法:
1.刷新整個頁面。這種方法最為原始,頁面中設置一個時間觸發器,指定時間周期執行頁面刷新操作。即隔一定時間向服務器請求一次最新的數據。
2.刷新部分頁面。為了避免全部頁面的刷新,可通過ajax動態的刷新部分頁面。
3.輪詢(現階段較好的實現方式)。通過ajax向服務器詢問是否有新的數據,如果有新的數據,則動態的刷新當前頁面的指定內容。
SignalR方式:
SignalR是一個實現實時通訊的完整解決方案,包含服務器端和客戶端(瀏覽器端)的全部內容。
它會根據客戶端的不同而動態的選擇低層實現方案,如果瀏覽器支持html5,signalr就會選擇WebSocket方式;如果不支持,就會選擇Comet方式。實際上都是嘗試在服務器端和客戶端(瀏覽器端)建立持久連接。
關於SignalR的原理這里就不再敖述,具體內容請參見官方介紹:
http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/introduction-to-signalr
實現原理圖:

即:用戶A在瀏覽器中的某個操作,使得用戶A的瀏覽器端調用了服務器端的某個函數MyServerFunc(),同時傳過來想將消息發送給誰的用戶Id。服務器端再根據這個Id號,調用這個Id對應的用戶的瀏覽器端定義個某個函數:myClientFunc()。從而實現了用戶A實時發送消息給用戶B。
2.如何在ASP.NET MVC框架中使用SignalR
微軟的官方網站上介紹了兩種搭配方案:
1.ASP.NET MVC4搭配SignalR1.x
2.ASP.NET MVC5搭配SignalR2
(按照asp.net mvc框架和signalr的發布時間順序,他們應該就是這樣的搭配。但是筆者自己在項目開發中先選擇了MVC4框架,但是后來在選擇signalr時選擇了最新版本2.0.3。之前也是由於直接安裝的最新版本,后來發現項目中確實需要使用signalr2.x版本,后面會闡述為什么需要使用2.x版本)
所以,本文筆者的項目中的搭配方案為:ASP.NET MVC4 和 SignalR2.0.3
如果讀者中也有類似的需求,或者原來使用的是signalr1.x現在需要遷移到signalr2.x的,可以參考微軟的一篇文檔,介紹如何從signalr1.x遷移到signalr2
如何在項目中加入SignalR:
1.
使用Nuget程序包管理工具引入signalr。
Visual Studio2012中依次點擊:項目->管理Nuget程序包。搜索signalr,點擊安裝即可。(默認為最新版本)
或者
命令行方式:工具->庫程序包管理器->程序包管理器控制台,輸入:install-package Microsoft.AspNet.SignalR
安裝完成以后可以再scripts文件夾中看到新增加的文件:jquery.signalR-2.0.3.js等
服務器端:
2.
創建自己的hub類(如果想實現signalr的功能,服務器端函數必須實現自己的類,該類繼承signalr的hub類)
比如,我新建了一個文件夾叫Hubs,用於存放與signalr操作相關的文件。在hubs文件夾中新建了一個MessageHub.cs類,該類繼承Hub類
- public class MessageHub: Hub
- {
- public void NoticeUser()
- {
- Clients.User(UserId).newLog();
- }
- }
3.
添加Startup.cs
在項目中新建類文件,命名為:Startup.cs,內容為:
- <span style="font-size:12px;">using Microsoft.AspNet.SignalR;
- using Microsoft.Owin;
- using Owin;
- [assembly: OwinStartup(typeof(Sample.Startup))]
- namespace Sample
- {
- public class Startup
- {
- public void Configuration(IAppBuilder app)
- {
- //使用自己的idprovider,文章后面有介紹,如果不需要可以刪除這兩行
- var idProvider = new CustomUserIdProvider();
- GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
- // Any connection or hub wire up and configuration should go here
- app.MapSignalR();
- }
- }
- }</span>
瀏覽器端:
3.
添加script引用,添加必要的script代碼:
- <span style="font-size:12px;">$(function () {
- //創建與服務器的連接,指定連接到MessageHub這個hub類(注意MessageHub的m小寫)
- var chat = $.connection.messageHub;
- //定義瀏覽器端函數,該函數可通過服務器端的hub類中調用
- chat.client.newLog = function () {
- //some code by yourself
- }
- //啟動signalr的與服務器的連接
- $.connection.hub.start();
- })</span>
其中newlog是我自己定義的函數,你可以根據自己的需要定義多個別的函數。
4.
如何指定自己的connectionId(往往使用自己系統中原有的成員關系),實現發送給指定用戶消息
SignalR的微軟官網中介紹了大部分的功能,你可以直接參考:
http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/tutorial-getting-started-with-signalr-20-and-mvc-5
但是這些介紹中只介紹了如何廣播,並沒有詳細介紹怎么 給指定的某個用戶發送消息
但是這些介紹中只介紹了如何廣播,並沒有詳細介紹怎么 給指定的某個用戶發送消息
比如在筆者所參與的項目中,系統原本有一套成員關系表,每個登陸到web項目中的用戶,都有自己的用戶名,id和密碼等信息。
默認情況下,當某個客戶端通過SignalR連接到服務器時,SignalR幫我們動態的制定了一個ConnectionId,你可以通過這個ConnectionId去給指定的某個用戶發消息。但是這樣的效果往往不是我們想要的。我們希望使用系統原有的成員Id作為這個ConnectionId。
比如系統中的用戶A,希望發送一則消息給用戶B,那么在使用SignalR的:
Clients.User(userId).send(message);
但是這里的userId希望使用系統自己的用戶成員關系
因此可以自定義IUserIdProvider類,比如我的系統中:
- public class CustomUserIdProvider : IUserIdProvider
- {
- public string GetUserId(IRequest request)
- {
- var userId = WebSecurity.CurrentUserId;
- return userId.ToString();
- }
- }
5.直接從服務器端調用某用戶前端函數
正如上面所講的那樣,普通的通訊模式為:
1)客戶端A在頁面中觸發某一事件(比如點擊某個button)
2)在button.click的事件處理函數中調用了SignalR的某一函數,比如上面講到的noticeUser函數
3)該noticeUser函數為服務器端方法,所以客戶A的前端會通過SignalR調用服務器端的noticeUser方法
4)服務器會根據客戶端A調用newlog時傳遞的若干參數(其中通常包括一個id號,即客戶端A想要發送消息給哪個用戶,比如客戶端B。當然,如果你可以在服務器中通過其他業務查詢的到,也可不必傳遞這個id參數),通過SignalR調用指定Id號的客戶端的某個函數,比如上面提到的newlog方法
有時候,由於系統的業務需求,可能需要直接在服務器端調用noticeUser函數,或者直接調用客戶端B的newlog函數。比如客戶端A提交某個form表單后,服務器端處理完成form表單的數據,存入數據庫,然后想通知客戶端B,有新消息。此時,不希望直接在客戶端A的前段調用noticeUser,然后在經過服務器調用newlog。而希望直接在服務器調用newlog。
其實很簡單,你只需要在你需要調用的地方添加如下代碼即可:
- //call client method on server but not in hub class
- IHubContext _hubContext = GlobalHost.ConnectionManager.GetHubContext<MessageHub>();
- _hubContext.Clients.User(docUserId.ToString()).newLog();
注:
筆者也是由於項目需要,剛接觸SignalR不久,對內部機理並不是很熟悉。在使用的過程中遇到了不少麻煩,因此寫出這篇文章希望對初用者有幫助。如有問題請留言交流。
本文為原創,如需轉載請注明。謝謝!