如何解决ERROR: cannot read command from socket, code 10054问题
问题分析与解决方案
问题背景
对接非自研服务器实现TCP读写操作时,写入功能正常,但读取阶段服务器日志报错:ERROR: cannot read command from socket, code 10054,客户端应用程序同时挂起。使用微软官方TCP客户端示例可正常运行,修改为复用连接的版本后出现上述问题。需求是实现连接复用(避免每次命令都开闭连接),同时有两个核心疑问:
- 是否必须等命令1的响应返回后才能发送命令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
相关产品推荐
相关产品推荐

