1. 程式人生 > >[機翻] WIRER ON THE WIRE - SIGNALR協議的非正式描述

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

原文
原文很簡單,以下為機翻

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}有效負載確認它收到了啟動請求
    您還可以在此處找到有關啟動順序的一些詳細資訊。

連線管理請求

以下是客戶端傳送以啟動,停止和重新連線連線的請求列表。

»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訊息):

{"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協議並不是很複雜,但是一些警告和例外可能會使實現有點麻煩。