理解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源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索