第四節:SignalR靈魂所在Hub模型及再探聊天室樣例


一. 整體介紹

  本節:開始介紹SignalR另外一種通訊模型Hub(中心模型,或者叫集線器模型),它是一種RPC模式,允許客戶端和服務器端各自自定義方法並且相互調用,對開發者來說相當友好。
  該節包括的內容有:
    ①:從零搭建
    ②:Hub模型和URL匹配,默認模式和指定路徑
    ③:服務器端代碼介紹
    ④:客戶端的兩種模式,代理和非代理,各自的寫法
    ⑤:服務器端和客戶端包含的方法和各自如何自定義方法並且相互調用
    ⑥:第三方調用的問題,比如:如何在控制器中調用客戶端的方法。
    ⑦:再探聊天室樣例
 

 

二. 從零搭建

 1. 新建MVC5項目,通過Nuget安裝程序集:Microsoft.AspNet.SignalR,安裝成功后如下圖。

2. 新建一個中心模型Hub類(MySpecHub1),該類繼承了Hub類,並且幾個必要方法可以override

 

3. 新建一個OWIN Startup Class(Startup),並在Configuration方法中指定使用的通訊模型的URl,  這里采用默認的方式:如: app.MapSignalR(); 【后續展開介紹如何指定URL及其中的問題】

  PS: 程序啟動時候首先會找到該類,然后運行里面的Configuration方法,從而url和通訊模型的匹配將生效。

 

4. 引入必要的JS文件,進行前端代碼的編寫,如下圖。【后續詳細介紹】

 

三. 模型和URL匹配

   我們都知道,在OWIN Startup Class(即Startup類)中Configuration方法中進行模型URL的指定,並且在很多例子中,看到都是這么寫:app.MapSignalR(); 貌似並沒有配置URL,但事實上並不是這樣的,我們通過反編譯代碼可以看到,它會默認指定一個路徑  "/signalr"  ,如下圖:

  特別注意:這里的"/signalr",與js端的自動生成代理類的代碼:<script src="~/signalr/hubs"></script>沒有任何毛線關系,這兩個根本不是一個東西!!!!!,只是路徑相似而已罷了。

   那么如何指定路徑:

  通過 代碼:app.MapSignalR("/myhub1", new HubConfiguration()); 可以將路徑指定為:"/myhub1",至於前端頁面怎么與之匹配,在下面介紹。

  PS:這里還可以配置其它參數,如下圖:

 

 

四. 服務器端代碼介紹

   前端頁面的JS代碼有兩種模式,代理模式和非代理模式(下面介紹),但無論JS使用哪種模式,服務器端的代碼都是唯一不變。

1. MySpecHub1類繼承成Hub類,所以可以Override三個方法:

  (1). OnConnected:連接成功時調用

  (2). OnDisconnected:連接斷開時調用

  (3). OnReconnected:重連時調用

2. 自定義方法

  服務器端可以自定義方法供客戶端調用,比如:  public void AddUser(string userName, string userId){....}

    特別特別注意:前端【代理模式】的情況下調用的服務器端方法或者與代理文件建立連接時,有一個非常坑爹的規則,首字母必須小寫,比如服務器端定義方法為:“AddUserMsg”,前端【代理模式】情況下調用必須寫成:“addUserMsg”;再比如這里的Hub類為 "MySpecHub1",前端調用的時候必須寫成"mySpecHub1";對此我表示很無語,當年這一點坑了我很久!!!

  PS:上述指定是【代理模式】,【非代理模式】不存在這個問題。

雖然我們已經知道這個規則了,但經常寫着寫着就忘了,那么如何解決上面這個問題呢?:

  這里有兩個特性分別是:[HubName()] 和  [HubMethodName()],可以自行指定Hub類和自定義方法的名稱,指定為什么,前端調用就用什么,這樣【代理模式】下,坑爹的首字母小寫規則,就不存在了。為了后續不麻煩,所以我通常在每個方法上面都加: [HubMethodName(nameof(方法名))],這樣就不會存在問題了,如下圖:

  

 3. 上下文對象(this.Context)

  (1). 當前用戶的標記: this.Context.ConnectionId (Guid生成,不會重復)

  (2). 其它信息:RequestCookies、Headers、QueryString、User、Request

