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

来源:https://www.cnblogs.com/cerl/archive/2018/12/03/10057484.html
-Advertisement-
Play Games

"原文" 原文很簡單,以下為機翻 WIRER ON THE WIRE SIGNALR協議的非正式描述 我已經看到詢問有關SignalR協議的描述的問題出現了很多。哎呀,當我開始關註SignalR時,我也在尋找類似的東西。現在,差不多一年之後,在我從架構上重新設計了SignalR C#客戶端並從頭開始 ...


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

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...