【问题标题】:ASP.NET close connection before sending all websocket data to clientASP.NET 在将所有 websocket 数据发送到客户端之前关闭连接
【发布时间】:2014-08-08 05:21:17
【问题描述】:

我编写了一个简单的 asp.net websocket 处理程序作为远程数据处理服务器和客户端之间的网关。 我在本地机器(win8、IIS EXPRESS 8)上进行了测试,一切正常。但是在 azure 网站中,ASP.NET 在将所有 websocket 数据发送到客户端之前关闭连接。

以下是我的数据传输代码:

internal class WebSocketStreamTransfer{

    public WebSocketStreamTransfer(CancellationToken disconnectionToken){
        DisconnectionToken = disconnectionToken;
    }

    private CancellationToken DisconnectionToken{
        get;
        set;
    }

    public async Task AcceptWebSocketConnection(WebSocketContext context) {
        if (context == null)
            throw new ArgumentNullException("context");
        WebSocket websocket = context.WebSocket;
        if (websocket == null)
            throw new SocksOverHttpException("Null websocket");
        using(IConnection conn = ConnectionManagerFactory.ConnectionManager.CreateConnection(Guid.NewGuid().ToString())) {
            try {
                DisconnectionToken.Register(conn.Close);
                TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(null);
                await Task.WhenAny(SendDataToRemoteServer(conn, websocket, DisconnectionToken, tcs), SendDataToClient(conn, websocket, DisconnectionToken, tcs.Task));
            } catch(Exception e) {
                Logger.LogException(e);
            }
        }
    }

    internal static async Task SendDataToRemoteServer(IConnection conn, WebSocket websocket, CancellationToken cancelToken, TaskCompletionSource<bool> tcs) {
        try {
            ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[ApplicationConfiguration.GetDefaultBufferSize()]);
            while (IsConnected(conn, cancelToken, websocket)) {
                WebSocketReceiveResult result = await websocket.ReceiveAsync(buffer, cancelToken);
                if (websocket.State == WebSocketState.Open) {
                    if (result.MessageType == WebSocketMessageType.Binary) {
                        if (result.Count > 0) {
                            if (IsConnected(conn, cancelToken, websocket)) {
                                int numRead = await conn.SendData(buffer.Array, 0, result.Count, cancelToken);
                                if (numRead > 0) {
                                    tcs.TrySetResult(true); // Notify SendDataToClient can continue
                                }else{
                                    Logger.LogError("Client not send enough data for remote connection built");
                                    return;
                                }
                            } else {
                                Logger.LogInformation("SendDataToRemoteServer: Cancel send data to remote server due to connection closed");
                            }
                        } else
                            Logger.LogInformation("Receive empty binary message");
                    } else if (result.MessageType == WebSocketMessageType.Text) {
                        Logger.LogError("Receive unexpected text message");
                        return;
                    } else {
                        Logger.LogInformation("Receive close message");
                        await websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Close Connection", cancelToken);
                        return;
                    }
                } else {
                    Logger.LogInformation("SendDataToRemoteServer: WebSocket connection closed by client");
                    return;
                }
            }
        }finally{
            tcs.TrySetResult(true);
        }
    }

    internal static async Task SendDataToClient(IConnection conn, WebSocket websocket, CancellationToken cancelToken, Task connectedTask) {
        await connectedTask;
        while (IsConnected(conn, cancelToken, websocket)) {
            byte[] data = await conn.ReceiveData(cancelToken);
            if (data.Length <= 0) {
                Logger.LogInformation("SendDataToClient: Get empty data from remote server");
                return;
            }
            if (IsConnected(conn, cancelToken, websocket)) {
                await websocket.SendAsync(new ArraySegment<byte>(data), WebSocketMessageType.Binary, true, cancelToken);
            } else {
                Logger.LogInformation("SendDataToClient: Cancel send data to client due to connection closed");
            }
        }
    }

    internal static bool IsConnected(IConnection conn, CancellationToken cancelToken, WebSocket websocket) {
        bool socketConnected = websocket.State == WebSocketState.Open;
        return socketConnected && conn.Connected && !cancelToken.IsCancellationRequested;
    }
}