4. 如何調用客戶端方法 

  使用Clients對象進行調用,Clients對象下的屬性和方法有:

    ① 向所有人發送(包括自己):All { get; }

    ② 向所有人發送(排除一些人):AllExcept(params string[] excludeConnectionIds);

    ③ 向指定人發送,一對一:Client(string connectionId);

    ④ 向一些人發送,一對多:Clients(IList<string> connectionIds);

    ⑤ 向某個組發送(排除一些人):Group(string groupName, params string[] excludeConnectionIds);

    ⑥ 向多個組發送(排除一些人):Groups(IList<string> groupNames, params string[] excludeConnectionIds);

    ⑦ 由Id標識的特定用戶:Users(IList<string> userIds);

    ⑧ 由Id標識的特定多用戶:User(string userId);

  調用形式比如:Clients.All.客戶端方法名稱

5. 組的概念(Groups對象)

  ① 將連接添加到指定組:Task Add(string connectionId, string groupName);

  ② 從指定組中刪除連接:Task Remove(string connectionId, string groupName);

  調用如:this.Groups.Add("", "");

截圖幾段代碼:

 

五. 客戶端(js)代碼介紹-代理模式

 1. 必備JS文件的引入

  前端Html頁面使用SignalR,必須引入兩個文件:JQuery和SignalR,必須JQuery在上,且最低版本為1.6.4,不能再低了。如下圖:

2. 代理JS代碼的生成

   代理JS代碼用於幫助客戶端調用服務器端自定義方法,注意這里的引入路徑只能是: <script src="/signalr/js"></script> 或者  <script src="/signalr/hubs"></script>,至於為什么路徑非要這么寫?這個地方不糾結了,我們姑且就這么使用(有興趣探討一下內部原理吧)。

  引入該代碼后,進入頁面F12,會發現多了JS代碼,沒錯,這就是自動生成的代理代碼,在前端代碼的編寫中,需要依賴該文件。

  可能會用人問,我把自動生成的這個JS代碼拷貝出來,單獨放到一個JS文件里,然后在頁面引入,並去掉生成代理代碼的這句話 <script src="/signalr/js"></script>,行不行呢?

  答案是:肯定行。

    但這種拷貝出來的方式有點Low,服務器端代碼只要一改,我就需要重新拷貝一遍,那么有沒有別的方便的方法呢?

    顯然有,大約有兩種方法。

  ①:借助Microsoft.AspNet.SignalR.Utils程序集和指令。

  ②:借助Microsoft.AspNet.SignalR.Utils程序集和VS開發工具

  在這一節里,暫時不介紹這兩種方式,后面章節詳細介紹。

3. 如何與服務器Hub模型路徑相配?

  在上面的代碼中介紹過,服務器Hub模型默認的URL為"/signalr",那么客戶端的代碼怎么寫呢?

1   //1. 與服務器路徑進行匹配
2   var conn = $.connection.hub;
3   //2. 與生成的代理類建立連接
4   var proxy = $.connection.mySpecHub1;

  乍一看,絲毫沒有看到與"/signalr"相關的代碼,不要急,這時去看一下自動生成代理類中的代碼,如下圖:

  

  我們再看一下SignalR的JS代碼中關於hubConnection方法的聲明,如下圖:

 

 

   配合第二個截圖簡單分析一下這塊源代碼,首先if判斷" 一真或為真",只要!url 和 useDefaultPath有一個是真的就進入方法體內部,然后在拼接 url+“/signalr”,如果不進入if方法體,那么你輸入的url是什么,這里用的就是什么。

  前面的代碼為: signalR.hub = $.hubConnection("/signalr", { useDefaultPath: false }); ||兩邊都為false,進入不了if方法體內部,所以URL就是默認輸入的“/signalr”。

