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

如何解决ERROR: cannot read command from socket, code 10054问题

问题分析与解决方案

问题背景

对接非自研服务器实现TCP读写操作时,写入功能正常,但读取阶段服务器日志报错:ERROR: cannot read command from socket, code 10054,客户端应用程序同时挂起。使用微软官方TCP客户端示例可正常运行,修改为复用连接的版本后出现上述问题。需求是实现连接复用(避免每次命令都开闭连接),同时有两个核心疑问:

  1. 是否必须等命令1的响应返回后才能发送命令2?
  2. TCP命令能否同时发送?若可以,如何确保响应与命令匹配?(需支持失败重试)

附修改后的C#代码

TCPConnection类

public class TCPConnection : IConnection
{
    public TCPConnection(MachineInfo machineInfo) => this.MachineInfo = machineInfo as MachineInfo;

    TcpClient TcpClient ;
    private NetworkStream Stream;
    private readonly MachineInfo MachineInfo;

    public void Connect()
    {
        Disconnect();
        TcpClient = new TcpClient();
        TcpClient.Connect(hostname: MachineInfo.IPAddress, this.MachineInfo.Port);
        Stream = TcpClient.GetStream();
    }

    public void Disconnect()
    {
        TcpClient?.Close();
        Stream?.Close();
    }

    public string ReadData(string message)
    {
        WriteData(message);
        data = new Byte[256];
        String responseData = String.Empty;
     
        Int32 bytes = Stream.Read(data, 0, data.Length);
        responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
        return responseData;
    }
    Byte[] data;
    public void WriteData(string message)
    {
        
        data = System.Text.Encoding.ASCII.GetBytes(message);
        Stream.Write(data, 0, data.Length);
    }
}

使用代码

var v= new  TCPConnection(new MachineInfo("127.0.0.1",8900));
v.Connect();

var AllCommands = new List<string>();
AllCommands.Add("cmd1");
AllCommands.Add("cmd1");
AllCommands.Add("cmd1");
AllCommands.Add("cmd1");
AllCommands.Add("cmd1");
AllCommands.Add("cmd1");
AllCommands.Add("cmd1");
AllCommands.Add("cmd1");
AllCommands.Add("cmd1");
AllCommands.Add("cmd1");
AllCommands.Add("cmd1");
AllCommands.Add("This could go up to 1000");

foreach (var item in AllCommands)
{
    var test = v.ReadData(item);
    Console.WriteLine(test);
}

v.Disconnect();
Console.ReadKey();  

问题排查与解答

1. 当前代码报错的核心原因

错误码10054表示TCP连接被远程主机强制关闭,问题根源在于命令边界识别和读取逻辑的缺陷:

  • Stream.Read()是阻塞调用,但仅读取一次256字节,若服务器响应超过256字节或分多次传输,会导致后续读取错位;
  • 未添加命令边界标识(比如结束符、长度前缀),服务器无法区分连续发送的多个命令(例如多个"cmd1"连在一起会被当成非法命令),因此返回错误并关闭连接,客户端则挂起在Read()调用上。

微软官方示例能正常运行,是因为它严格遵循了服务器的协议规范,而你的修改破坏了这个规范。

2. 核心疑问解答

疑问1:是否必须等命令1的响应返回才能发送命令2?

取决于服务器实现的上层协议:

  • 若服务器是同步请求-响应模型(如简单的串口转TCP协议、HTTP 1.0),必须等前一个响应完全接收后再发送下一个命令,否则服务器无法识别命令边界,解析失败;
  • 若服务器支持流水线(Pipeline)或异步多请求,则可以连续发送多个命令,但前提是协议本身有明确的消息区分机制。

从当前报错判断,目标服务器需要严格的请求-响应顺序,不支持无边界的连续命令发送。

疑问2:TCP命令能否同时发送?若可以,如何确保响应与命令匹配?

TCP是字节流协议,无天然消息边界,“同时发送”本质是连续写入多个命令到流中。要实现响应与命令匹配,必须依赖上层协议设计,常见方案:

  • 唯一ID标识:每个命令附带唯一ID,服务器返回响应时携带相同ID,客户端通过ID匹配响应与命令;
  • 固定长度/长度前缀:每个命令/响应固定长度,或先发送4字节int表示后续数据长度,客户端据此准确拆分消息;
  • 结束符分隔:每个命令/响应末尾添加特定结束符(如\r\n、\0),客户端读取到结束符才判定一个消息完成。

