ASP.NET核心websocket管理器 - 始终处置websockets

时间:2018-04-01 23:17:41

标签: c# asp.net-core websocket

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

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

websocket manager(singleton):

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();
    }
}

1 个答案:

答案 0 :(得分:5)

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

正确的方法是阻止侦听套接字上的传入消息。即使您不希望客户端有任何数据 - 您仍然需要这样做,因为客户端可能向您发送的消息包括一些控制消息,例如乒乓消息(heartbeat)和“close”消息,表明客户端是关闭沟通。

所以做完之后:

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

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

await _wsMgs.ReceiveAsync(guid);

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