4. 坑爹的調用規范

  在代理模式中,客戶端調用服務器端方法或者與代理建立連接的時候,比如:

  ①:服務器端的Hub名稱為MySpecHub1,客戶端調用的時候必須為首字母小寫:$.connection.mySpecHub1;

  ②:服務器端自定義的方法為SendSingleMsg,客戶端調用的時候必須為首字母小寫:proxy.server.sendSingleMsg;

  注:非代理模式中則不存在這個問題!!!!

  解決: 引入兩個特性[HubName("")] 和 [HubMethodName("")]  ,放在服務器端代碼上面,就解決了。 (詳見服務器端代碼)

5. 客戶端方法的聲明和調用服務器端方法

  ①. 聲明客戶端方法: proxy.client.xxx = function (x1, x2) {}       xxx代表客戶端方法名稱

  ②. 調用服務器端方法: proxy.server.xxx(x1,x2);                          xxx代表服務器端方法名稱

 注:這里的proxy,是 $.connection.mySpecHub1; 與自動生成的代理類建立連接。

6. 服務器端指定模型URL后,前端如何匹配?

  如服務器端代碼為:app.MapSignalR("/myhub1", new HubConfiguration());

  ①. 當使用自動生成代理類js文件時候,與<script src="/signalr/hubs"></script>沖突,暫時未解決 (歡迎下方留言討論)

  ②. 手動引入代理類時候可以使用,只需添加 conn.url = "/myhub1"; 即可以將路徑改為 "/myhub1"。 

代碼如下:

通過Fiddler檢測一下。

 

7. 其它方法

  同PersistentConnection模式中相同,比如建立連接和檢測斷線。

 

 

六. 客戶端(js)代碼介紹-非代理模式 

   有了前面代理模式的鋪墊,非代理模式就很容易了,下面介紹一下在使用上的一些區別:

1. 基本使用

  不需要引入 <script src="/signalr/js"></script> 或者 <script src="/signalr/hubs"></script>,也不需要引入手動添加的代理類 <script src="~/Scripts/AutoProxy.js"></script>,但在代碼上要這么寫,比如創建代理類: $.hubConnection().CreateHubProxy("MySpecHub1");

詳細代碼如下:

1  //1. 與服務器路徑進行匹配
2 var conn = $.hubConnection();
3 //2. 手動創建代理類
4  var proxy = conn.createHubProxy("MySpecHub1");

2. 在非代理模式中,服務器端的Hub名稱和服務器端自定義的方法不必首字母小寫(PS:小寫也能用)

  ①:服務器端的Hub名稱為MySpecHub1,客戶端調用的時候 conn.createHubProxy("MySpecHub1");

  ②:服務器端自定義的方法為SendSingleMsg,客戶端調用的時候必須為首字母小寫: proxy.invoke("SendSingleMsg", $("#j_receiveId").val(), $("#j_content").val());

  注:服務器端的兩個特性[HubName("")] 和 [HubMethodName("")]仍然好用!!!

3. 聲明客戶端方法和調用服務器端方法

  ①. 聲明客戶端方法: proxy.on("方法名",function(x1,x2,x3){});

  ②. 調用服務器端方法: proxy.invoke("方法名", "參數1","參數2","參數3");

4 默認路徑匹配

  在不使用代理的情況下,$.hubConnection()與服務器路徑進行匹配的時候,通過Fiddler可以發現,默認是"/signalr"路徑

5. 服務器端指定路徑模型路徑:

  如服務器端代碼為:app.MapSignalR("/myhub1", new HubConfiguration());

  客戶端對應的代碼為:$.hubConnection("/myhub1", { useDefaultPath: false });

  注:通過Fidder或者調試源代碼,可以看到路徑已經改為"/myhub1";

  特別補充:如果客戶端代碼var conn = $.hubConnection("/myhub1")這么寫,useDefaultPath這個屬性默認為true,則最后的路徑為:"/myhub1/signalr",原因借助前面的分析很容易理解了。

 

 

