理解ASP.NET Core 中的WebSocket


在本文中,咱們將詳細介紹RFC 6455 WebSocket規範,並配置一個通用的.NET 5應用程序經過WebSocket鏈接與SignalR通訊。javascript

咱們將深刻底層的概念,以理解底層發生了什麼。css

關於WebSockethtml

引入WebSocket是爲了實現客戶端和服務器之間的雙向通訊。HTTP 1.0的一個痛點是每次向服務器發送請求時建立和關閉鏈接。可是,在HTTP 1.1中,經過使用保持鏈接機制引入了持久鏈接(RFC 2616)。這樣,鏈接能夠被多個請求重用——這將減小延遲,由於服務器知道客戶端,它們不須要在每一個請求的握手過程當中啓動。java

WebSocket創建在HTTP 1.1規範之上,由於它容許持久鏈接。所以,當你第一次建立WebSocket鏈接時,它本質上是一個HTTP 1.1請求(稍後詳細介紹)。這使得客戶端和服務器之間可以進行實時通訊。簡單地說,下圖描述了在發起(握手)、數據傳輸和關閉WS鏈接期間發生的事情。咱們將在後面更深刻地研究這些概念。nginx

協議中包含了兩部分:握手和數據傳輸。web

握手api

讓咱們先從握手開始。安全

簡單地說,WebSocket鏈接基於單個端口上的HTTP(和做爲傳輸的TCP)。下面是這些步驟的總結。服務器

1. 服務器必須監聽傳入的TCP套接字鏈接。這能夠是你分配的任何端口—一般是80或443。微信

2. 客戶端經過一個HTTP GET請求發起開始握手(不然服務器將不知道與誰對話)——這是「WebSockets」中的「Web」部分。在消息報頭中,客戶端將請求服務器將鏈接升級到WebSocket。

3. 服務器發送一個握手響應,告訴客戶端它將把協議從HTTP更改成WebSocket。

4. 客戶端和服務器雙方協商鏈接細節。任何一方均可以退出。

下面是一個典型的打開(客戶端)握手請求的樣子。

GET /ws-endpoint HTTP/1.1Host: example.com:80Upgrade: websocketConnection: UpgradeSec-WebSocket-Key: L4kHN+1Bx7zKbxsDbqgzHw==Sec-WebSocket-Version: 13

注意客戶端是如何在請求中發送Connection: Upgrade和Upgrade: websocket報頭的。

而且,服務器握手響應。

HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: CTPN8jCb3BUjBjBtdjwSQCytuBo=

數據傳輸

咱們須要理解的下一個關鍵概念是數據傳輸。任何一方均可以在任何給定的時間發送消息——由於它是一個全雙工通訊協議。

消息由一個或多個幀組成。幀的類型能夠是文本(UTF-8)、二進制和控制幀(例如0x8 (Close)、0x9 (Ping)和0xA (Pong))。

安裝

讓咱們付諸行動,看看它是如何工做的。

首先建立一個 ASP.NET 5 WebAPI 項目。

dotnet new webapi -n WebSocketsTutorialdotnet new slndotnet 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"); } }}

這是咱們所作的。

一、添加一個名爲ws/的新路由。

二、檢查當前請求是否經過WebSockets,不然拋出400。

三、等待,直到客戶端發起請求。

四、進入一個循環,直到客戶端關閉鏈接。

五、在循環中,咱們將發送「Server: Hello. You said: <client’s message>」信息,並把它發回給客戶端。

六、等待,直到客戶端發送另外一個請求。

注意,在初始握手以後,服務器不須要等待客戶端發送請求來將消息推送到客戶端。讓咱們運行應用程序,看看它是否工做。

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的表面,還有不少其餘的東西咱們能夠討論,好比安全,負載平衡,代理等等。



本文分享自微信公衆號 - 碼農譯站(gh_c0d62fbb4bda)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索