乐趣区

关于c#:理解ASPNET-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/underst…

​​​​​​

退出移动版