七. 第三方調用 

  上面介紹的所有代碼都是直接基於 Hub模型這個類來通信的,但在很多情況下,  發送信息之前需要進行很多其他業務的處理,這個時候所有的代碼都寫在Hub類中就不太合適了,  這時候需要借助控制器里的Action來通信,那么問題來了,如何通過調用控制器里的方法來實現發送信息的功能呢?

  其實非常簡單,我們只需要通過 GlobalHost.ConnectionManager.GetHubContext<MySpecHub1>(); 獲取到這個Hub即可。

  代碼如下:

 1        /// <summary>
 2         /// 向所有人發送消息
 3         /// </summary>
 4         /// <param name="myConnectionId">當前用戶的登錄標記</param>
 5         /// <param name="msg">發送的信息</param>
 6         public string MySendAll(string myConnectionId ,string msg)
 7         {
 8             //Hub模式
 9             var hub = GlobalHost.ConnectionManager.GetHubContext<MySpecHub1>();
10             hub.Clients.AllExcept(myConnectionId).receiveMsg($"用戶【{myConnectionId}】發來消息:{msg}");
11             return "ok";
12         } 

 

八. 聊天室樣例

   在本系列的第一節,基於WebSocket寫了一個聊天室樣例,還吐槽了一番,寫法很麻煩,這里基於Signalr的Hub模型,再寫一次聊天室,並補充幾個新功能。

  效果圖如下:

  包括的功能有:

  ①:連接成功后通知所有人包括自己登錄成功。

  ②:離線后,通知除了自己以外的所有人已經離開。

  ③:通過輸入接收人的ConnectionId,實現一對一發送信息。

  ④: 通過點擊群發按鈕,向除了自己以外的所有人發送信息。

  ⑤:可以進入room1或room2房間,然后實現向指定房間內的所有人發送信息。

  ⑥:調用控制器中中的方法進行通訊

下面分享代碼,包括(Hub模型代碼,控制器代碼,前端代碼(代理和非代理兩套))

Hub模型代碼

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Threading.Tasks;
  5 using System.Web;
  6 using Microsoft.AspNet.SignalR;
  7 using Microsoft.AspNet.SignalR.Hubs;
  8 
  9 namespace HubDemo
 10 {
 11   
 12 
 13     //[HubName("MySpecHub1")]
 14     public class MySpecHub1 : Hub
 15     {
 16 
 17         /**************************************下面是Override的方法*************************************************/
 18 
 19 
 20         #region 01-連接成功的時候調用
 21         /// <summary>
 22         /// 連接成功的時候調用
 23         /// </summary>
 24         /// <returns></returns>
 25         public override Task OnConnected()
 26         {
 27 
 28             //調用客戶端的方法通知所有人包括自己
 29             Clients.All.LoginSuccessNotice($"用戶【{this.Context.ConnectionId}】登錄成功", this.Context.ConnectionId);
 30             //回傳給客戶端自己的CId
 31             Clients.Client(this.Context.ConnectionId).ReceiveOwnCid(this.Context.ConnectionId);
 32             return base.OnConnected();
 33         }
 34         #endregion
 35 
 36         #region 02-連接斷開的時候調用
 37         /// <summary>
 38         /// 連接斷開的時候調用
 39         /// </summary>
 40         /// <param name="stopCalled"></param>
 41         /// <returns></returns>
 42         public override Task OnDisconnected(bool stopCalled)
 43         {
 44             //除去自己以外的消息
 45             Clients.AllExcept(this.Context.ConnectionId).receiveMsg($"用戶【{this.Context.ConnectionId}】已經離開");
 46             return base.OnDisconnected(stopCalled);
 47         }
 48         #endregion
 49 
 50         #region 03-重新連接的時候調用
 51         /// <summary>
 52         /// 重新連接的時候調用
 53         /// </summary>
 54         /// <returns></returns>
 55         public override Task OnReconnected()
 56         {
 57             return base.OnReconnected();
 58         }
 59         #endregion
 60 
 61         /**************************************下面是自定義的服務器端方法*************************************************/
 62 
 63         #region 01-點對點發送消息
 64         /// <summary>
 65         /// 點對點發送消息
 66         /// </summary>
 67         /// <param name="receiveId"></param>
 68         /// <param name="msg"></param>
 69         public void SendSingleMsg(string receiveId, string msg)
 70         {
 71             Clients.Client(receiveId).receiveMsg($"用戶【{this.Context.ConnectionId}】發來消息:{msg}");
 72         }
 73         #endregion
 74 
 75         #region 02-群發消息
 76         /// <summary>
 77         /// 群發消息
 78         /// </summary>
 79         /// <param name="msg"></param>
 80         [HubMethodName(nameof(SendAllMsg))]
 81         public void SendAllMsg(string msg)
 82         {
 83             //除去自己以外的消息(不需要自己存儲ConnectionId)
 84             Clients.AllExcept(this.Context.ConnectionId).receiveMsg($"用戶【{this.Context.ConnectionId}】發來消息:{msg}");
 85         }
 86         #endregion
 87 
 88         #region 03-進入指定組
 89         /// <summary>
 90         /// 進入指定組
 91         /// </summary>
 92         /// <param name="roomName">組的名稱</param>
 93         [HubMethodName(nameof(EnterRoom))]
 94         public void EnterRoom(string roomName)
 95         {
 96             //進入組
 97             Groups.Add(this.Context.ConnectionId, roomName);
 98             //告訴自己進入成功
 99             Clients.Client(this.Context.ConnectionId).receiveMsg($"用戶【{this.Context.ConnectionId}】成功進入組:【{roomName}】");
100         }
101         #endregion
102 
103         #region 04-向指定組發送消息
104         /// <summary>
105         /// 向指定組發送消息
106         /// </summary>
107         /// <param name="roomName">組名</param>
108         /// <param name="msg">內容</param>
109         [HubMethodName(nameof(SendRoomNameMsg))]
110         public void SendRoomNameMsg(string roomName, string msg)
111         {
112             //向指定組發送消息,如果這個組包含自己,將自己除外
113             Clients.Group(roomName, this.Context.ConnectionId).receiveMsg($"用戶【{this.Context.ConnectionId}】發來消息:{msg}");
114         }
115         #endregion
116 
117     }
118 }
View Code

