[機翻] WIRER ON THE WIRE - SIGNALR協議的非正式描述

原文
原文很簡單,如下爲機翻web

WIRER ON THE WIRE - SIGNALR協議的非正式描述

我已經看到詢問有關SignalR協議的描述的問題出現了不少。哎呀,當我開始關注SignalR時,我也在尋找相似的東西。如今,差很少一年以後,在我從架構上從新設計了SignalR C#客戶端並從頭開始編寫SignalR C ++客戶端後,我想我能夠很是準確地描述協議。因此,咱們走了。
在我看來,SignalR使用的協議由兩部分組成。第一部分與鏈接管理有關,即鏈接如何啓動,中止,從新鏈接等。這部分包含一些很是複雜的部分(特別是在啓動鏈接時),對於想要編寫本身的客戶端的人來講,這是最有趣的。 ,我相信,是少數)。我認爲絕大多數用戶實際感興趣的第二部分是全部這些「H」,「A」,「我」等等.SignalR正在線上並寫入日誌。我將從第一部分開始,而後將描述第二部分。
免責聲明:在某些狀況下,我將討論客戶之間的差別。我只用工做SignalR .NET客戶端,該SignalR C ++客戶端和SignalR JavaScript客戶端(在這種狀況下「工做」是一種誇大其詞 - 我只修復了一些錯誤並屢次查看代碼)。我知道其餘SignalR客戶端,如Java或Objective-C,但我沒有嘗試過它們,也沒有看過代碼,我不知道它們作了什麼,它們是如何作的以及它們與下面的描述一致。數組

鏈接管理

SignalR使用HTTP(S)協議管理鏈接。操做由客戶端發起,客戶端發送包含請求的操做和公共參數子集的HTTP請求。可使用GET或(當使用協議版本1.5時)POST方法發送請求。並不是全部請求都須要全部參數。如下是SignalR請求中使用的參數及其描述:瀏覽器

  • transport - 正在使用的傳輸的名稱。有效值:webSockets,longPolling,serverSentEvents,foreverFrame
  • clientProtocol - 客戶端使用的協議版本。最新版本是1.5可是它僅由JavaScript客戶端使用,由於強制將協議版本提高爲1.5的更改僅與此客戶端相關。.NET和C ++客戶端目前使用1.4版。請注意,服務器旨在支持下層客戶端(即便用之前版本協議的客戶端),當前(2.2.0)版本支持1.2到1.5之間的協議版本
  • connectionToken - 標識發件人的字符串。它在對negotiate請求的響應中返回。有關鏈接令牌的更多詳細信息,請參閱此文檔
  • connectionData - 一個url編碼的JSon數組,包含客戶端訂閱的集線器列表。例如,若是客戶端訂閱了兩個集線器 - 「my_hub」,「your_hub」,要發送的數組以下所示:[{"Name":"my_hub"},{"Name":"your_hub"}]而且在url-encoding以後它變爲:
    %5B%7B%22Name%22:%22my_hub%22%7D,%7B%22Name%22:%22your_hub%22%7D%5D
  • messageId - 最後收到的消息的ID。用於從新鏈接和 - 在使用longPolling傳輸時 - poll請求中
  • groupsToken - 描述鏈接所屬組的標記。用於從新鏈接
  • queryString - 用戶提供的任意查詢字符串; 附加到全部請求

啓動鏈接

啓動鏈接是與SignalR客戶端執行的鏈接管理相關的最複雜的任務。它須要發送三個請求到服務器- negotiate,connect和start。整個序列以下:服務器

  • 客戶端發送negotiate請求。對協商請求的響應包含許多客戶端配置設置
  • 客戶端經過發送connect請求來啓動傳輸。該connect請求必須由服務器在響應返回的超時時間內完成negotiate請求。對新connect請求(即初始消息)的響應在新啓動的傳輸上發送(即若是您使用webSockets傳輸,它將在新打開的websocket上發送,若是您使用serverSentEvents它將在新打開的事件流上發送,若是您使用longPolling它將做爲對connect/ pollrequest 的回覆發送)
  • 一旦收到init消息,客戶端就會發送啓動請求。服務器經過響應{Response: Started}有效負載確認它收到了啓動請求
    您還能夠在此處找到有關啓動順序的一些詳細信息。

鏈接管理請求

如下是客戶端發送以啓動,中止和從新鏈接鏈接的請求列表。websocket