失败重试注意事项:

  • 重试前需验证连接状态(TcpClient.Connected不可靠,建议通过心跳或尝试读取验证);
  • 重试时重新发送对应命令,避免重复执行非幂等操作(如写入类命令);
  • 给读取操作添加超时,防止服务器无响应导致客户端永久挂起。

3. 代码修复建议

针对连接复用场景,修复读写逻辑以符合服务器协议:

public class TCPConnection : IConnection
{
    private TcpClient _tcpClient;
    private NetworkStream _stream;
    private readonly MachineInfo _machineInfo;
    // 假设服务器用\r\n作为消息结束符,需根据实际协议调整
    private readonly string _messageDelimiter = "\r\n";
    private readonly Encoding _encoding = Encoding.ASCII;
    private readonly byte[] _delimiterBytes;

    public TCPConnection(MachineInfo machineInfo)
    {
        _machineInfo = machineInfo;
        _delimiterBytes = _encoding.GetBytes(_messageDelimiter);
    }

    public void Connect()
    {
        Disconnect();
        _tcpClient = new TcpClient();
        // 添加连接超时
        _tcpClient.ConnectAsync(_machineInfo.IPAddress, _machineInfo.Port).Wait(TimeSpan.FromSeconds(5));
        _stream = _tcpClient.GetStream();
        // 设置读取超时
        _stream.ReadTimeout = 5000;
    }

    public void Disconnect()
    {
        _stream?.Close();
        _tcpClient?.Close();
        _tcpClient = null;
        _stream = null;
    }

    public string SendCommand(string command)
    {
        if (_stream == null || !_tcpClient.Connected)
        {
            throw new InvalidOperationException("连接未建立");
        }

        // 给命令添加结束符,确保服务器识别边界
        var commandBytes = _encoding.GetBytes(command + _messageDelimiter);
        _stream.Write(commandBytes, 0, commandBytes.Length);
        _stream.Flush();

        // 读取直到获取完整响应
        var responseBuffer = new List<byte>();
        var tempBuffer = new byte[256];
        while (true)
        {
            int bytesRead = _stream.Read(tempBuffer, 0, tempBuffer.Length);
            if (bytesRead == 0)
            {
                throw new IOException("连接被服务器关闭");
            }

            responseBuffer.AddRange(tempBuffer.Take(bytesRead));
            // 检查是否包含结束符
            var responseBytes = responseBuffer.ToArray();
            int delimiterIndex = IndexOfSequence(responseBytes, _delimiterBytes);
            if (delimiterIndex != -1)
            {
                // 提取完整响应
                var fullResponseBytes = responseBytes.Take(delimiterIndex).ToArray();
                // 保存未处理的字节(若有)
                responseBuffer.Clear();
                if (delimiterIndex + _delimiterBytes.Length < responseBytes.Length)
                {
                    responseBuffer.AddRange(responseBytes.Skip(delimiterIndex + _delimiterBytes.Length));
                }
                return _encoding.GetString(fullResponseBytes);
            }
        }
    }

    // 辅助方法:查找字节数组中的子序列
    private int IndexOfSequence(byte[] source, byte[] search)
    {
        for (int i = 0; i <= source.Length - search.Length; i++)
        {
            bool match = true;
            for (int j = 0; j < search.Length; j++)
            {
                if (source[i + j] != search[j])
                {
                    match = false;
                    break;
                }
            }
            if (match)
            {
                return i;
            }
        }
        return -1;
    }
}

修改使用代码:

var connection = new TCPConnection(new MachineInfo("127.0.0.1", 8900));
connection.Connect();

var allCommands = new List<string>
{
    "cmd1",
    "cmd1",
    // ... 其他命令
    "This could go up to 1000"
};

foreach (var cmd in allCommands)
{
    try
    {
        var response = connection.SendCommand(cmd);
        Console.WriteLine(response);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"命令执行失败:{ex.Message}");
        // 尝试重连并重试一次
        try
        {
            connection.Connect();
            var response = connection.SendCommand(cmd);
            Console.WriteLine($"重试成功:{response}");
        }
        catch (Exception retryEx)
        {
            Console.WriteLine($"重试失败:{retryEx.Message}");
        }
    }
}

connection.Disconnect();
Console.ReadKey();

关键修复点说明

  • 添加消息边界处理(以\r\n为例,需根据服务器实际协议调整),确保服务器能正确解析每个命令;
  • 读取逻辑改为持续读取直到获取完整响应,避免单次读取导致的截断;
  • 增加超时设置,防止客户端无限挂起;
  • 完善连接状态检查和重试逻辑;
  • 修正资源释放顺序(先关流再关客户端)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 07:15:35