理解ASP.NET Core 中的WebSocket
在本文中,我們將詳細介紹RFC 6455 WebSocket規範,並配置一個通用的.NET 5應用程式通過WebSocket連線與SignalR通訊。
我們將深入底層的概念,以理解底層發生了什麼。
關於WebSocket
引入WebSocket是為了實現客戶端和伺服器之間的雙向通訊。HTTP 1.0的一個痛點是每次向伺服器傳送請求時建立和關閉連線。但是,在HTTP 1.1中,通過使用保持連線機制引入了持久連線(RFC 2616)。這樣,連線可以被多個請求重用——這將減少延遲,因為伺服器知道客戶端,它們不需要在每個請求的握手過程中啟動。
WebSocket建立在HTTP 1.1規範之上,因為它允許持久連線。因此,當你第一次建立WebSocket連線時,它本質上是一個HTTP 1.1請求(稍後詳細介紹)。這使得客戶端和伺服器之間能夠進行實時通訊。簡單地說,下圖描述了在發起(握手)、資料傳輸和關閉WS連線期間發生的事情。我們將在後面更深入地研究這些概念。
協議中包含了兩部分:握手和資料傳輸。
握手
讓我們先從握手開始。
簡單地說,WebSocket連線基於單個埠上的HTTP(和作為傳輸的TCP)。下面是這些步驟的總結。
1. 伺服器必須監聽傳入的TCP套接字連線。這可以是你分配的任何埠—通常是80或443。
2. 客戶端通過一個HTTP GET請求發起開始握手(否則伺服器將不知道與誰對話)——這是“WebSockets”中的“Web”部分。在訊息報頭中,客戶端將請求伺服器將連線升級到WebSocket。
3. 伺服器傳送一個握手響應,告訴客戶端它將把協議從HTTP更改為WebSocket。
4. 客戶端和伺服器雙方協商連線細節。任何一方都可以退出。
下面是一個典型的開啟(客戶端)握手請求的樣子。
GET /ws-endpoint HTTP/1.1 Host: example.com:80 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: L4kHN+1Bx7zKbxsDbqgzHw== Sec-WebSocket-Version: 13
注意客戶端是如何在請求中傳送Connection: Upgrade和Upgrade: websocket報頭的。
並且,伺服器握手響應。
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: CTPN8jCb3BUjBjBtdjwSQCytuBo=
資料傳輸
我們需要理解的下一個關鍵概念是資料傳輸。任何一方都可以在任何給定的時間傳送訊息——因為它是一個全雙工通訊協議。
訊息由一個或多個幀組成。幀的型別可以是文字(UTF-8)、二進位制和控制幀(例如0x8 (Close)、0x9 (Ping)和0xA (Pong))。
安裝
讓我們付諸行動,看看它是如何工作的。
首先建立一個 ASP.NET 5 WebAPI 專案。
dotnet new webapi -n WebSocketsTutorial dotnet new sln dotnet sln add WebSocketsTutorial
現在新增SignalR到專案中。
dotnet add WebSocketsTutorial/ package Microsoft.AspNet.SignalR
示例程式碼
我們首先將WebSockets中介軟體新增到我們的WebAPI應用程式中。開啟Startup.cs,向Configure方法新增下面的程式碼。
在本教程中,我喜歡保持簡單。因此,我不打算討論SignalR。它將完全基於WebSocket通訊。你也可以用原始的WebSockets實現同樣的功能,如果你想讓事情變得更簡單,你不需要使用SignalR。
app.UseWebSockets();
接下來,我們將刪除預設的WeatherForecastController,並新增一個名為WebSocketsController的新控制器。注意,我們將只是使用一個控制器action,而不是攔截請求管道。
這個控制器的完整程式碼如下所示。
using System; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace WebSocketsTutorial.Controllers{ [ApiController] [Route("[controller]")] public class WebSocketsController : ControllerBase { private readonly ILogger<WebSocketsController> _logger; public WebSocketsController(ILogger<WebSocketsController> logger) { _logger = logger; } [HttpGet("/ws")] public async Task Get() { if (HttpContext.WebSockets.IsWebSocketRequest) { using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); _logger.Log(LogLevel.Information, "WebSocket connection established"); await Echo(webSocket); } else { HttpContext.Response.StatusCode = 400; } } private async Task Echo(WebSocket webSocket) { var buffer = new byte[1024 * 4]; var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); _logger.Log(LogLevel.Information, "Message received from Client"); while (!result.CloseStatus.HasValue) { var serverMsg = Encoding.UTF8.GetBytes($"Server: Hello. You said: {Encoding.UTF8.GetString(buffer)}"); await webSocket.SendAsync(new ArraySegment<byte>(serverMsg, 0, serverMsg.Length), result.MessageType, result.EndOfMessage, CancellationToken.None); _logger.Log(LogLevel.Information, "Message sent to Client"); result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); _logger.Log(LogLevel.Information, "Message received from Client"); } await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); _logger.Log(LogLevel.Information, "WebSocket connection closed"); } } }
這是我們所做的。
1、新增一個名為ws/的新路由。
2、檢查當前請求是否通過WebSockets,否則丟擲400。
3、等待,直到客戶端發起請求。
4、進入一個迴圈,直到客戶端關閉連線。
5、在迴圈中,我們將傳送“Server: Hello. You said: <client’s message>”資訊,並把它發回給客戶端。
6、等待,直到客戶端傳送另一個請求。
注意,在初始握手之後,伺服器不需要等待客戶端傳送請求來將訊息推送到客戶端。讓我們執行應用程式,看看它是否工作。
dotnet run --project WebSocketsTutorial
執行應用程式後,請訪問https://localhost:5001/swagger/index.html,應該看到Swagger UI。
現在我們將看到如何讓客戶端和伺服器彼此通訊。在這個演示中,我將使用Chrome的DevTools(開啟新標籤→檢查或按F12→控制檯標籤)。但是,你可以選擇任何客戶端。
首先,我們將建立一個到伺服器終結點的WebSocket連線。
let webSocket = new WebSocket('wss://localhost:5001/ws');
它所做的是,在客戶端和伺服器之間發起一個連線。wss://是WebSockets安全協議,因為我們的WebAPI應用程式是通過TLS服務的。
然後,可以通過呼叫webSocket.send()方法傳送訊息。你的控制檯應該類似於下面的控制檯。
讓我們仔細看看WebSocket連線
如果轉到Network選項卡,則通過WS選項卡過濾掉請求,並單擊最後一個稱為WS的請求。
單擊Messages選項卡並檢查來回傳遞的訊息。在此期間,如果呼叫以下命令,將能夠看到“This was sent from the Client!”。試試吧!
webSocket.send("Client: Hello");
如你所見,伺服器確實需要等待客戶端傳送響應(即在初始握手之後),並且客戶端可以傳送訊息而不會被阻塞。這是全雙工通訊。我們已經討論了WebSocket通訊的資料傳輸方面。作為練習,你可以執行一個迴圈將訊息推送到客戶機,以檢視它的執行情況。
除此之外,伺服器和客戶端還可以通過ping-pong來檢視客戶端是否還活著。這是WebSockets中的一個實際特性!如果你真的想看看這些資料包,你可以使用像WireShark這樣的工具來了解。
它是如何握手的?好吧,如果你跳轉到Headers選項卡,你將能夠看到我們在這篇文章的第一部分談到的請求-響應標題。
也可以嘗試一下webSocket.close(),這樣我們就可以完全覆蓋open-data-close迴圈了。
結論
如果你對WebSocket的RFC感興趣,請訪問RFC 6455並閱讀。這篇文章只是觸及了WebSocket的表面,還有很多其他的東西我們可以討論,比如安全,負載平衡,代理等等。
原文連結:https://sahansera.dev/understanding-websockets-with-aspnetcore-5/