自定义Socket封装连接Whois服务器时读取前Socket意外关闭的异常问题
问题根源分析
你的问题出在自定义SocketClient的协议逻辑和Whois协议不匹配,以及对Socket连接关闭时机的错误处理上:
- 错误的协议适配
Whois是一个简单的纯文本协议:客户端发送域名\r\n后,服务器返回对应的Whois信息,然后主动关闭连接。但你的SocketClient却在发送数据时添加了4字节的长度头部(HEADER_SIZE = 4),接收时先读取这个头部来计算消息体长度——这是你自定义的私有协议逻辑,Whois服务器完全不识别这个头部,导致:
- 服务器收到包含多余头部的错误请求后,可能直接关闭连接,触发你读取时的异常
- 即使服务器返回了内容,你先读取的4字节其实是返回文本的前4个字符,转成整数后计算出的
bodyLength会远大于实际返回的内容长度,后续读取时服务器已经关闭连接,自然抛出Tried to read from closed socket
- 对连接关闭的错误判定
Whois服务器返回完数据后主动关闭连接是正常行为,但你的ReadStream方法只要读到bytesReceived == 0就抛出异常,这把正常的连接关闭当成了错误,这也是异常的来源之一。
解决方案
针对Whois协议的特性,修改你的SocketClient和WhoisResolver逻辑:
1. 修改SocketClient适配Whois协议
去掉自定义的长度头部逻辑,改为按照纯文本/字节流的方式收发数据:
public class SocketClient : ISocketClient { private Socket _socket; public SocketClient(Socket socket) { _socket = socket; } public virtual Stream Connect(IPEndPoint endPoint) { _socket.Connect(endPoint); return new NetworkStream(_socket); } // 直接发送原始字符串,不需要添加头部 public void Send(Stream networkStream, string message, Encoding encoding) { var bodyBytes = encoding.GetBytes(message); networkStream.Write(bodyBytes, 0, bodyBytes.Length); // 确保数据立即发送(可选,但Whois建议这样做) networkStream.Flush(); } // 读取直到连接关闭,因为Whois返回完就关连接 public string Receive(Stream networkStream) { using var ms = new MemoryStream(); var buffer = new byte[1024]; int bytesRead; while ((bytesRead = networkStream.Read(buffer, 0, buffer.Length)) > 0) { ms.Write(buffer, 0, bytesRead); } // 连接正常关闭,不是错误,返回读取到的内容 return Encoding.UTF8.GetString(ms.ToArray()); } public void Disconnect(bool reuseSocket) { _socket.Disconnect(reuseSocket); _socket.Close(); } }
2. 调整WhoisResolver的异常处理
确保正确处理连接关闭的情况,同时保持现有结构:
public class WhoisResolver : IWhoisResolver { private ISocketClient _socketClient; public WhoisResolver(ISocketClient socketClient) { _socketClient = socketClient; } public string? Lookup(string fullDomainName, string whoisServerAddress, int whoisServerPort = 43) { try { var ip = Dns.GetHostAddresses(whoisServerAddress).First(); using var stream = _socketClient.Connect(new IPEndPoint(ip, whoisServerPort)); _socketClient.Send(stream, $"{fullDomainName}\r\n", Encoding.ASCII); var response = _socketClient.Receive(stream); _socketClient.Disconnect(false); return response; } catch (SocketException ex) { // 可以在这里记录日志,区分是连接错误还是正常关闭 Console.WriteLine($"Socket异常: {ex.Message}"); return null; } catch (Exception ex) { Console.WriteLine($"其他异常: {ex.Message}"); return null; } } }
3. 额外优化建议
- 给
SocketClient实现IDisposable接口,确保Socket和Stream能被正确释放,避免资源泄漏 - 使用
using语句管理SocketClient实例,比如:
using var socketClient = new SocketClient(new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)); var resolver = new WhoisResolver(socketClient); var result = resolver.Lookup($"{domainName}.{tld}", whoisServer);
这样修改后,你的代码就完全适配Whois协议的工作方式了,不会再出现随机异常。
内容的提问来源于stack exchange,提问作者Rafał Mierzejewski
相关产品推荐
相关产品推荐

