在本文中,我们将详细介绍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/

​​​​​​​

理解ASP.NET Core 中的WebSocket的更多相关文章

  1. ASP.NET Core 中的 WebSocket 支持(转自MSDN)

    本文介绍 ASP.NET Core 中 WebSocket 的入门方法. WebSocket (RFC 6455) 是一个协议,支持通过 TCP 连接建立持久的双向信道. 它用于从快速实时通信中获益的 ...

  2. 网络游戏开发-服务器(01)Asp.Net Core中的websocket,并封装一个简单的中间件

    先拉开MSDN的文档,大致读一遍 (https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/websockets) WebSocket 是一 ...

  3. 在Asp.net core中使用WebScocket

    今天小试了一下在Asp.net core中使用websocket,这里记录一下: 在 Startup 类的 Configure 方法中添加 WebSocket 中间件. app.UseWebSocke ...

  4. 在Asp.net Core中使用中间件来管理websocket

    介绍 ASP.NET Core SignalR是一个有用的库,可以简化Web应用程序中实时通信的管理.但是,我宁愿使用WebSockets,因为我想要更灵活,并且与任何WebSocket客户端兼容. ...

  5. 浅谈ASP.NET Core中IOC与DI的理解和使用

    说起IOC和DI,使用过ASP.NET Core的人对这两个概念一定不陌生,早前,自己也有尝试过去了解这两个东西,但是一直觉得有点很难去理解,总觉得对其还是模糊不清,所以,趁着今天有空,就去把两个概念 ...

  6. ASP.NET Core 中的那些认证中间件及一些重要知识点

    前言 在读这篇文章之间,建议先看一下我的 ASP.NET Core 之 Identity 入门系列(一,二,三)奠定一下基础. 有关于 Authentication 的知识太广,所以本篇介绍几个在 A ...

  7. ASP.NET Core中的依赖注入(1):控制反转(IoC)

    ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...

  8. ASP.NET Core中的依赖注入(2):依赖注入(DI)

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...

  9. ASP.NET Core中的依赖注入(4): 构造函数的选择与服务生命周期管理

    ServiceProvider最终提供的服务实例都是根据对应的ServiceDescriptor创建的,对于一个具体的ServiceDescriptor对象来说,如果它的ImplementationI ...

随机推荐

  1. ACM International Collegiate Programming Contest, Egyptian Collegiate Programming Contest (ECPC 2015) G. It is all about wisdom (二分,单源最短路)

    题意:有\(n\)个点,\(m\)条边,只有当你的智力值大于这条边的\(w\)才能走,问在花费不超过\(k\)的情况下,从\(1\)走到\(n\)的所需的最小智力值. 题解:这题比赛为什么没想出来呢? ...

  2. 记一次Python调试问题

    C#调用python脚本错误: 使用cmd直接运行脚本报错: TabError: inconsistent use of tabs and spaces in indentation 查看日志报错: ...

  3. forEachRemaining()方法的用法

    forEachRemaining()是java1.8新增的Iterator接口中的默认方法对于这个方法,官方文档是这么描述的:Performs the given action for each re ...

  4. CentOS 7 架设LNMP动态网站

    1.安装Nginx 1)使用Nginx官方的yum源 [root@localhost ~]# vim /etc/yum.repos.d/nginx.repo [nginx] name=nginx re ...

  5. _.shuffle、_.debounce中下划线对象的理解

    Vue 官方教程中有_.shuffle._.debounce,不明白"_"是怎么来的,有什么意义? Lodash 和 Underscorejs 都有相关解释

  6. P4213 【模板】杜教筛(杜教筛)题解

    题意: 求\(\sum_{i=1}^n\varphi(i)\)和\(\sum_{i=1}^n\mu(i)\) 思路: 由性质可知:\(\mu*I=\epsilon,\varphi*I=id\)那么可得 ...

  7. python阿里云api查询域名是否可以注册(CheckDomain)

    import requests from fun import * from urllib import parse url ='http://domain.aliyuncs.com/?' acces ...

  8. reCAPTCHA OCR 详解 , 验验证, OCR(光学自动识别)

    WEB安全专题 ‍‍reCAPTCHA的诞生及意义‍‍ CMU(卡耐基梅隆大学)设计了一个名叫reCAPTCHA的强大系统,让电脑去向人类求助.具体做法是:将OCR(光学自动识别)软件无法识别的文字扫 ...

  9. nodemon all in one

    nodemon all in one https://nodemon.io/ https://github.com/remy/nodemon#nodemon https://www.npmjs.com ...

  10. Array.fill & array padding

    Array.fill & array padding arr.fill(value[, start[, end]]) https://developer.mozilla.org/en-US/d ...