1. 程式人生 > >ASP.NET SignalR2持久連線層解析

ASP.NET SignalR2持久連線層解析

一.ASP.NET SignalR概述:

    談到ASP.NET SignalR大多數人應該會比較的熟悉,因在我們的mvc專案中,使用到ASP.NET SignalR的地方還是比較多的。

    ASP.NET SignalR是ASP.NET開發人員的庫,它簡化了嚮應用程式新增實時Web功能的過程。實時網路功能能夠讓伺服器程式碼在連線的客戶端可用時立即將內容推送到連線的客戶端,而不是讓伺服器等待客戶端請求新資料。SignalR可用於向ASP.NET應用程式新增任何型別的“實時”Web功能。任何時候使用者重新整理網頁,看看新的資料或頁面實現長輪詢來獲取新的資料,它是使用SignalR的候選人。示例包括儀表板和監視應用程式,協作應用程式(如同時編輯文件),作業進度更新和實時表單。

   SignalR還支援需要來自伺服器的高頻更新的全新型別的web應用。SignalR自動處理連線管理,並允許您同時向所有連線的客戶端廣播訊息。SignalR支援“伺服器推送”功能,其中伺服器程式碼可以使用遠端過程呼叫(RPC)在瀏覽器中呼叫客戶端程式碼。SignalR的應用可以擴充套件到使用服務匯流排。

   SignalR提供了一個用於建立從伺服器端.NET程式碼呼叫客戶端瀏覽器(和其他客戶端平臺)中的JavaScript函式的伺服器到客戶端遠端過程呼叫(RPC)的簡單API。SignalR還包括用於連線管理(例如,連線和斷開事件)的API和分組連線。SignalR提供了一個用於建立從伺服器端.NET程式碼呼叫客戶端瀏覽器(和其他客戶端平臺)中的JavaScript函式的伺服器到客戶端遠端過程呼叫(RPC)的簡單API。

SignalR還包括用於連線管理(例如,連線和斷開事件)的API和分組連線。

    (以上描述摘自MSDN)

   上面介紹了ASP.NET SignalR的一些基本概念和操作,接下來看一下ASP.NET SignalR的抽象層,由底層向上以此為:Internet協議,傳輸(WebSockets,Server-SentEvents,foreverFrame,長輪詢),持久連線層,Hub層。

    

     由以上的結構圖可知ASP.NET SignalR的抽象層結構,在伺服器端,當連線開啟或關閉、接收資料、給客戶端傳送資訊時,將接受到通知;在客戶端,開啟或關閉連線,傳送或接收任何資料。在ASP.NET SignalR的持久連線層中,有一個核心物件:PersisterConnection類,接下來我們具體瞭解一下這個類的一些方法。

二.ASP.NET SignalR持久連線層服務端核心物件方法解析:

    ASP.NET SignalR中的每一個持久層都可以通過某一個URL從外部進行訪問。為保持客戶端和伺服器之間持久連線的開放性,並使用傳輸在這樣的連線上傳送資料,這個用來訪問SignalR持久連線的底層API提供了隱藏固有複雜性的抽象層。

   1.PersisterConnection類的事件方法:

      在PersisterConnection中包含幾個事件方法,這幾個方法都是虛方法,如下:

    OnConnected():在建立新連線時呼叫。

    OnReconnected():在超時後連線重新連線時呼叫。

    OnReceived():從連線接收資料時呼叫。

    OnDisconnected():當連線正常斷開或由於超時時呼叫。

      以上的4個方法中,返回的型別都是TaskAsyncHelper.Empty,如下原始碼:

複製程式碼
    /// <summary>
    /// 從連線接收資料時呼叫。
    /// </summary>
    /// <param name="request">當前連線的IRequest。</param>
<param name="connectionId">傳送資料的連線的ID。
</param>
<param name="data">有效負載傳送到連線。
</param> /// <returns> ///System.Threading.Tasks.Task在接收操作完成時完成。/// </returns> protected virtual Task OnReceived(IRequest request, string connectionId, string data) { return TaskAsyncHelper.Empty; }
複製程式碼

    以上的方法都是非同步方法,用以實現程式碼的非同步執行,或者返回一個能夠通過某個Task物件表示和非同步執行的後臺任務,在方法的傳入引數中,程式碼中已經做類對應的介紹。如果需要進一步瞭解TaskAsyncHelper.Empty的相關資訊,可以具體檢視System.Threading.Tasks.Task名稱空間中的類。

   2.ProcessRequest():處理所有的請求。

      PersisterConnection類的ProcessRequest()方法是用與OWIN入口點,該方法存在多個過載版本,現在只介紹一個過載的原始碼:

複製程式碼
    public virtual Task ProcessRequest(HostContext context)
    {
      if (context == null)
        throw new ArgumentNullException("context");
      if (!this._initialized)
        throw new InvalidOperationException(string.Format((IFormatProvider) CultureInfo.CurrentCulture, Resources.Error_ConnectionNotInitialized, new object[0]));
      if (PersistentConnection.IsNegotiationRequest(context.Request))
        return this.ProcessNegotiationRequest(context);
      if (PersistentConnection.IsPingRequest(context.Request))
        return PersistentConnection.ProcessPingRequest(context);
      this.Transport = this.GetTransport(context);
      if (this.Transport == null)
        return PersistentConnection.FailResponse(context.Response, string.Format((IFormatProvider) CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorUnknownTransport, new object[0]), 400);
      string connectionToken = context.Request.QueryString["connectionToken"];
      if (string.IsNullOrEmpty(connectionToken))
        return PersistentConnection.FailResponse(context.Response, string.Format((IFormatProvider) CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorMissingConnectionToken, new object[0]), 400);
      string connectionId;
      string message;
      int statusCode;
      if (!this.TryGetConnectionId(context, connectionToken, out connectionId, out message, out statusCode))
        return PersistentConnection.FailResponse(context.Response, message, statusCode);
      this.Transport.ConnectionId = connectionId;
      Task<string> groupsToken = this.Transport.GetGroupsToken();
      Func<string, PersistentConnection, HostContext, Task> func = (Func<string, PersistentConnection, HostContext, Task>) ((g, pc, c) => pc.ProcessRequestPostGroupRead(c, g));
      HostContext hostContext = context;
      Func<string, PersistentConnection, HostContext, Task> successor;
      return TaskAsyncHelper.FastUnwrap(TaskAsyncHelper.Then<string, PersistentConnection, HostContext, Task>(groupsToken, successor, this, hostContext));
    }
