从Linux通过Telnet发送Ctrl-C后为何停止接收数据?
问题分析与解决
问题现象
你编写的C# TCP回显程序,功能是读取字节并返回十六进制文本表示:
using System.Net; using System.Net.Sockets; using System.Text; var listener = new TcpListener(IPAddress.Any, 5050); listener.Start(); var client = listener.AcceptTcpClient(); var stream = client.GetStream(); int b; while ((b = stream.ReadByte()) != -1) { var str = $"{(byte)b:x2} "; Console.Write(str); stream.Write(Encoding.ASCII.GetBytes(str)); }
- 用Windows原生telnet连接时,输入内容(包括Ctrl-C以
03发送)都能正常回显十六进制。 - 用WSL2 Ubuntu或MSYS2的telnet连接时,按下Ctrl-C后:
- 程序控制台输出
ff f4 ff fd 06 - 后续输入的内容仅在程序控制台显示,telnet客户端收不到回显数据
- 程序控制台输出
原因
WSL2/MSYS2的telnet客户端默认启用了Telnet协议选项协商,而Windows原生telnet默认是raw模式(不进行协商)。按下Ctrl-C时,WSL的telnet会发送Telnet协议的中断协商序列:
FF(IAC,Interpret As Command):标识后续字节为Telnet命令F4(IP,Interrupt Process):请求中断远程进程FF FD 06:DO 06,请求启用TIMING MARK选项
你的程序直接把这些协商字节当成普通数据处理并回显,但telnet客户端发送协商序列后会等待服务器的响应(比如WONT或WILL),如果没收到预期响应,客户端会进入“等待协商确认”状态,不再处理后续回显数据。
解决方案
方案1:让WSL/MSYS2的telnet使用raw模式
连接时添加-r参数,强制telnet以raw模式运行,不发送Telnet协商序列,Ctrl-C会直接发送03字节:
telnet -r localhost 5050
这样就能和Windows原生telnet表现一致。
方案2:修改C#程序处理Telnet协商字节
如果需要兼容带协商的telnet客户端,可在程序中识别并处理Telnet命令序列,避免把协商字节当成普通数据回显,同时回复客户端的协商请求。示例修改如下:
using System.Net; using System.Net.Sockets; using System.Text; var listener = new TcpListener(IPAddress.Any, 5050); listener.Start(); var client = listener.AcceptTcpClient(); var stream = client.GetStream(); int b; while ((b = stream.ReadByte()) != -1) { // 处理Telnet协议命令 if (b == 0xFF) { // 识别IAC命令起始字节 var cmd = stream.ReadByte(); if (cmd == -1) break; // 处理DO/DONT/WILL/WONT类选项协商命令,统一回复WONT拒绝 if (cmd is 0xFB or 0xFC or 0xFD or 0xFE) { var opt = stream.ReadByte(); if (opt == -1) break; // 发送WONT响应,告知客户端拒绝该选项 stream.WriteByte(0xFF); stream.WriteByte(0xFC); // WONT命令码 stream.WriteByte((byte)opt); } // 跳过IP等中断类命令,不回显 continue; } // 处理普通数据,回显十六进制格式 var str = $"{(byte)b:x2} "; Console.Write(str); stream.Write(Encoding.ASCII.GetBytes(str)); }
该修改会识别Telnet的IAC命令,对选项协商回复WONT拒绝,跳过中断命令,让telnet客户端完成协商流程,后续输入内容即可正常回显。
内容的提问来源于stack exchange,提问作者Alexey
相关产品推荐
相关产品推荐