»negotiate - 協商鏈接參數
必需參數:clientProtocol,connectionData(使用集線器時)
可選參數:queryString
樣本請求:網絡

HTTP://主機/ signalr /談判clientProtocol = 1.5&connectionData =%5B%7B%22name%22%3A%22chat%22%7D%5D
樣品回覆:架構

{
  "Url":"/signalr",
  "ConnectionToken":"X97dw3uxW4NPPggQsYVcNcyQcuz4w2",
  "ConnectionId":"05265228-1e2c-46c5-82a1-6a5bcc3f0143",
  "KeepAliveTimeout":10.0,
  "DisconnectTimeout":5.0,
  "TryWebSockets":true,
  "ProtocolVersion":"1.5",
  "TransportConnectTimeout":30.0,
  "LongPollDelay":0.0
}
  • Url - SignalR端點的路徑。目前還沒有被客戶使用。
  • ConnectionToken - 服務器分配的鏈接令牌。有關詳細信息,請參閱此文章。此值須要在每一個後續請求中做爲connectionToken參數的值發送
  • ConnectionId- 鏈接的ID
  • KeepAliveTimeout- 客戶端在嘗試從新鏈接以前應等待的時間(以秒爲單位),若是它還沒有收到保持活動消息。若是服務器配置爲不發送保持活動消息,則此值爲空。
  • DisconnectTimeout - 若是鏈接消失,客戶端應嘗試從新鏈接的時間量。
  • TryWebSockets- 服務器是否支持websockets
  • ProtocolVersion- 用於通訊的協議版本
  • TransportConnectTimeout - 客戶端應嘗試使用給定傳輸鏈接到服務器的最長時間

»connect -開始傳輸
所需的參數:transport,clientProtocol,connectionToken,connectionData(使用集線器時)
可選參數:queryString
樣本請求:併發

WSS://主機/ signalr /鏈接傳輸的WebSockets =&clientProtocol = 1.5&connectionToken = LkNk&connectionData =%5B%7B%22name%22%3A%22chat%22%7D%5D?
示例響應(也稱爲init消息):socket

{"C":"s-0,2CDDE7A|1,23ADE88|2,297B01B|3,3997404|4,33239B5","S":1,"M":[]}
備註:
該connect請求將啓動運輸。若是您使用webSockets傳輸,客戶端將使用ws://或wss://方案打開websocket。若是您正在使用serverSentEvents傳輸,則客戶端將打開事件流。對於longPolling傳輸,服務器將鏈接請求視爲第一個輪詢請求。connect使用新打開的通道發送對請求的響應,而且是包含該屬性的JSon對象"S"設置爲1(aka init messge)。然而,服務器不保證該消息是發送給客戶端的第一個消息(例如,正在進行的廣播將在服務器發送init消息以前發送到客戶端。這在longPolling傳輸的狀況下頗有意思。由於對鏈接請求的響應將關閉掛起的鏈接請求,即便它不是init消息。在這種狀況下,init消息將做爲對後續輪詢請求的響應發送。編碼

»start -通知運輸成功啓動服務器
必需的參數:transport,clientProtocol,connectionToken,connectionData(使用集線器時)
可選參數:queryString
樣品要求:

HTTP://主機/ signalr /啓動運輸=&的WebSockets clientProtocol = 1.5&connectionToken = LkNk&connectionData =%5B%7B%22name%22%3A%22chat%22%7D%5D
樣品回覆:

{"Response":"started"}
備註:
start在協議版本1.4中添加了請求,以使某些方案在服務器端可靠地運行。將此請求添加到啓動序列會使客戶端上的事情變得複雜,由於在客戶端收到init消息以後但在收到對啓動消息的響應以前有不少事情可能會出錯(如鏈接丟失和客戶端開始從新鏈接,用戶中止鏈接等)。

»reconnect -當鏈接丟失發送到服務器和客戶端被從新鏈接
所需的參數:transport,clientProtocol,connectionToken,connectionData(使用集線器時), ,messageId(groupsToken若是鏈接所屬的組)
的可選參數:queryString
樣本請求:

