app.Run(async (context) =>
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
var socketFinishedTcs = new TaskCompletionSource<object>();
BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);
await socketFinishedTcs.Task;
如果从操作方法返回过快,则还可能发生 WebSocket 关闭异常。 接受操作方法中的套接字时,请等待使用该套接字的代码完成运行,然后再从操作方法返回。
坚决不要使用 Task.Wait
、Task.Result
或类似阻塞调用来等待套接字完成,因为这可能导致严重的线程处理问题。 请始终使用 await
。
为现有控制器添加 HTTP/2 WebSockets 支持
.NET 7 为 Kestrel、SignalR JavaScript 客户端和带有 Blazor WebAssembly 的 SignalR 引入了基于 HTTP/2 的 Websockets 支持。 HTTP/2 WebSockets 使用 CONNECT 请求,而不是 GET。 如果以前在用于 Websocket 请求的控制器操作方法上使用过 [HttpGet("/path")]
,请改为将其更新到 [Route("/path")]
。
public class WebSocketController : ControllerBase
[Route("/ws")]
public async Task Get()
if (HttpContext.WebSockets.IsWebSocketRequest)
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
await Echo(webSocket);
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
通过加密连接启用压缩可能会使应用受到 CRIME/BREACH 攻击。
如果发送敏感信息,请避免在调用 WebSocket.SendAsync
时启用压缩或使用 WebSocketMessageFlags.DisableCompression
。
这适用于 WebSocket 的两端。 请注意,浏览器中的 WebSockets API 没有为每次发送禁用压缩的配置。
如果需要通过 WebSockets 压缩消息,则接受代码必须指定它允许压缩,如下所示:
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
new WebSocketAcceptContext { DangerousEnableCompression = true }))
WebSocketAcceptContext.ServerMaxWindowBits
和 WebSocketAcceptContext.DisableServerContextTakeover
是用于控制压缩工作方式的高级选项。
首次建立连接时,在客户端和服务器之间协商压缩。 可以在 WebSocket RFC 的压缩扩展中了解有关协商的更多信息。
如果服务器或客户端不接受压缩协商,则仍会建立连接。 但是,在发送和接收信息时,连接不使用压缩。
发送和接收消息
AcceptWebSocketAsync
方法将 TCP 连接升级到 WebSocket 连接,并提供 WebSocket 对象。 使用 WebSocket
对象发送和接收消息。
之前显示的接受 WebSocket 请求的代码将 WebSocket
对象传递给 Echo
方法。 代码接收消息并立即发回相同的消息。 循环发送和接收消息,直到客户端关闭连接:
private static async Task Echo(WebSocket webSocket)
var buffer = new byte[1024 * 4];
var receiveResult = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
while (!receiveResult.CloseStatus.HasValue)
await webSocket.SendAsync(
new ArraySegment<byte>(buffer, 0, receiveResult.Count),
receiveResult.MessageType,
receiveResult.EndOfMessage,
CancellationToken.None);
receiveResult = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
await webSocket.CloseAsync(
receiveResult.CloseStatus.Value,
receiveResult.CloseStatusDescription,
CancellationToken.None);
如果在开始循环之前接受 WebSocket 连接,中间件管道会结束。 关闭套接字后,管道展开。 即接受 WebSocket 时,请求停止在管道中推进。 循环结束且套接字关闭时,请求继续回到管道。
处理客户端连接断开
当客户端由于失去连接而断开连接时,不会自动向服务器发送通知。 服务器只有在客户端发送通知时才会收到断开连接消息,而此操作无法在失去 Internet 连接的情况下进行。 如果想要在发生此情况时采取某个操作,在特定时间范围内未收到来自客户端的任何消息后设置超时。
如果客户端并非总是发送消息,并且你不希望仅由于连接进入空闲状态就设置超时,则让客户端使用一个计时器并每隔 X 秒发送一条 ping 消息。 在服务器上,如果某条消息在上一条消息发出后的 2*X 秒内尚未到达,则终止连接并报告客户端已断开连接。 等待两次预测的时间间隔,以便为可能延迟 ping 消息的网络延迟提供额外的时间。
WebSocket 源限制
CORS 提供的保护不适用于 WebSocket。 浏览器不会:
执行 CORS 预检请求。
在发出 WebSocket 请求时,遵守 Access-Control
标头中指定的限制。
但是,浏览器在发出 WebSocket 请求时会发送 Origin
标头。 应将应用程序配置为验证这些标头,以确保只允许来自预期来源的 WebSocket。
如果在“https://server.com"”上托管服务器并在“https://client.com"”上托管客户端,请将“https://client.com"”添加到 AllowedOrigins 列表以验证 WebSocket。
var webSocketOptions = new WebSocketOptions
KeepAliveInterval = TimeSpan.FromMinutes(2)
webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");
app.UseWebSockets(webSocketOptions);
与 Referer
标头一样,Origin
标头由客户端控制,并可以伪造。 请勿将这些标头用作身份验证机制。
IIS/IIS Express 支持
安装了 IIS/IIS Express 8 或更高版本的 Windows Server 2012 或更高版本以及 Windows 8 或更高版本支持 WebSocket 协议,但不支持基于 HTTP/2 的 WebSockets。
使用 IIS Express 时始终启用 WebSocket。
在 IIS 上启用 Websocket
在 Windows Server 2012 或更高版本上启用对 WebSocket 协议的支持:
使用 IIS Express 时无需执行这些步骤
通过“管理”菜单或“服务器管理器”中的链接使用“添加角色和功能”向导。
选择“基于角色或基于功能的安装”。 选择“下一步” 。
选择适当的服务器(默认情况下选择本地服务器)。 选择“下一步” 。
在“角色”树中展开“Web 服务器 (IIS)”、然后依次展开“Web 服务器”和“应用程序开发” 。
选择“WebSocket 协议”。 选择“下一步” 。
如果无需其他功能,请选择“下一步”。
选择“安装” 。
安装完成后,选择“关闭”以退出向导。
在 Windows 8 或更高版本上启用对 WebSocket 协议的支持:
使用 IIS Express 时无需执行这些步骤
导航到“控制面板”>“程序”>“程序和功能”>“启用或禁用 Windows 功能”(位于屏幕左侧) 。
打开以下节点:“Internet Information Services”>“万维网服务”>“应用程序开发功能” 。
选择“WebSocket 协议”功能。 选择“确定”。
在 Node.js 上使用 socket.io 时禁用 WebSocket
如果在 Node.js 的 socket.io 中使用 WebSocket 支持,请使用 web.config 或 applicationHost.config 中的 webSocket
元素禁用默认的 IIS WebSocket 模块 。如果不执行此步骤,IIS WebSocket 模块将尝试处理 WebSocket 通信而不是 Node.js 和应用。
<system.webServer>
<webSocket enabled="false" />
</system.webServer>
本文附带的示例应用是一个 echo 应用。 它有一个可建立 WebSocket 连接的网页,且服务器将其收到的消息都重新发回到客户端。 使用 .NET 7 或更高版本的目标框架时,示例应用支持基于 HTTP/2 的 WebSocket。
运行应用:
在 Visual Studio 中运行应用:在 Visual Studio 中打开示例项目,然后按 Ctrl+F5 在不使用调试程序的情况下运行。
若要在命令行界面中运行应用:运行命令 dotnet run
并在浏览器中导航到 http://localhost:<port>
。
该网页显示连接状态:
选择“连接”,向显示的 URL 发送 WebSocket 请求。 输入测试消息并选择“发送”。 完成后,请选择“关闭套接字”。 “通信日志”部分会报告每一个发生的“打开”、“发送”和“关闭”操作。
本文介绍 ASP.NET Core 中 WebSocket 的入门方法。 WebSocket (RFC 6455) 是一个协议,支持通过 TCP 连接建立持久的双向信道。 它用于从快速实时通信中获益的应用,如聊天、仪表板和游戏应用。
查看或下载示例代码(如何下载、如何运行)。
SignalR
ASP.NET Core SignalR 是一个库,可用于简化向应用添加实时 Web 功能。 它会尽可能地使用 WebSocket。
对于大多数应用程序,我们建议使用 SignalR,而不是原始 WebSocket。 SignalR 可为 WebSocket 不可用的环境提供传输回退。 它还可提供基本的远程过程调用应用模型。 并且在大多数情况下,与使用原始 WebSocket 相比,SignalR 没有显著的性能缺点。
对于某些应用,.NET 上的 gRPC 提供了 WebSocket 的替代方法。
支持 ASP.NET Core 的任何操作系统:
- Windows 7/Windows Server 2008 或更高版本
- Linux
- macOS
- 如果应用在安装了 IIS 的 Windows 上运行: