越是到年末越是感受渾身無力,看着啥也不想動,只期盼着年終獎的到來以此來給本身打一針強心劑。估摸着大多數人都跟我同樣犯着這樣渾身無力的病,感受今年算是沒掙到啥錢,可是話也不能這麼說,搞得好像去年掙到錢了似的。不過該作的事情仍是獲得,由於畢竟本身拿着工資,拿一天錢就得幹一天活,不管本身以爲工資給的多仍是少,以爲少能夠辭職不幹,拿着工資就證實本身願意接受,既然願意接受,那就沒有理由不努力去幹活,我以爲這是做爲一個員工最起碼的信念。web
隔着有一段時間沒寫博客了,反正最近各類亂七八糟的事都在發生,不過無論怎麼樣,事情總會慢慢過去。(最近沒事,正在研究神學,待有所感悟再與你們分享。)json
這一篇博客主要介紹一下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和分組鏈接。mvc
(以上描述摘自MSDN)框架
上面介紹了ASP.NET SignalR的一些基本概念和操做,接下來看一下ASP.NET SignalR的抽象層,由底層向上以此爲:Internet協議,傳輸(WebSockets,Server-SentEvents,foreverFrame,長輪詢),持久鏈接層,Hub層。異步
由以上的結構圖可知ASP.NET SignalR的抽象層結構,在服務器端,當鏈接打開或關閉、接收數據、給客戶端發送信息時,將接受到通知;在客戶端,打開或關閉鏈接,發送或接收任何數據。在ASP.NET SignalR的持久鏈接層中,有一個核心對象:PersisterConnection類,接下來咱們具體瞭解一下這個類的一些方法。ide
ASP.NET SignalR中的每個持久層均可以經過某一個URL從外部進行訪問。爲保持客戶端和服務器之間持久鏈接的開放性,並使用傳輸在這樣的鏈接上發送數據,這個用來訪問SignalR持久鏈接的底層API提供了隱藏固有複雜性的抽象層。
在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命名空間中的類。
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的入口。
看到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()用與獲取用戶身份。
在咱們的實際項目中,通常是針對某一個用戶進行消息的處理,若是想要將消息按照組別進行操做應該怎麼處理,在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,微軟的官網也提供的更爲詳盡的操做說明,因此在這裏就不作這一方面的重複介紹。