複製程式碼

    由以上的原始碼可以看出,該方法為一個非同步虛方法,並且接受一個引數HostContext表示當前請求。當一個PersistentConnection的管道完成時,返回一個System.Threading.Tasks.Task。Transport.GetGroupsToken()用與獲取組令牌。ProcessRequest()的另一個過載版本是OWIN的入口。

   3.TryGetConnectionId():用與獲取ConnectionId。

     看到ConnectionId應該都不會陌生,因為在前面介紹的4中事件方法中有一個引數就是ConnectionId,該引數是一個唯一識別符號,他和初始化通訊過程中通過SiganlR自動產生的連線有關。預設情況下,SignalR框架將使用一個guid進行標識。

    可以使用該連線符給某些特定的客戶端直接傳送訊息,或是對他們實施任何型別的個性化監視。

    接下來,我們具體看一下原始碼:

複製程式碼
internal bool TryGetConnectionId(HostContext context, string connectionToken, out string connectionId, out string message, out int statusCode)
    {
      string str1 = (string) null;
      connectionId = (string) null;
      message = (string) null;
      statusCode = 400;
      try
      {
        str1 = this.ProtectedData.Unprotect(connectionToken, "SignalR.ConnectionToken");
      }
      catch (Exception ex)
      {
        TraceSource trace = this.Trace;
        string format = "Failed to process connectionToken {0}: {1}";
        object[] objArray = new object[2];
        int index1 = 0;
        string str2 = connectionToken;
        objArray[index1] = (object) str2;
        int index2 = 1;
        Exception exception = ex;
        objArray[index2] = (object) exception;
        trace.TraceInformation(format, objArray);
      }
      if (string.IsNullOrEmpty(str1))
      {
        message = string.Format((IFormatProvider) CultureInfo.CurrentCulture, Resources.Error_ConnectionIdIncorrectFormat, new object[0]);
        return false;
      }
      string[] strArray = str1.Split(PersistentConnection.SplitChars, 2);
      connectionId = strArray[0];
      if (string.Equals(strArray.Length > 1 ? strArray[1] : string.Empty, PersistentConnection.GetUserIdentity(context), StringComparison.OrdinalIgnoreCase))
        return true;
      message = string.Format((IFormatProvider) CultureInfo.CurrentCulture, Resources.Error_UnrecognizedUserIdentity, new object[0]);
      statusCode = 403;
      return false;
    }
複製程式碼

   該方法返回bool值,接受5個引數,分別為:HostContext請求內容,connectionToken連線令牌,connectionId連線ID,message訊息,statusCode狀態程式碼。進入方法後,首先判斷引數資訊是否符合要求。ProtectedData.Unprotect()方法用與取消保護,接受傳入的連線令牌。PersistentConnection.GetUserIdentity()用與獲取使用者身份。

   4.VerifyGroups():用與驗證組。

     在我們的實際專案中,一般是針對某一個使用者進行訊息的處理,如果想要將訊息按照組別進行操作應該怎麼處理,在SignalR提供了一個VerifyGroups方法。

複製程式碼
 internal IList<string> VerifyGroups(string connectionId, string groupsToken)
    {
      if (string.IsNullOrEmpty(groupsToken))
        return ListHelper<string>.Empty;
      string str1 = (string) null;
      try
      {
        str1 = this.ProtectedData.Unprotect(groupsToken, "SignalR.Groups.v1.1");
      }
      catch (Exception ex)
      {
        TraceSource trace = this.Trace;
        string format = "Failed to process groupsToken {0}: {1}";
        object[] objArray = new object[2];
        int index1 = 0;
        string str2 = groupsToken;
        objArray[index1] = (object) str2;
        int index2 = 1;
        Exception exception = ex;
        objArray[index2] = (object) exception;
        trace.TraceInformation(format, objArray);
      }
      if (string.IsNullOrEmpty(str1))
        return ListHelper<string>.Empty;
      string[] strArray = str1.Split(PersistentConnection.SplitChars, 2);
      string a = strArray[0];
      string json = strArray.Length > 1 ? strArray[1] : string.Empty;
      string b = connectionId;
      int num = 5;
      if (!string.Equals(a, b, (StringComparison) num))
        return ListHelper<string>.Empty;
      return (IList<string>) JsonSerializerExtensions.Parse<string[]>(this.JsonSerializer, json);
    }
複製程式碼

  該方法返回一個List<string>集合,接受兩個引數connectionId,groupsToken進行分組操作。ProtectedData.Unprotect()方法用與取消保護,接受傳入的連線令牌。在SignalR中一般在處理訊息請求時,需要進行取消保護這一步操作。Split()對取消保護操作後返回的資料進行分割獲取一個數組。

三.總結:

   以上是簡單的介紹SignalR的持久層的一些方法,並沒有提供一些基本樣例,因為個人覺得在網上還是有自己多的demo,微軟的官網也提供的更為詳盡的操作說明,所以在這裡就不做這一方面的重複介紹。