WS://主機/ signalr /從新鏈接運輸=&的WebSockets clientProtocol = 1.4&connectionToken = AA- AQA&connectionData =%5B%7B%22Name%22:%22hubConnection%22%7D%5D&MESSAGEID = d-3104A0A8-H,0%7CL,0%7CM,2%7CK,0&groupsToken = AQ
樣本響應:N / A
備註:
與connect請求相似,reconnect請求啓動(從新啓動)傳輸。對於longPolling從客戶端角度來看的傳輸,它只是另外一種形式的輪詢,對於serverSentEvents傳輸,將打開一個新的事件流,爲webSockets傳輸它將打開一個新的websocket。在messageId講述什麼是客戶端收到的最後一條消息服務器和groupsToken通知客戶端從新鏈接前屬於什麼組服務器。

»abort -中止鏈接
所需的參數:transport,clientProtocol,connectionToken,connectionData(使用集線器時)
可選參數:queryString
樣本請求:

HTTP://主機/ signalr /停止運輸= longPolling&clientProtocol = 1.5&connectionToken = QcnlM&connectionData =%5B%7B%22name%22%3A%22chathub%22%7D%5D
示例響應:空
備註:JavaScript和C ++客戶端abort以一種消防方式發送請求並忽略全部錯誤。.NET客戶端阻塞,直到收到響應或發生超時,除了花費更多時間,會致使一些問題(如此錯誤)。

»ping - ping服務器
必需參數:無
可選參數:queryString
示例請求:

HTTP://主機/ signalr /ping
樣品回覆:

{ "Response": "pong" }
備註:ping請求實際上不是「鏈接管理請求」。此請求的惟一目的是使ASP.NET會話保持活動狀態。它僅由JavaScript客戶端發送。

SignalR消息

在咱們看看SignalR發送的消息以前,咱們須要討論不一樣的傳輸如何發送和接收消息。的webSockets運輸是很是簡單,由於它正在建立用於從服務器向客戶端,並從客戶端向服務器發送數據的全雙工通訊信道。設置通道後HTTP,在客戶端中止(abort請求)或鏈接丟失且客戶端嘗試從新創建鏈接(reconnect請求)以前,不會再有其餘請求。的serverSentEvents傳輸建立用於從服務器接收消息的事件流。若是客戶端想要向服務器發送消息,它將建立sendHTTP POST請求並在請求正文中發送數據。該longPollingtransport建立一個長時間運行的HTTP請求,若是服務器有客戶端消息,服務器將響應該請求。若是服務器未在配置的超時內發送任何數據(計算爲對請求ConnectionTimeout的響應中收到的總和negotiate+ 10秒 - 默認爲120秒),則當前輪詢請求將關閉,客戶端將啓動新的輪詢請求(這是爲了防止代理關閉長時間運行的請求,這會致使沒必要要的從新鏈接)。發送消息的工做方式與serverSentEvents傳輸相同- send包含請求正文中的消息的HTTP請求將發送到服務器。如下是的描述send和poll要求。

»send - 將數據發送到服務器。由所使用的serverSentEvents和longPolling傳輸
所需的參數:transport,clientProtocol,connectionToken,connectionData(使用集線器時),數據(請求正文發送)
可選參數:queryString
樣本請求:

HTTP://主機/ signalr /發送傳輸= longPolling&clientProtocol = 1.5&connectionToken = Ac5y5&connectionData =%5B%7B%22name%22%3A%22chathub%22%7D%5D
數據發送到請求體(url編碼,請參閱下面的說明):

數據=%7B%22H%22%3A%22chathub%22%2C%22M%22%3A%22Send%22%2C%22A%22%3A%5B%22A%22%2C%22test + MSG%22%5D %2C%22I%22%3A0%7D
樣品回覆(見下面的說明):

{ "I" : 0 }
»poll - 啓動(可能)長時間運行的輪詢請求,服務器將使用該請求將數據發送到客戶端。僅由所使用的longPolling傳輸
所需的參數:transport,clientProtocol,connectionToken,connectionData(使用集線器時),messageId(JavaScript的客戶端發送messageId在請求體)
可選參數:queryString
樣本請求:

HTTP://主機/ signalr /輪詢傳輸= longPolling&clientProtocol = 1.5&connectionToken = A12 -FX&connectionData =%5B%7B%22name%22%3A%22chathub%22%7D%5D&MESSAGEID = d-53B8FCED-B%2C1%7CC%2C0%7CD%2C1
樣品回覆(見下面的說明):

