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
相关产品推荐
相关产品推荐