控制器代碼

 1 using Microsoft.AspNet.SignalR;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Web;
 6 using System.Web.Mvc;
 7 
 8 namespace HubDemo.Controllers
 9 {
10     /// <summary>
11     /// Hub模型(中心模型)
12     /// 
13     /// </summary>
14     public class HubController : Controller
15     {
16         #region 01-代理模式頁面
17         /// <summary>
18         /// 代理模式頁面
19         /// </summary>
20         /// <returns></returns>
21         public ActionResult Index()
22         {
23             return View();
24         }
25         #endregion
26 
27         #region 02-非代理模式頁面
28         /// <summary>
29         /// 非代理模式頁面
30         /// </summary>
31         /// <returns></returns>
32         public ActionResult NoProxyIndex()
33         {
34             return View();
35         }
36         #endregion
37 
38 
39 
40         /****************************************************下面是第三方調用方法****************************************************************/
41 
42 
43         #region 01-向所有人發送消息
44         /// <summary>
45         /// 向所有人發送消息
46         /// </summary>
47         /// <param name="myConnectionId">當前用戶的登錄標記</param>
48         /// <param name="msg">發送的信息</param>
49         public string MySendAll(string myConnectionId ,string msg)
50         {
51             //Hub模式
52             var hub = GlobalHost.ConnectionManager.GetHubContext<MySpecHub1>();
53             hub.Clients.AllExcept(myConnectionId).receiveMsg($"用戶【{myConnectionId}】發來消息:{msg}");
54             return "ok";
55         } 
56         #endregion
57 
58 
59 
60     }
61 }
View Code

