You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WinForm TCP Socket聊天程序Disconnect方法异常处理咨询

C# TCP Socket断开连接的正确实现方案

问题描述

我是网络编程新手,正在用C#开发WinForm TCP Socket聊天程序,其他功能正常,但处理断开连接时遇到异常。

客户端断开按钮调用的代码:

public async void Disconnect()
{
    tokenSource.Cancel(); // 取消等待消息的异步循环
    await sock.DisconnectAsync(true); // 此处抛出异常
    sock.Close();
    streamReader.Close();
    streamWriter.Close();
}

服务器收到0字节数据包后调用的移除客户端代码:

public async void RemoveClient(ClientHandler client)
{
    client.tokenSource.Cancel(); // 取消等待消息的异步循环
    await client.sock.DisconnectAsync(false);
    client.sock.Close();
    client.streamReader.Close();
    client.streamWriter.Close();
    clients.Remove(client);
}

客户端抛出异常:system.net.sockets.socketexception: disconnect A request to send or receive data was not allowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was specified.

试过的方案均无效:

  • 客户端和服务器均将DisconnectAsync替换为Disconnect方法
  • 服务器将DisconnectAsync替换为Shutdown方法
  • 服务器移除DisconnectAsync,直接关闭Socket

错误原因分析

核心问题是Socket状态不一致:当服务器收到客户端的FIN包(表现为0字节数据)后,已经完成了断开操作,此时客户端再调用DisconnectAsync(true)时,Socket已处于断开状态,必然抛出异常。另外,DisconnectAsync多用于Socket重用的特殊场景,普通TCP断开无需使用,遵循四次挥手的标准流程才是可靠方案。


正确的断开连接实现

客户端主动断开流程

客户端主动断开时,应先通知服务器停止接收自己的发送数据,等待服务器确认后再清理资源:

public async Task Disconnect()
{
    // 1. 取消异步接收循环
    tokenSource.Cancel();

    try
    {
        // 2. 关闭发送流,向服务器发送FIN包
        sock.Shutdown(SocketShutdown.Send);
        
        // 3. 等待服务器的断开确认(接收0字节表示完成)
        byte[] buffer = new byte[1024];
        while (await sock.ReceiveAsync(buffer, SocketFlags.None) > 0)
        {
            // 忽略剩余未处理数据
        }
    }
    catch (SocketException)
    {
        // 断开过程中接收抛出异常属于正常情况,直接捕获即可
    }
    finally
    {
        // 4. 确保所有资源被清理
        sock.Close();
        streamReader?.Close();
        streamWriter?.Close();
    }
}

服务器端处理客户端断开流程

服务器检测到客户端断开(收到0字节或Socket异常)时,按标准流程完成断开并清理:

public async Task RemoveClient(ClientHandler client)
{
    // 1. 取消该客户端的异步接收循环
    client.tokenSource.Cancel();

    try
    {
        // 2. 关闭发送流,向客户端发送FIN包
        client.sock.Shutdown(SocketShutdown.Send);
        
        // 3. 接收客户端剩余数据(可选,确保四次挥手完整)
        byte[] buffer = new byte[1024];
        while (await client.sock.ReceiveAsync(buffer, SocketFlags.None) > 0)
        {
            // 忽略剩余数据
        }
    }
    catch (SocketException)
    {
        // 捕获断开时的异常,无需额外处理
    }
    finally
    {
        // 4. 清理资源并移除客户端
        client.sock.Close();
        client.streamReader?.Close();
        client.streamWriter?.Close();
        clients.Remove(client);
    }
}

关键注意事项

  • 优先用Shutdown而非Disconnect:Shutdown(SocketShutdown.Send)会触发TCP四次挥手,确保双方都能正确感知断开状态,避免数据丢失或状态异常。
  • 异步方法用Task替代void:async void无法捕获异步异常,改成async Task可以更好地处理异常,防止程序崩溃。
  • 资源清理放在finally块:无论断开过程中是否出现异常,都要确保Socket和流资源被正确关闭。
  • 避免滥用DisconnectAsync:该方法仅适用于需要快速重用Socket的场景,普通聊天程序不需要使用。

内容的提问来源于stack exchange,提问作者ST K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 08:52:56