C#多客户端消息应用断开重连后无法发送消息问题
问题分析与修复方案
核心问题定位
重连后客户端无法发送消息的根源在于服务器端的全局变量冲突、缓冲区资源共享和异常静默处理,这些问题在多客户端场景下会直接导致消息接收逻辑混乱,甚至完全失效。
具体修复步骤
1. 移除服务器端全局clientSocket与byteData变量
当前服务器使用全局的clientSocket和byteData,多客户端同时连接时会被互相覆盖,导致消息解析错误或接收逻辑中断。修改OnReceive方法,将资源改为局部或客户端绑定:
private void OnReceive(IAsyncResult ar) { try { Socket currentClientSocket = (Socket)ar.AsyncState; byte[] receiveBuffer = new byte[1024]; int bytesReceived = currentClientSocket.EndReceive(ar); // 客户端主动断开连接的情况 if (bytesReceived == 0) { HandleClientLogout(currentClientSocket); return; } // 截取有效数据解析,避免空数据干扰 byte[] validData = new byte[bytesReceived]; Array.Copy(receiveBuffer, validData, bytesReceived); Data msgReceived = new Data(validData); Data msgToSend = new Data(); byte[] message; msgToSend.cmdCommand = msgReceived.cmdCommand; msgToSend.strName = msgReceived.strName; bool flag = false; bool disconnect = false; switch (msgReceived.cmdCommand) { case Command.Login: ClientInfo clientInfo = new ClientInfo { socket = currentClientSocket, strName = msgReceived.strName }; clientList.Add(clientInfo); flag = true; msgToSend.strMessage = $"<<<{msgReceived.strName} has joined the room>>>"; break; case Command.Logout: HandleClientLogout(currentClientSocket); flag = true; msgToSend.strMessage = $"<<<{msgReceived.strName} has left the room>>>"; break; case Command.Message: if (msgReceived.strMessage == "**disconnect**") { HandleClientLogout(currentClientSocket); disconnect = true; break; } else msgToSend.strMessage = msgReceived.strMessage; flag = false; break; case Command.List: msgToSend.cmdCommand = Command.List; msgToSend.strName = null; msgToSend.strMessage = null; foreach (ClientInfo client in clientList) { msgToSend.strMessage += $"{client.strName}*"; } flag = false; message = msgToSend.ToByte(); currentClientSocket.BeginSend(message, 0, message.Length, SocketFlags.None, new AsyncCallback(OnSend), currentClientSocket); break; } if (!disconnect && msgToSend.cmdCommand != Command.List) { message = msgToSend.ToByte(); foreach (ClientInfo clientInfo in clientList) { if ((clientInfo.socket != currentClientSocket || msgToSend.cmdCommand != Command.Login)) { try { clientInfo.socket.BeginSend(message, 0, message.Length, SocketFlags.None, new AsyncCallback(OnSend), clientInfo.socket); } catch (Exception ex) { msgBox.AppendText($"发送消息到{clientInfo.strName}失败: {ex.Message}\r\n"); } } } if (flag) msgBox.AppendText($"{msgToSend.strMessage}\r\n"); else msgBox.AppendText($"{msgToSend.strName}: {msgToSend.strMessage}\r\n"); } if (!disconnect) { currentClientSocket.BeginReceive(new byte[1024], 0, 1024, SocketFlags.None, new AsyncCallback(OnReceive), currentClientSocket); } } catch (Exception ex) { msgBox.AppendText($"接收消息异常: {ex.Message}\r\n"); Socket errorSocket = (Socket)ar.AsyncState; HandleClientLogout(errorSocket); } } // 提取Logout逻辑为独立方法,复用代码 private void HandleClientLogout(Socket clientSocket) { int nIndex = 0; string userName = string.Empty; foreach (ClientInfo client in clientList) { if (client.socket == clientSocket) { userName = client.strName; clientList.RemoveAt(nIndex); break; } ++nIndex; } try { clientSocket.Shutdown(SocketShutdown.Both); clientSocket.Close(); } catch { } Data logoutMsg = new Data { cmdCommand = Command.Logout, strName = userName, strMessage = $"<<<{userName} has left the room>>>" }; byte[] msgBytes = logoutMsg.ToByte(); foreach (ClientInfo client in clientList) { try { client.socket.BeginSend(msgBytes, 0, msgBytes.Length, SocketFlags.None, new AsyncCallback(OnSend), client.socket); } catch { } } msgBox.AppendText($"{logoutMsg.strMessage}\r\n"); }
2. 修复OnAccept方法的缓冲区问题
每个新客户端连接时使用独立的接收缓冲区,避免全局资源冲突:
private void OnAccept(IAsyncResult ar) { try { Socket clientSocket = serverSocket.EndAccept(ar); serverSocket.BeginAccept(new AsyncCallback(OnAccept), null); byte[] clientBuffer = new byte[1024]; clientSocket.BeginReceive(clientBuffer, 0, clientBuffer.Length, SocketFlags.None, new AsyncCallback(OnReceive), clientSocket); } catch (Exception ex) { msgBox.AppendText($"接受客户端连接异常: {ex.Message}\r\n"); } }
3. 移除全局disconnect与flag变量
将这两个变量改为OnReceive方法内的局部变量,避免多客户端共享导致逻辑混乱。
4. 取消空catch块,添加异常日志
空catch会隐藏所有错误,添加异常信息输出到消息框,便于排查连接、接收、发送过程中的问题。
5. 客户端重连时确保strName正确初始化
在OnConnect方法中提前设置strName,避免发送消息时名称为空:
private void OnConnect(IAsyncResult ar) { try { clientSocket.EndConnect(ar); strName = usernameBox.Text.Trim(); Data msgToSend = new Data { cmdCommand = Command.Login, strName = strName, strMessage = null }; byte[] b = msgToSend.ToByte(); clientSocket.BeginSend(b, 0, b.Length, SocketFlags.None, new AsyncCallback(OnSend), null); if (clientSocket.Connected) { Data msgToSendConn = new Data { cmdCommand = Command.List, strName = strName, strMessage = null }; byteData = msgToSendConn.ToByte(); clientSocket.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(OnSend), null); byteData = new byte[1024]; clientSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(OnReceive), null); Active(true); } } catch (Exception ex) { msgBox.AppendText($"连接服务器失败: {ex.Message}\r\n"); } }
6. 客户端断开时确保Logout命令发送成功
改为异步发送,避免同步发送失败导致服务器未收到断开通知:
private void disconnBtn_Click(object sender, EventArgs e) { if (clientSocket == null || !clientSocket.Connected) return; Data msgToSend = new Data { strName = strName, strMessage = "**disconnect**", cmdCommand = Command.Message }; byte[] byteData = msgToSend.ToByte(); clientSocket.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, (ar) => { try { clientSocket.EndSend(ar); } catch { } finally { clientSocket.Shutdown(SocketShutdown.Both); clientSocket.Close(); clientSocket = null; Active(false); } }, null); }
关键注意事项
- 多客户端场景下绝对避免全局共享socket和缓冲区,每个客户端的连接、接收、发送都应使用独立资源,否则会出现数据覆盖、逻辑混乱。
- 永远不要忽略异常,空
catch是调试的大敌,至少要记录异常信息才能快速定位问题。 - 连接清理要彻底,客户端断开时服务器必须从
clientList移除对应客户端并关闭socket,避免残留无效连接影响后续逻辑。
内容的提问来源于stack exchange,提问作者Efe
相关产品推荐
相关产品推荐