代理

  1 @{
  2     Layout = null;
  3 }
  4 
  5 <!DOCTYPE html>
  6 
  7 <html>
  8 <head>
  9    
 10     <meta name="viewport" content="width=device-width" />
 11     <title>Index</title>
 12     <script src="~/Scripts/jquery-3.3.1.min.js"></script>
 13     <script src="~/Scripts/jquery.signalR-2.3.0.js"></script>
 14     <!--自動生成代理類 -->
 15     <script src="/signalr/hubs"></script>
 16     <!--手動引入代理類 -->
 17     @*<script src="~/Scripts/AutoProxy.js"></script>*@
 18     <script type="text/javascript">
 19         $(function () {
 20 
 21             //一. 初始化信息
 22             //默認路徑
 23             //1. 與服務器路徑進行匹配
 24             var conn = $.connection.hub;
 25             //2. 與生成的代理類建立連接
 26             var proxy = $.connection.mySpecHub1;
 27             //3.當服務器端指定路徑的時候,需要有下面的代碼進行匹配(僅使用手動代理)
 28             //conn.url = "/myhub1";
 29 
 30             //二. 定義客戶端的方法
 31             //1 接受用戶登錄成功后的提示
 32             proxy.client.LoginSuccessNotice = function (data, connectionId) {
 33                 $("#j_Msg").append("<li>" + data + "</li>");
 34                
 35             };
 36             //2 接收點對點用戶發送來的消息
 37             proxy.client.receiveMsg = function (data) {
 38                 $("#j_Msg").append("<li>" + data + "</li>");
 39             }
 40             //3. 接收自己的connectionId
 41             proxy.client.ReceiveOwnCid = function (connectionId) {
 42                 //把用戶ConnectionID存起來
 43                 $("#j_connectionId").val(connectionId);
 44             }
 45 
 46 
 47             //三. 主動事件
 48             //1.建立連接
 49             $("#j_connect").click(function () {
 50                 conn.start().done(function () {
 51                     $("#j_notice").html("連接成功");
 52                 }).fail(function () {
 53                     console.log('Could not connect');
 54                 });
 55             });
 56             //2.斷開連接
 57             $("#j_close").click(function () {
 58                 conn.stop();
 59             });
 60             //3.點對點發送消息
 61             $("#j_send").click(function () {
 62                 var state = conn.state;
 63                 if (state == 1) {
 64                     //調用服務器端方法
 65                     proxy.server.sendSingleMsg($("#j_receiveId").val(), $("#j_content").val());
 66                 } else if (state == 0) {
 67                     $("#j_notice").html("正在連接中,請稍等");
 68                 } else if (state == 2) {
 69                     $("#j_notice").html("正在重連,請稍等");
 70                 } else if (state == 4) {
 71                     $("#j_notice").html("掉線了,請重新連接");
 72                 }
 73 
 74             });
 75             //4.群發消息
 76             $("#j_sendAll").click(function () {
 77                 var state = conn.state;
 78                 if (state == 1) {
 79                     //調用服務器端方法
 80                     proxy.server.SendAllMsg($("#j_content").val());
 81                 } else if (state == 0) {
 82                     $("#j_notice").html("正在連接中,請稍等");
 83                 } else if (state == 2) {
 84                     $("#j_notice").html("正在重連,請稍等");
 85                 } else if (state == 4) {
 86                     $("#j_notice").html("掉線了,請重新連接");
 87                 }
 88 
 89             });
 90             //5.進入room1
 91             $("#j_room1").click(function () {
 92                 //調用服務器端方法
 93                 proxy.server.EnterRoom("room1");
 94             });
 95             //6.進入room2
 96             $("#j_room2").click(function () {
 97                 //調用服務器端方法
 98                 proxy.server.EnterRoom("room2");
 99             });
100             //7. 給room1中的用戶發送消息
101             $("#j_sendRoom1").click(function () {
102                 proxy.server.SendRoomNameMsg("room1", $('#j_content2').val());
103             });
104             //8. 給room2中的用戶發送消息
105             $("#j_sendRoom2").click(function () {
106                 proxy.server.SendRoomNameMsg("room2", $('#j_content2').val());
107             });
108 
109             //9. 調用控制器中的方法發送信息
110             $("#j_btn").click(function () {
111                 var myConnectionId = $("#j_connectionId").val();
112                 var msg = $('#j_content3').val();
113                 console.log(myConnectionId);
114                 $.post("/Hub/MySendAll", { myConnectionId: myConnectionId, msg: msg }, function (data) {
115                     if (data == "ok") {
116                         alert("發送成功");
117                     }
118                 });
119 
120             });
121 
122 
123 
124 
125 
126             //四. 監控事件
127             //1. 連接斷開的方法
128             conn.disconnected(function () {
129                 $("#j_notice").html("連接中斷");
130             });
131 
132 
133 
134 
135         });
136     </script>
137 </head>
138 <body>
139     <div>
140         <div><span>提示:</span><span id="j_notice"></span></div>
141         <div style="margin-top:20px">
142             <button id="j_connect">建立連接</button>
143             <button id="j_close">關閉連接</button>
144         </div>
145         <div style="margin-top:20px">
146             <input type="text" value="" placeholder="請輸入接收人的標記" id="j_receiveId" />
147             <input type="text" value="" placeholder="請輸入發送內容" id="j_content" />
148             <button id="j_send">單發</button>
149             <button id="j_sendAll">群發</button>
150         </div>
151         <div style="margin-top:20px">
152             <button id="j_room1">進入room1</button>
153             <button id="j_room2">進入room2</button>
154         </div>
155         <div style="margin-top:20px">
156             <input type="text" value="" placeholder="請輸入發送內容" id="j_content2" />
157             <button id="j_sendRoom1">給room1發送消息</button>
158             <button id="j_sendRoom2">給room2發送消息</button>
159         </div>
160         <div style="margin-top:20px">
161             <input type="text" value="" placeholder="請輸入發送內容" id="j_content3" />
162             <button id="j_btn">調用控制器中的方法</button>
163         </div>
164         <div>
165             <ul id="j_Msg"></ul>
166         </div>
167     </div>
168     <input type="hidden" value="" id="j_connectionId" />
169 </body>
170 </html>
View Code

