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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 11:35:19