{
  "C":"d-53B8FCED-B,4|C,0|D,1",
  "M":
  [
    {"H":"ChatHub","M":"broadcastMessage","A":["client","test msg1"]},
    {"H":"ChatHub","M":"broadcastMessage","A":["client","test msg2"]},
    {"H":"ChatHub","M":"broadcastMessage","A":["client","qwerty"]}
  ]
}

持久鏈接消息

用於持久鏈接的協議很是簡單。發送到服務器的消息只是原始字符串。它們沒有任何特定的格式.C#客戶端有一個方便的Send()方法,它接受一個應該發送到服務器的對象,但全部這個方法只是將對象轉換爲JSon並調用Send()重載採起字符串。發送到客戶端的消息更加結構化。它們是具備許多屬性的JSon字符串。根據消息的目的,有效負載中可能存在不一樣的屬性,或者消息可能沒有屬性(KeepAlive消息)。您能夠在消息中找到的屬性以下:

  • C - 全部非KeepAlive消息的消息ID

  • M - 包含實際數據的數組。

{"C":"d-9B7A6976-B,2|C,2","M":["Welcome!"]}

  • S - 表示傳輸已初始化(也稱爲init消息)

{"C":"s-0,2CDDE7A|1,23ADE88|2,297B01B|3,3997404|4,33239B5","S":1,"M":[]}

  • G - groups token - 表示組成員身份的加密字符串

{"C":"d-6CD4082D-B,0|C,2|D,0","G":"92OXaCStiSZGy5K83cEEt8aR2ocER=","M":[]}

  • T- 若是值是1客戶端應該轉換到從新鏈接狀態並嘗試從新鏈接到服務器(即發送reconnect請求)。1若是正在關閉或從新啓動,則服務器正在發送一條消息,並將此屬性設置爲。longPolling僅適用於運輸。

  • L - 從新創建輪詢鏈接之間的延遲。longPolling僅適用於運輸。僅由JavaScript客戶端使用。可經過設置IConfigurationManager.LongPollDelay屬性在服務器上進行配置。

{"C":"d-E9D15DD8-B,4|C,0|D,0","L":2000,
"M":[{"H":"ChatHub","M":"broadcastMessage","A":["C++","msg"]}]}
KeepAlive消息
KeepAlive消息是空對象JSon字符串(即{}),SignalR客戶端可使用它來檢測網絡問題。SignalR服務器將以配置的時間間隔發送保持活動消息。若是客戶端在一段時間內沒有從服務器收到任何消息(包括保持活動消息),它將嘗試從新啓動鏈接。請注意,並不是全部客戶端當前都支持基於網絡活動從新啓動鏈接(最值得注意的是SignalR C ++客戶端不支持)。經過將KeepAlive服務器配置屬性設置爲,能夠關閉服務器發送保持活動消息null。

集線器消息

Hubs API能夠從服務器的客戶端和客戶端方法調用服務器方法。用於持久鏈接的協議不夠豐富,沒法表達RPC(遠程過程調用)語義。可是,這並不意味着用於集線器鏈接的協議與用於持久鏈接的協議徹底不一樣。相反,用於集線器鏈接的協議主要是用於持久鏈接的協議的擴展。
當客戶端調用服務器方法時,它再也不像持久鏈接那樣發送自由流字符串。相反,它發送一個JSon字符串,其中包含調用該方法所需的全部必要信息。如下是客戶端發送以調用服務器方法的示例消息:

{"H":"chathub","M":"Send","A":["JS Client","Test message"],"I":0, "S":{"customProperty" : "abc"}}
有效負載具備如下屬性:

  • I- 調用標識符 - 容許將響應與請求匹配
  • H- 集線器
  • M的名稱 - 方法的名稱
  • A- 參數(若是方法沒有任何參數,則數組能夠爲空)
  • S- 狀態 - 包含其餘自定義數據的字典(可選,當前C ++客戶端不支持)

從服務器發送到客戶端的消息能夠是如下之一:

  • 服務器方法調用的結果
  • 調用客戶端方法
  • 進度信息
  • 服務器端集線器方法調用結果

當調用服務器方法時,服務器經過向客戶端發送調用id來返回調用已完成的確認,而且 - 若是方法返回值 - 返回值,或者 - 若是調用方法失敗 - 則返回錯誤。有兩種錯誤 - 通常錯誤和集線器錯誤。在通常的錯誤的狀況下,響應僅包含一個錯誤消息,而且該錯誤由客戶端變成一個通用異常- .NET客戶端拋出InvalidOperationException,C ++的客戶端拋出一個std::runtime_error和JavaScript客戶機建立Error與Exception做爲源。集線器錯誤包含布爾屬性設置,true以指示它們是集線器錯誤,而且它們可能包含一些其餘錯誤數據。集線器錯誤HubException由.NET客戶端轉換爲asignalr::hub_exception由C ++客戶端和JavaScript客戶端建立一個Error源設置爲HubException。如下是服務器方法調用的示例結果:

