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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:48:41