C# Socket实现Telnet通信:握手后无法持续读取服务器响应
解决Telnet持续读写与欢迎消息接收问题
看起来你已经啃下了Telnet握手这块硬骨头,剩下的持续读写和欢迎消息问题其实只要调整下Socket的生命周期,再处理好Telnet的控制序列就能解决。我给你一步步拆解:
1. 为什么收不到欢迎消息?
你当前的代码在握手过程中只打印了原始字节,但Telnet服务器的欢迎消息是和协商用的控制字节(以0xFF开头的序列)混在一起的。你需要先过滤掉这些控制序列,才能提取出纯文本的欢迎消息。另外,握手完成后服务器才会发送欢迎消息,而你之前的Socket是局部变量,握手完成后方法结束就被释放了,根本没机会读取后续的消息。
2. 重构Socket连接逻辑(保留连接)
首先把Socket改成类的成员变量,这样整个类实例都能复用这个连接,而不是握手完就丢掉:
class TelnetClient { // 把Socket升级为成员变量,维持长连接 private Socket _telnetSocket; private const int BufferSize = 1024; private readonly byte[] _readBuffer = new byte[BufferSize]; public bool ConnectToServer(string ipAddress, int port = 23) { try { var ip = IPAddress.Parse(ipAddress); var endpoint = new IPEndPoint(ip, port); _telnetSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _telnetSocket.Connect(endpoint); Console.WriteLine("Connected to {0} successfully!", ipAddress); // 执行你已经调试好的握手流程 PerformTelnetHandshake(); // 握手完成后立刻读取欢迎消息 var welcomeMsg = ReadServerResponse(); if (!string.IsNullOrWhiteSpace(welcomeMsg)) { Console.WriteLine("Server Welcome: {0}", welcomeMsg); } return true; } catch (Exception ex) { Console.WriteLine("Failed to connect to {0}: {1}", ipAddress, ex.Message); return false; } } private void PerformTelnetHandshake() { byte[] tempBuffer = new byte[BufferSize]; int bytesReceived; // 第一次接收服务器协商请求 bytesReceived = _telnetSocket.Receive(tempBuffer); DumpRawBytes(tempBuffer, bytesReceived); _telnetSocket.Send(new byte[] { 0xFF, 0xFC, 0x18, 0xFF, 0xFC, 0x20, 0xFF, 0xFC, 0x23, 0xFF, 0xFC, 0x27, 0xFF, 0xFC, 0x24 }); bytesReceived = _telnetSocket.Receive(tempBuffer); DumpRawBytes(tempBuffer, bytesReceived); _telnetSocket.Send(new byte[] { 0xFF, 0xFE, 0x03, 0xFF, 0xFC, 0x01, 0xFF, 0xFC, 0x22, 0xFF, 0xFC, 0x1F, 0xFF, 0xFE, 0x05, 0xFF, 0XFC, 0x21 }); bytesReceived = _telnetSocket.Receive(tempBuffer); DumpRawBytes(tempBuffer, bytesReceived); _telnetSocket.Send(new byte[] { 0xFF, 0xFE, 0x01, 0xFF, 0xFC, 0x06, 0xFF, 0xFC, 0x00 }); bytesReceived = _telnetSocket.Receive(tempBuffer); DumpRawBytes(tempBuffer, bytesReceived); _telnetSocket.Send(new byte[] { 0xFF, 0xFE, 0x03, 0xFF, 0xFE, 0x01 }); bytesReceived = _telnetSocket.Receive(tempBuffer); DumpRawBytes(tempBuffer, bytesReceived); } // 保留你原来的字节打印方法,方便调试 private void DumpRawBytes(byte[] buffer, int count) { var hexString = BitConverter.ToString(buffer, 0, count).Replace("-", " "); var asciiString = Encoding.ASCII.GetString(buffer, 0, count); Console.WriteLine("{0} | {1}", hexString, asciiString); } }
3. 实现响应读取逻辑(过滤Telnet控制序列)
Telnet的控制序列都是以0xFF(IAC,Interpret As Command)开头的,我们需要在读取时跳过这些序列,只保留纯文本内容。下面的方法会持续读取直到遇到\r\n(Telnet常用的行结束标记):
public string ReadServerResponse() { var responseBuilder = new StringBuilder(); bool processingIacSequence = false; while (true) { var bytesRead = _telnetSocket.Receive(_readBuffer); if (bytesRead == 0) { // 服务器关闭了连接 break; } for (int i = 0; i < bytesRead; i++) { var currentByte = _readBuffer[i]; if (processingIacSequence) { // 跳过IAC序列的后续字节:如果是WILL/WONT/DONT/DO(0xFB-0xFE),还要多跳一个选项字节 processingIacSequence = false; if (currentByte >= 0xFB && currentByte <= 0xFE) { i++; // 跳过选项字节 } continue; } if (currentByte == 0xFF) { // 遇到IAC标记,开始处理控制序列 processingIacSequence = true; continue; } // 普通文本字节,添加到响应 responseBuilder.Append((char)currentByte); // 检查是否到达行尾标记 if (responseBuilder.Length >= 2 && responseBuilder[responseBuilder.Length - 2] == '\r' && responseBuilder[responseBuilder.Length - 1] == '\n') { // 返回去掉换行符的纯响应文本 return responseBuilder.ToString().TrimEnd('\r', '\n'); } } } return responseBuilder.ToString(); }
4. 发送XML中的命令并获取响应
现在你可以在外部调用这个类,读取XML命令并和服务器交互:
// 示例:读取XML中的命令并执行 var xmlDoc = new XmlDocument(); xmlDoc.Load("telnetCommands.xml"); var commandNodes = xmlDoc.SelectNodes("//command"); var telnetClient = new TelnetClient(); if (telnetClient.ConnectToServer("192.168.10.33")) { foreach (XmlNode commandNode in commandNodes) { var commandText = commandNode.Attributes["commandText"].Value; Console.WriteLine("Sending command: {0}", commandText); // 发送命令,记得加上Telnet要求的换行符\r\n var commandBytes = Encoding.ASCII.GetBytes($"{commandText}\r\n"); telnetClient._telnetSocket.Send(commandBytes); // 读取服务器响应 var serverResponse = telnetClient.ReadServerResponse(); Console.WriteLine("Server Response: {0}", serverResponse); // 把响应传递给你应用内的其他方法 YourApplicationMethod(serverResponse); } }
几个关键注意点
- 异步读取优化:如果你的应用是GUI或者需要高并发,建议改用
Socket.ReceiveAsync(async/await模式),避免同步读取阻塞主线程。 - 连接状态检查:每次读写前记得检查
_telnetSocket.Connected,如果连接断开要重新连接。 - 异常处理:在读写过程中要捕获
SocketException,处理超时、连接中断等异常情况。
这样调整后,你应该能正常收到服务器的欢迎消息,并且可以持续发送命令并获取响应了——核心就是保留Socket连接,正确过滤Telnet的控制序列,提取出有用的文本内容。
内容的提问来源于stack exchange,提问作者Majickal
相关产品推荐
相关产品推荐

