【问题标题】:How do I properly close a persistent System.Net.WebSockets.ClientWebSocket connection?如何正确关闭持久性 System.Net.WebSockets.ClientWebSocket 连接?
【发布时间】:2019-11-03 18:41:36
【问题描述】:

我正在使用System.Net.WebSockets.ClientWebSocket 创建一个 C# WebSocket 客户端来连接到我不拥有源代码的服务器。到目前为止一切正常,但我希望“正确”断开我的客户。这是我的源代码的缩写版本:

public class Client : IDisposable
{
    private ClientWebSocket socket;
    private string endpoint;
    private Task receiveTask;

    public Client(string endpoint)
    {
        this.endpoint = endpoint;
        this.socket = new ClientWebSocket();
    }

    public async Task Initialize()
    {
        byte[] contentBuffer = Encoding.UTF8.GetBytes("notify message");

        // Connect to the server, and send a message to notify
        // it of the client's availability to receive data.
        await OpenConnection();
        await socket.SendAsync(new ArraySegment<byte>(contentBuffer), WebSocketMessageType.Text, true, CancellationToken.None);
    }

    private async Task OpenConnection()
    {
        if (socket.State != WebSocketState.Open)
        {
            await socket.ConnectAsync(new Uri(endpoint), CancellationToken.None);
            receiveTask = Task.Run(async () => await Receive());
        }
    }

    private async Task Receive()
    {
        while (socket.State == WebSocketState.Open)
        {
            byte[] buffer = new byte[1024];
            var result = await m_sessionSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);

            if (result.MessageType == WebSocketMessageType.Close)
            {
                break;
            }
            else
            {
                using (var stream = new MemoryStream())
                {
                    stream.Write(buffer, 0, result.Count);
                    while (!result.EndOfMessage)
                    {
                        result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                        stream.Write(buffer, 0, result.Count);
                    }

                    stream.Seek(0, SeekOrigin.Begin);
                    using (var reader = new StreamReader(stream, Encoding.UTF8))
                    {
                        string message = reader.ReadToEnd();
                        // Do stuff with received message
                    }
                }
            }
        }
    }

    public void Dispose()
    {
        // NOTE: This is a gross oversimplification. Assume in the
        // actual project that a proper implementation of the Dispose
        // pattern has been created.
        if (socket.State == WebSocketState.Open)
        {
            // How do I notify the server of disconnection?
            // The below has some sort of race condition, whereby
            // it hangs the client.
            Task.Run(async () => await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None)).Wait();
        }

        if (receiveTask != null)
        {
            receiveTask.Dispose();
        }

        socket.Dispose()
    }
}

现在是我面临的挑战。在完成之前,我无法处理运行数据接收的Task。该任务被阻止等待从服务器接收。尝试从 Dispose 内部调用 CloseAsync 似乎会遇到某种死锁,因此这可能不是正确的方法。

显然,我可以使用CancellationTokenSourceCancellationToken 传递给Receive 方法,然后将其传递给WebSocket 的ReceiveAsync 方法。如果我这样做并告诉令牌源标记令牌,它将释放块,并且我可以在接收方法中检查令牌中的 IsCancellationRequested 标志,并在看到时退出。但是,如果我这样做,它会引发异常。我真的需要在我的 Dispose 方法中捕获异常(糟糕!)只是为了取消任务吗?另外,如果我取消任务,是否会正确通知服务器断开连接?还是我一开始就做错了,让长期运行(非等待)Task 运行 ReceiveAsync 方法?

【问题讨论】:

    标签: c# websocket task-parallel-library


    【解决方案1】:

    在这种情况下,您不应使用 IDisposable。优雅地关闭 websocket 连接涉及异步 I/O。它需要一个干净的“关闭握手”,即发送方发送一个 CLOSE 帧而接收方发回一个 CLOSE 帧。只有这样,连接才会转换到干净的套接字关闭(使用 FIN 而不是 RST)。

    如果您需要“IDisposable”语义,您可以查看最新的 .NET Core 中引入的新 IAsyncDisposable 模式。但总的来说,使用同步 Dispose() 模式并不是一个好主意。

    【讨论】:

    • 这是一个公平的观点,我对同步/异步 API 的差异并不无动于衷,但即使我明确尝试在 Dispose 方法之外调用 CloseAsync(或 CloseOutputAsync),看来我没有从服务器接收到关闭帧(我上面的 Receive 方法没有被关闭帧调用)。关闭而不取消会在 Receive 方法中引发 WebSocketException。如果我取消运行ReciveAsync 的现有任务,则在尝试关闭之前,套接字将转换为Aborted 状态。我该如何处理?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多