You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 8 Web API中同时使用SignalR与原生WebSocket冲突问题求助

问题

在托管ASP.NET应用的.NET 8 LTS Web API项目的同一Kestrel服务器上,同时使用SignalR与原生WebSocket时出现异常行为:单独使用两者均正常,但同时启用后,所有通信都会停止,直至其中一方断开连接。

项目包含REST接口,引入原生WebSocket是为了适配无SignalR客户端库的客户端。

SignalR在Program.cs中的配置如下:

builder.Services.AddSignalR(options => {
   options.EnableDetailedErrors = true;
});

原生WebSocket的配置如下:

app.UseWebSockets();
app.Map("/ws", WebSocketHandler.HandleWebSocketConnection);

WebSocketHandler.HandleWebSocketConnection方法代码如下:

public static async Task HandleWebSocketConnection(HttpContext context)
{
    if (!context.WebSockets.IsWebSocketRequest)
    {
        context.Response.StatusCode = 400; // Bad Request
        await context.Response.WriteAsync("WebSocket request expected.");
        return;
    }

    var clientId = Guid.NewGuid().ToString();
    WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
    Console.WriteLine($"Client connected: {clientId}");

    try
    {
        await HandleClientMessagesAsync(webSocket);
    }
    finally
    {
        Console.WriteLine($"Client disconnected: {clientId}");
    }
}

按底层逻辑,两者应共用一个WebSocket服务器端口,为何会出现冲突?如何在同一服务器上同时启用两者且无冲突?

原因分析

冲突的核心问题在于中间件注册顺序和请求线程占用:

  • 路由匹配顺序问题:app.UseWebSockets()是全局WebSocket中间件,若原生WebSocket的路由映射先于SignalR注册,会误拦截SignalR的WebSocket升级请求;反之同理,导致其中一方无法正常建立连接。
  • 线程阻塞问题:如果HandleClientMessagesAsync方法存在同步阻塞逻辑(比如未正确使用异步读写、死循环未释放线程),会耗尽Kestrel的请求处理线程,进而导致SignalR的通信也被阻塞。

解决方案

1. 调整路由与中间件注册顺序

确保SignalR的路由映射优先于原生WebSocket的路由注册,让SignalR先处理自身的WebSocket请求,剩余请求再由原生WebSocket处理:

// 先注册SignalR的Hub路由
app.MapHub<YourHub>("/hub");
// 再注册原生WebSocket相关配置
app.UseWebSockets();
app.Map("/ws", WebSocketHandler.HandleWebSocketConnection);

2. 修复消息处理方法的异步逻辑

确保HandleClientMessagesAsync采用非阻塞异步实现,避免占用请求线程:

private static async Task HandleClientMessagesAsync(WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    WebSocketReceiveResult result;
    do
    {
        // 异步读取消息,不阻塞线程
        result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
        
        if (result.MessageType == WebSocketMessageType.Text)
        {
            var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
            // 处理消息时同样使用异步逻辑
            await ProcessReceivedMessageAsync(webSocket, message);
        }
        else if (result.MessageType == WebSocketMessageType.Close)
        {
            await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
        }
    } while (!result.CloseStatus.HasValue);
}

3. 为原生WebSocket配置独立路径与选项

给原生WebSocket指定明确路径,并配置独立的WebSocket选项,避免与SignalR默认配置冲突:

var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2),
    // 根据实际需求配置跨域规则
    AllowedOrigins.Add("*")
};
// 仅对/wspath启用该WebSocket配置
app.UseWebSockets("/ws", webSocketOptions);
app.Map("/ws", WebSocketHandler.HandleWebSocketConnection);

内容的提问来源于stack exchange,提问作者Vector

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 21:07:15