问题场景:

  1. SendDataToRemoteServer 正在等待客户端数据并且客户端还没有数据要发送

  2. SendDataToClient 从远程服务器接收空数据,表示远程服务器开始关闭连接。所以完成 SendDataToClient

  3. AcceptWebSocketConnection 完成,因为Task.WhenAny(SendDataToRemoteServer(conn, websocket, DisconnectionToken, tcs), SendDataToClient(conn, websocket, DisconnectionToken, tcs.Task))

  4. 期望 ASP.NET 在关闭 tcp 连接之前发送所有数据,但 ASP.NET 立即关闭连接(天蓝色)。

【问题讨论】:

  • 我也遇到了同样的问题,你找到原因了吗?提前致谢。
  • 我没有要展示的示例,但基本上这会中断循环并且客户端永远不会收到消息:while (IsConnected()) { ... await SendAsync(...).配置等待(假);休息;并且它只发生在 Azure 中。我必须在“休息”之前添加 Task.Delay(1000) 作为解决方法。
  • @Zygimantas 我从来没有解决过 Azure 网站环境中的问题。我使用github.com/Bobris/Nowin 在 docker 容器中使用 mono 运行,一切正常。
  • “期望 ASP.NET 在关闭 tcp 连接之前发送所有数据,但 ASP.NET 立即关闭连接(天蓝色)。”关闭前需要发送哪些数据?在您的描述中,所有数据都是在步骤 1-3 中发送的,而不是新数据,对吧?
  • @Vhao,所有数据意味着 ASP.NET 不仅确保所有数据发送到网络缓冲区,而且确保所有数据包都得到客户端的 ACK 响应。从应用程序日志中,所有数据都传递给了 SendDataToClient 方法,但是从 Wireshark 中,ASP.NET 在客户端收到所有数据之前关闭了 tcp 连接。

标签: asp.net azure websocket


【解决方案1】:

WebSocket 消息可以拆分为不同的帧。您没有检查消息是否已完成。从 WS 连接向其他人发送信息的代码应如下所示:

WebSocketReceiveResult result = null;
do
{
    result = await source.ReceiveAsync(buffer, CancellationToken.None);
    var sendBuffer = new ArraySegment<Byte>(buffer.Array, buffer.Offset, result.Count);

    await target.SendAsync(sendBuffer, result.MessageType, result.EndOfMessage, CancellationToken.None);
}
while (!result.EndOfMessage);

您必须检查EndOfMessage 属性,并在消息未完成时继续阅读。

它可以在您的本地计算机上运行,​​因为在本地您不会以同样的方式受到缓冲的影响,或者因为您尝试的消息更小。

【讨论】:

  • 自从SendDataToRemoteServer waiting for client data and client has not data to send yet 以来,我的代码接收没有问题。从wireshark,ASP.NET会自动帧发送4KB大小的数据。
【解决方案2】:

await Task.WhenAny 等待任何任务完成。当 SendDataToRemoteServer 任务完成时,不会等待 SendDataToClient 任务完成。

你需要改变

await Task.WhenAny(SendDataToRemoteServer(conn, websocket, DisconnectionToken, tcs), SendDataToClient(conn, websocket, DisconnectionToken, tcs.Task));

await Task.WhenAll(SendDataToRemoteServer(conn, websocket, DisconnectionToken, tcs), SendDataToClient(conn, websocket, DisconnectionToken, tcs.Task));

小样本:

async Task t1()
{
    await Task.Delay(1000);
}
async Task t2()
{
    await Task.Delay(2000);
}

Stopwatch watch = new Stopwatch();
watch.Start();
await Task.WhenAny(t1(), t2());
watch.Stop();
Debug.WriteLine("total milliseconds {0}", watch.ElapsedMilliseconds); // total milliseconds 1007


Stopwatch watch = new Stopwatch();
watch.Start();
await Task.WhenAll(t1(), t2());
watch.Stop();
Debug.WriteLine("total milliseconds {0}", watch.ElapsedMilliseconds); // total milliseconds 2009

【讨论】:

  • 这里可以使用WhenAny,因为在SendDataToRemoteServer,事件客户端将所有数据发送到服务器,客户端不会关闭tcp连接,SendDataToRemoteServer会等到tcp连接关闭
猜你喜欢
  • 2021-02-26
  • 1970-01-01
  • 1970-01-01
  • 2021-02-28
  • 1970-01-01
  • 2021-03-25
  • 1970-01-01
  • 2015-05-30
  • 1970-01-01
相关资源
最近更新 更多