理解ASP.NET Core 中的WebSocket
- 2021 年 3 月 4 日
- 筆記
在本文中,我們將詳細介紹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
運行應用程式後,請訪問//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的表面,還有很多其他的東西我們可以討論,比如安全,負載平衡,代理等等。
原文鏈接://sahansera.dev/understanding-websockets-with-aspnetcore-5/