c# - 与 ASPNET Core 和 websockets 的双向通信

标签 c# asp.net websocket .net-core kestrel-http-server

我不知道如何实现。这是问题所在,也是我已经尝试过的。

问题

有一个消息队列,其中包含来自外部系统的消息。客户端必须打开与服务器的 websocket 连接,并在插入消息后立即从队列中接收这些消息。除此之外,客户端可以通过相同的 websocket 连接向服务器发送命令(客户端和服务器之间的通信必须尽可能低的延迟)。

工具

我更喜欢使用最低级别但仍然实用的官方库,所以选择 Microsoft.AspNetCore.WebSockets (1.0.0)。该应用程序在 Azure App Service 上作为启用了 websockets (WSS) 的 Web 应用程序运行。应用程序是netcoreapp1.1。不需要 SSE/长轮询/等回退 (SignalR)。

解决方案 #1 - 已测试

服务器应该运行一个循环来:

  1. 如果消息队列不为空,则发送任何更新。
  2. 等待传入的消息超时。

可以这样实现:

while (_request_not_aborted_) // The condition is simplified.
{
    if (_there_is_a_new_message_in_message_queue_)
    {
        // Send all available messages from the message queue.
        await SendAsync();
    }

    using (var timeoutCancellationTokenSource = new CancellationTokenSource())
    {
        var timeoutCancellationToken = timeoutCancellationTokenSource.Token;
        var receiveTask = ReceiveAsync(timeoutCancellationToken, webSocket);

        // Wait for any incoming message for 1000ms and cancel the task if
        // the client hasn't sent any yet.
        if (await Task.WhenAny(receiveTask, Task.Delay(1000, timeoutCancellationToken)) ==
            receiveTask)
        {
            var result = await receiveTask;
            // Process the message.
        }

        timeoutCancellationTokenSource.Cancel();
    }
}

解决方案 #1 - 有什么问题吗?

一旦 timeoutCancellationTokenSource 被取消,websocket 连接进入“中止”状态,客户端断开连接。

可能相关的问题:https://github.com/aspnet/WebSockets/issues/68

解决方案 #2 - 已测试

使用 SSE 将数据从服务器流式传输到客户端,并使用常规 HTTPS 请求发回消息。

解决方案 #2 - 有什么问题吗?

HTTPS 请求延迟是 Not Acceptable ,IE/Edge 尚不支持 SSE。

解决方案 #3

使用多个 websocket 连接,一个用于服务器->客户端,另一个用于客户端->服务器通信。

解决方案 #3 - 怎么了?

但这会增加断开连接错误的可能性,并且通常听起来很糟糕,因为 websocket 技术应该提供双向通信。

解决方案 #4

使用移除了超时逻辑的解决方案 #2,但每 1000 毫秒从客户端向服务器发送一次 NOOP 消息,以允许 ReceiveAsync 正常完成。

解决方案 #4 - 怎么了?

听起来像是一个糟糕的解决方法。

寻求帮助/想法

也许知道服务器如何在相对同时监听传入消息并向客户端发送消息的任何其他解决方案?

其他发现

  1. “每个 WebSocket 对象都支持并行发送和接收。” MSDN

最佳答案

写下问题有助于从另一个角度看问题,所以我用解决方案 #5 来回答我自己的问题。我还没有发现任何问题。

“每个 WebSocket 对象并行支持一个发送和一个接收。” 引起了我的注意,我决定同时运行发送和接收任务,继续重新启动已完成的任务。

循环应该是:

var sendTask = SendAsync(cancellationToken, webSocket);
var receiveTask = ReceiveAsync(cancellationToken, webSocket);

while (!cancellationToken.IsCancellationRequested)
{
    if (await Task.WhenAny(sendTask, receiveTask) == sendTask)
    {
        // The server has finished sending to the client or it had nothing to send.
        await sendTask;
        sendTask = SendAsync(cancellationToken, webSocket);

        continue;
    }

    var message = await receiveTask;
    // TODO: Process message here
    receiveTask = ReceiveAsync(cancellationToken, webSocket);
}

await webSocket.CloseAsync(
    WebSocketCloseStatus.NormalClosure,
    string.Empty,
    cancellationToken);

SendAsync() 方法应包含:

while(_there_is_a_messages_in_queue_)
{     
    var data = "message"; // Simplified. Should be taken from the message queue.

    await webSocket.SendAsync(
        new System.ArraySegment<byte>(System.Text.Encoding.UTF8.GetBytes(data)),
        WebSocketMessageType.Text,
        true,
        cancellationToken);
}

await Task.Delay(1000);

延迟任务允许:

  1. 如果从客户端发送任何数据,则 ReceiveAsync() 方法首先完成。
  2. 通过不那么积极地轮询消息队列来节省 CPU 和带宽。

我确信我可能遗漏了一些边缘案例,所以如果您将来要阅读这篇文章,请告诉我您是否会发现此解决方案有任何明显的问题。

附言所有 ConfigureAwait(false) 已被删除以提高可读性。

附言我不接受我自己的回答,以允许您添加有关任何可能解决方案的任何有用信息。

关于c# - 与 ASPNET Core 和 websockets 的双向通信,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42306135/

相关文章:

Joe 的 Erlang websocket 示例的 Python 示例

linux - 我可以在 Linux 上识别正在进行的 Web 请求吗?

c# - 为什么发送数据包没有发送任何数据(与minecraft服务器握手)?

C#:如何根据值剪切字符串?

c# - 为什么在启动 Chrome 并将 html 文件加载到 chrome 时需要花费这么多时间来加载它?

asp.net - 在 Visual Studio 中使用 CSS 的好方法

javascript - 使用 jQuery 数据表将复选框数据导出到 Excel

asp.net - 按值查找 TreeView 节点

c# - 如何使用 XAML/C# 创建透明的 'hotspot'

mysql - 在 Websocket 期间避免 MySQL 连接