非代理

  1 @{
  2     Layout = null;
  3 }
  4 
  5 <!DOCTYPE html>
  6 
  7 <html>
  8 <head>
  9    
 10     <meta name="viewport" content="width=device-width" />
 11     <title>Index</title>
 12     <script src="~/Scripts/jquery-3.3.1.min.js"></script>
 13     <script src="~/Scripts/jquery.signalR-2.3.0.js"></script>
 14     <script type="text/javascript">
 15         $(function () {
 16 
 17 
 18             //一. 初始化信息
 19             //默認路徑
 20             //1. 與服務器路徑進行匹配
 21             var conn = $.hubConnection();
 22             //2. 手動創建代理類
 23             var proxy = conn.createHubProxy("MySpecHub1");
 24 
 25 
 26             //指定路徑
 27             ////1. 與服務器路徑進行匹配
 28             //var conn = $.hubConnection("/myhub1", { useDefaultPath: false }); //對應的路徑為"/myhub1"
 29             //////var conn = $.hubConnection("/myhub1");   //對應的路徑為"/myhub1/signalr"
 30             ////2. 手動創建代理類
 31             //var proxy = conn.createHubProxy("MySpecHub1");
 32 
 33 
 34             //二. 定義客戶端的方法
 35             //1 接受用戶登錄成功后的提示
 36             proxy.on("LoginSuccessNotice", function (data, connectionId) {
 37                 $("#j_Msg").append("<li>" + data + "</li>");
 38             });
 39             //2 接收點對點用戶發送來的消息
 40             proxy.on("receiveMsg", function (data) {
 41                 $("#j_Msg").append("<li>" + data + "</li>");
 42             });
 43             //3. 接收自己的connectionId
 44             proxy.on("ReceiveOwnCid", function (connectionId) {
 45                 //把用戶ConnectionID存起來
 46                 $("#j_connectionId").val(connectionId);
 47             });
 48 
 49 
 50 
 51             //三. 主動事件
 52             //1.建立連接
 53             $("#j_connect").click(function () {
 54                 conn.start().done(function () {
 55                     $("#j_notice").html("連接成功");
 56                 }).fail(function () {
 57                     console.log('Could not connect');
 58                 });
 59             });
 60             //2.斷開連接
 61             $("#j_close").click(function () {
 62                 conn.stop();
 63             });
 64 
 65             //3.點對點發送消息
 66             $("#j_send").click(function () {
 67                 var state = conn.state;
 68                 if (state == 1) {
 69                     //調用服務器端方法
 70                     proxy.invoke("SendSingleMsg", $("#j_receiveId").val(), $("#j_content").val());
 71                 } else if (state == 0) {
 72                     $("#j_notice").html("正在連接中,請稍等");
 73                 } else if (state == 2) {
 74                     $("#j_notice").html("正在重連,請稍等");
 75                 } else if (state == 4) {
 76                     $("#j_notice").html("掉線了,請重新連接");
 77                 }
 78 
 79             });
 80             //4.群發消息
 81             $("#j_sendAll").click(function () {
 82                 var state = conn.state;
 83                 if (state == 1) {
 84                     //調用服務器端方法
 85                     proxy.invoke("SendAllMsg", $("#j_content").val());
 86                 } else if (state == 0) {
 87                     $("#j_notice").html("正在連接中,請稍等");
 88                 } else if (state == 2) {
 89                     $("#j_notice").html("正在重連,請稍等");
 90                 } else if (state == 4) {
 91                     $("#j_notice").html("掉線了,請重新連接");
 92                 }
 93 
 94             });
 95 
 96             //5.進入room1
 97             $("#j_room1").click(function () {
 98                 //調用服務器端方法
 99                 proxy.invoke("EnterRoom", "room1");
100             });
101             //6.進入room2
102             $("#j_room2").click(function () {
103                 //調用服務器端方法
104                 proxy.invoke("EnterRoom", "room2");
105             });
106             //7. 給room1中的用戶發送消息
107             $("#j_sendRoom1").click(function () {
108                 proxy.invoke("SendRoomNameMsg", "room1", $('#j_content2').val());
109             });
110             //8. 給room2中的用戶發送消息
111             $("#j_sendRoom2").click(function () {
112                 proxy.invoke("SendRoomNameMsg", "room2", $('#j_content2').val());
113             });
114 
115             //9. 調用控制器中的方法發送信息
116             $("#j_btn").click(function () {
117                 var myConnectionId = $("#j_connectionId").val();
118                 var msg = $('#j_content3').val();
119                 console.log(myConnectionId);
120                 $.post("/Hub/MySendAll", { myConnectionId: myConnectionId, msg: msg }, function (data) {
121                     if (data == "ok") {
122                         alert("發送成功");
123                     }
124                 });
125 
126             });
127 
128 
129 
130 
131             //四. 監控事件
132             //1. 連接斷開的方法
133             conn.disconnected(function () {
134                 $("#j_notice").html("連接中斷");
135             });
136 
137 
138             //五. 其它事件
139      
140 
141         });
142     </script>
143 </head>
144 <body>
145     <div>
146         <div><span>提示:</span><span id="j_notice"></span></div>
147         <div style="margin-top:20px">
148             <button id="j_connect">建立連接</button>
149             <button id="j_close">關閉連接</button>
150         </div>
151         <div style="margin-top:20px">
152             <input type="text" value="" placeholder="請輸入接收人的標記" id="j_receiveId" />
153             <input type="text" value="" placeholder="請輸入發送內容" id="j_content" />
154             <button id="j_send">單發</button>
155             <button id="j_sendAll">群發</button>
156         </div>
157         <div style="margin-top:20px">
158             <button id="j_room1">進入room1</button>
159             <button id="j_room2">進入room2</button>
160         </div>
161         <div style="margin-top:20px">
162             <input type="text" value="" placeholder="請輸入發送內容" id="j_content2" />
163             <button id="j_sendRoom1">給room1發送消息</button>
164             <button id="j_sendRoom2">給room2發送消息</button>
165         </div>
166         <div style="margin-top:20px">
167             <input type="text" value="" placeholder="請輸入發送內容" id="j_content3" />
168             <button id="j_btn">調用控制器中的方法</button>
169         </div>
170         <div>
171             <ul id="j_Msg"></ul>
172         </div>
173     </div>
174     <input type="hidden" value="" id="j_connectionId" />
175 </body>
176 </html>
View Code

 

  PS:SignalR 與WebSocket原生代碼編寫的相比較,最大的改進之處有:

  ①:單發和群發可以對應不同的服務器端方法,不再需要自行約定規則來識別了,同樣客戶端接收也很靈活了,可以指定方法接收。

  ②:不需要自己創建登錄標記,默認會生成一個ConnectionId。

  ③:ConnectionId會自動記錄,不需要單獨聲明一個集合來存儲。

  ④:寫起來太爽了(^_^)。

  

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 本人才疏學淺,用郭德綱的話說“我是一個小學生”,如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則將保留追究法律責任,如需代碼請在評論處留下你的郵箱

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM