【问题标题】:ASP.NET Core websocket manager - websockets are always disposedASP.NET Core websocket 管理器 - websockets 总是被释放
【发布时间】:2018-09-11 05:05:55
【问题描述】:

我正在处理 ASP.NET Core 2.0 中的一些 websocket。我想接受传入的请求并将它们交给单例服务,以便可以轻松地从 API 控制器向它们发送消息。我的服务器根本没有读取任何内容,它只是接受连接并向它们发送消息。

每当我尝试访问套接字以发送消息时,它总是被释放 (ObjectDisposedException)。我不确定如何以不处理的方式正确地将连接从中间件传递到服务。我该怎么做?

websocket 管理器(单例):

public class WebSocketManager
{
    private readonly ConcurrentDictionary<Guid, WebSocket> _sockets;

    protected WebSocketManager()
    {
        _sockets = new ConcurrentDictionary<Guid, WebSocket>();
    }

    public Guid AddWebSocket(WebSocket socket)
    {
        var guid = Guid.NewGuid();
        _sockets.TryAdd(guid, socket);

        return guid;
    }

    public async Task SendAsync(Guid guid, string message)
    {
        if (!_sockets.TryGetValue(guid, out var socket))
            return;

        await SendMessageAsync(socket, message);
    }

    public async Task SendAllAsync(string message)
    {
        foreach (var socket in _sockets)
        {
            if (socket.Value.State == WebSocketState.Open)
                await SendMessageAsync(socket.Value, message);
            else
                await RemoveWebSocketAsync(socket.Key);
        }
    }

    private async Task SendMessageAsync(WebSocket socket, string message)
    {
        await socket.SendAsync(
            new ArraySegment<byte>(Encoding.UTF8.GetBytes(message)),
            WebSocketMessageType.Text,
            true,
            CancellationToken.None);
    }

    private async Task RemoveWebSocketAsync(Guid guid)
    {
        if (!_sockets.TryRemove(guid, out var socket))
            return;

        if (socket?.State == WebSocketState.Open)
            await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);

        socket.Dispose();
    }
}

接受连接的中间件:

public class WebSocketMiddleware
{
    private readonly RequestDelegate _next;
    private readonly WebSocketManager _wsMgr;

    public WebSocketMiddleware(
        RequestDelegate next,
        WebSocketManager wsMgr)
    {
        _next = next;
        _wsMgr = wsMgr;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.Path != "/ws")
        {
            await _next(context);
            return;
        }

        if (!context.WebSockets.IsWebSocketRequest)
        {
            context.Response.StatusCode = 400;
            await context.Response.WriteAsync("This endpoint accepts only websocket connections.");
            return;
        }

        var guid = _wsMgr.AddWebSocket(await context.WebSockets.AcceptWebSocketAsync());

        // this call succeeds
        await _wsMgr.SendAsync(guid, "test message");
    }
}

示例控制器调用:

public class WinLossController : Controller
{
    private readonly WebSocketManager _wsMgr;

    public WinLossController(WebSocketManager wsMgr)
    {
        _wsMgr = wsMgr;
    }

    [HttpPost]
    public async Task<IActionResult> Update()
    {
        await _wsMgr.SendAllAsync("test controller update");

        return NoContent();
    }
}

【问题讨论】:

    标签: c# asp.net-core websocket


    【解决方案1】:

    一旦您离开中间件的InvokeAsync - websocket 连接就会关闭,这是一个预期的流程。

    防止这种情况的正确方法是侦听套接字上的传入消息。即使您不期望来自客户端的任何数据 - 您仍然需要这样做,因为客户端发送给您的消息可能包含一些控制消息,例如 ping-pong 消息(心跳)和“关闭”消息,表明客户端是结束沟通。

    所以做了之后:

    var guid = _wsMgr.AddWebSocket(await context.WebSockets.AcceptWebSocketAsync());
    

    在您的管理器中实现一个循环,该循环至少处理“关闭”消息并等待:

    await _wsMgs.ReceiveAsync(guid);
    

    如果您需要在连接后立即向客户端发送内容 - 在等待接收循环之前执行此操作,因为此循环仅应在您完成此 websocket 后完成。

    【讨论】:

    • 完美,这是有道理的。我认为在 InvokeAsync 函数完成后 websocket 被处理了,但我不知道如何防止这种情况发生。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-18
    • 2018-12-27
    • 1970-01-01
    • 2014-01-09
    相关资源
    最近更新 更多