在本文中,我们将详细介绍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. zjnu1725 COCI (类似二维树状数组模拟)

    Description The 3rd round of COCI is already here! In order to bet on predict the scores, we have as ...

  2. C. Table Decorations

    time limit per test 1 second memory limit per test 256 megabytes input standard input output standar ...

  3. Educational Codeforces Round 94 (Rated for Div. 2) D. Zigzags (枚举,前缀和)

    题意:有一长度为\(n(4\le n\le 3000)\)的数组,选择四个位置\((i,j,k,l)\ (1\le i<j<k\le n)\),使得\(a_i=a_k\)并且\(a_j=a ...

  4. Educational Codeforces Round 95 (Rated for Div. 2) C. Mortal Kombat Tower (DP)

    题意:你和基友两人从左往右轮流打怪兽,强怪用\(1\)表示,垃圾用\(0\)表示,但基友比较弱,打不过强怪,碰到强怪需要用一次魔法,而你很强,无论什么怪都能乱杀,基友先打,每人每次至少杀一个怪兽,最多 ...

  5. DSSM在召回和粗排的应用举例

    0.写在前面的话 DSSM(Deep Structured Semantic Models)又称双塔模型,因其结构简单,在推荐系统中应用广泛:下面仅以召回.粗排两个阶段的应用举例,具体描述下DSSM在 ...

  6. 使用DTK创建模糊背景窗口并自定义阴影效果

    DTK是deepin开发的基于Qt的开发套件,提供了大量的具有独特风格的美化控件,也提供了很多非常方便的API,下边我们用DTK实现一个模糊窗口,并设置其阴影效果. 使用场景 一切需要模糊窗口作为美化 ...

  7. meterpreter php payload && windows payload 学习

    一 情景 本地kali linux 192.168.1.2 目标 windows NT 服务器192.168.1.4 目的是获取shell 二 过程 首先在linux建立终端 ,msfconsole ...

  8. 关于string类中find函数的讲解

    以下所讲的所有的string查找函数,都有唯一的返回类型,那就是size_type,即一个无符号整数(按打印出来的算).若查找成功,返回按查找规则找到的第一个字符或子串的位置:若查找失败,返回npos ...

  9. windows10 浏览器跑分对比!

    2015-12-12 windows10 浏览器跑分对比! YOUR BROWSER SCORES!                MaxScore=555http://html5test.com/i ...

  10. Android APP 多端适配

    Android APP 多端适配 传统的多终端适配方案,是为大尺寸 Pad开发一个特定的 HD版本. 但是目前支持 Android 系统的设备类型越来越丰富,不同类型的设备尺寸也越来越多样化,特定的H ...