{"I":"0"}
void調用標識符已"0"成功完成的服務器方法。

"{"I":"0", "R":42}
返回"0"成功完成調用標識符的數字的服務器方法,並返回該值42。

{"I":"0", "E":"Error occurred"}
一種服務器方法,其調用標識符因"0"錯誤而失敗"Error occurred"

{"I":"0","E":"Hub error occurred", "H":true, "D":{"ErrorNumber":42}}
一種服務器方法,其調用標識符因"0"集線器錯誤"Hub error occurred"而失敗,併發送了一些其餘錯誤數據。

如下是服務器方法調用結果中能夠包含的完整屬性列表:

  • I- invocation Id(始終存在)
  • R- 服務器方法返回的值(若是方法不爲void,則顯示)
  • E- 錯誤消息
  • H- true若是這是一個集線器錯誤
  • D- 包含其餘錯誤數據的對象(只能出現集線器錯誤)
  • T- 堆棧跟蹤(若是HubConfiguration.EnableDetailedErrors在服務器上打開了詳細的錯誤報告(即屬性))。請注意,沒有任何客戶端當前將堆棧跟蹤傳播給用戶,但若是啓用了跟蹤,則會記錄消息
  • S- state - 包含其餘自定義數據的字典(可選,當前C ++客戶端不支持)

客戶端集線器方法調用

要調用客戶端方法,服務器會擴展用於持久鏈接的協議。不一樣之處在於,服務器不是在消息的消息部分中發送自由流文本,而是發送一個JSon字符串,其中包含調用該方法所需的全部詳細信息(如集線器和方法名稱和參數)。如下是服務器發送的用於在客戶端上調用hub方法的消息的示例:

{"C":"d- F430FB19", "M":[{"H":"my_hub", "M":"broadcast", "A":["Hi!", 1]}] }
正如您所看到的,消息ID或消息屬性形式的「信封」與持久鏈接相同。從中心角度來看,有趣的部分是M財產的價值:

{"H":"my_hub", "M":"broadcast", "A":["Hi!", 1]}
此結構與客戶端用於調用服務器中心方法的結構很是類似(除了沒有調用ID,由於服務器不指望對此消息作出任何響應)。

  • H- 集線器
  • M的名稱 - 集線器方法的名稱
  • A- 參數(若是方法沒有任何參數,則數組能夠爲空)
  • S- 狀態 - 包含其餘自定義數據的字典(可選,當前不支持(忽略) C ++客戶端)

進展信息

從服務器發送到客戶端的最後一種消息是進度消息。當服務器方法是長時間運行的方法時,服務器能夠將關於方法的執行進度的信息發送到客戶端。與客戶端方法調用相似,進度信息嵌入在持久鏈接消息的消息部分中。整個消息以下所示:

{"C":"d-5E80A020-A,1|B,0|C,15|D,0", M:[{I:"P|1", "P":{"I":"0", "D":1}}] }
但進度消息自己看起來像這樣:

{I:"P|1", "P":{"I":"0", "D":1}}
包含有關進度信息的結構包含兩個屬性:
I- 一種調用ID,但前綴爲"P|"。僅供較舊的客戶使用。
P - 包含有關進度的實際信息的對象

包含「真實」進度信息的對象具備如下屬性:
I- 調用此進程消息適用於哪一個調用的調用ID
D- 方法返回的進度數據

請注意,在服務器發送調用方法的實際結果以前,可能會有多個進度消息發送到客戶端。

最近的協議修訂

1.4 - start請求的介紹 1.5 - 如今可使用該POST方法發送請求。在Chrome和IE瀏覽器中使用傳輸時,這有助於避免內存泄漏longPolling(錯誤2953)。僅在longPolling運輸時由JS客戶端使用。請注意,服務器檢查請求主體的惟一屬性是groupsToken和messageId 這就是它。SignalR協議並非很複雜,可是一些警告和例外可能會使實現有點麻煩。

相關文章
相關標籤/搜索