Delphi Indy TIdCmdTCPServer读取数据出现随机乱码问题排查
问题描述
我正在实现一个非常简易的 TIdCmdTCPServer 服务端,用于接收我方自研手机应用发送的数据。手机应用启动后会首先发送简单的 hello 指令,但服务端触发 OnBeforeCommandHandler 事件时收到的数据存在乱码。
AData 参数的内容格式为 '随机非ASCII字符 hello',乱码部分均为非ASCII字符。
我最初使用Delphi 2007自带的Indy 10.1.5组件开发,先后开展了如下排查工作:
- 怀疑自身程序存在内存损坏问题,新建仅包含窗体和
TIdCmdTCPServer组件的最简测试项目,问题复现。 - 怀疑Delphi 2007自带Indy组件存在Bug,切换至Delphi 10.3环境测试,问题依旧存在。
- 怀疑本机环境导致通信数据损坏,在多台物理、虚拟Windows 10设备上运行测试程序,问题仍未解决。
- 怀疑手机应用发送数据异常,改用Putty直接向服务端发送指令,仍出现乱码。
- 更换服务端监听端口测试,问题无变化。
我检索了相关问题资料,未找到同类问题记录,现有公开资料均显示 TIdCmdTCPServer 可正常稳定运行。
该问题存在特殊规律:
- 连续发送5-6次指令后,数据读取就会恢复正常
- 每次事件触发时的乱码内容均与上一次不同
- 单步调试Indy源码发现,服务端首次执行读取操作时,输入缓冲区中就已经存在乱码字符
- 不同指令需要的发送次数不同才能实现无乱码读取,例如"hello"指令通常需要发送8-9次(次数不固定)才能正常读取。
我仅通过如下代码启动服务,所有组件属性均保持默认值未做修改,不清楚是否需要为服务端单独开辟线程,Indy官方帮助文档未提及相关要求:
IdCmdTCPServer1.Active := True;
以下为最简测试应用的完整代码:
单元代码:
unit test; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, IdBaseComponent, IdComponent, IdCustomTCPServer, IdTCPServer, IdCmdTCPServer, IdCommandHandlers, IdContext, IdServerIOHandler, IdServerIOHandlerSocket, IdServerIOHandlerStack; type TForm1 = class(TForm) cmdsMain: TIdCmdTCPServer; ioshMain: TIdServerIOHandlerStack; procedure FormShow(Sender: TObject); procedure cmdsMainCommandHandlers0Command(ASender: TIdCommand); procedure cmdsMainBeforeCommandHandler(ASender: TIdCmdTCPServer; var AData: string; AContext: TIdContext); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.cmdsMainBeforeCommandHandler(ASender: TIdCmdTCPServer; var AData: string; AContext: TIdContext); begin // 此处收到的数据存在乱码 end; procedure TForm1.cmdsMainCommandHandlers0Command(ASender: TIdCommand); begin // end; procedure TForm1.FormShow(Sender: TObject); begin cmdsMain.Active := True end; end.
DFM窗体设计代码:
object Form1: TForm1 Left = 0 Top = 0 Caption = 'Form1' ClientHeight = 299 ClientWidth = 635 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'Tahoma' Font.Style = [] OldCreateOrder = False OnShow = FormShow PixelsPerInch = 96 TextHeight = 13 object cmdsMain: TIdCmdTCPServer Bindings = < item IP = '0.0.0.0' Port = 55 end> DefaultPort = 55 IOHandler = ioshMain CommandHandlers = < item CmdDelimiter = ' ' Command = 'hello' Disconnect = False Name = 'TIdCommandHandler0' NormalReply.Code = '200' ParamDelimiter = ' ' ParseParams = True Tag = 0 OnCommand = cmdsMainCommandHandlers0Command end> ExceptionReply.Code = '500' ExceptionReply.Text.Strings = ( 'Unknown Internal Error') Greeting.Code = '200' Greeting.Text.Strings = ( 'Welcome') HelpReply.Code = '100' HelpReply.Text.Strings = ( 'Help follows') MaxConnectionReply.Code = '300' MaxConnectionReply.Text.Strings = ( 'Too many connections. Try again later.') ReplyTexts = <> ReplyUnknownCommand.Code = '400' ReplyUnknownCommand.Text.Strings = ( 'Unknown Command') OnBeforeCommandHandler = cmdsMainBeforeCommandHandler Left = 312 Top = 152 end object ioshMain: TIdServerIOHandlerStack Left = 376 Top = 168 end end
问题更新:我们修改手机应用逻辑,改为向公共回声服务器tcpbin.com发送指令,回显数据完全正常;使用Putty模拟手机端向该回声服务器发送数据,同样未出现乱码,可排除客户端发送数据异常的可能。
答案
核心问题点
- 多余的手动IOHandler配置:
TIdCmdTCPServer激活时会自动创建适配的IOHandler实例,不需要手动在窗体上添加TIdServerIOHandlerStack组件。手动挂载的IOHandler工厂在Indy 10部分版本中存在初始化异常,会导致输入缓冲区读取到未初始化的内存垃圾,这是随机乱码的核心诱因。 - Telnet协商字节干扰:默认开启的服务端Greeting消息会在客户端连接后主动推送
200 Welcome响应,Putty如果使用Telnet模式连接,会误将服务端识别为Telnet服务器,自动回复0xFF开头的Telnet协商控制字节(非ASCII二进制数据),这些字节会被当作命令前缀读入形成乱码。 - 测试端口选择问题:当前使用的55端口属于系统保留特权端口(<1024),部分Windows系统的网络过滤组件会对这类端口的数据包做额外处理,可能引入异常数据。
- 命令结束符校验缺失:
TIdCmdTCPServer默认以CRLF(\r\n)作为单条命令的结束标记,如果客户端发送指令时未携带正确的换行符,会导致缓冲区数据累计错位,出现解析异常。
修复步骤
- 删掉窗体上的
ioshMain组件,清空cmdsMain.IOHandler属性,让组件自动创建IOHandler,不要手动指定IOHandler工厂。 - 更换监听端口为1024以上的高端口(如8080、12345),排除特权端口的系统过滤影响。
- Putty测试时必须选择Raw连接模式,禁止使用Telnet、SSH等其他模式,避免客户端自动发送协议协商字节。如果不需要服务端发送欢迎消息,直接将
cmdsMain.Greeting.Code设为空字符串,关闭Greeting推送。 - 确认自研手机端发送每条指令时,结尾必须追加CRLF换行符,不要只发纯指令内容。
- 不需要手动为服务端开辟额外线程,
IdCmdTCPServer1.Active := True;的启动方式完全符合Indy设计要求,组件内部会自动维护监听线程和工作线程池。
验证方式
在OnConnect事件中添加日志,读取AContext.Connection.IOHandler.InputBuffer的原始字节转16进制输出:如果输出字节以FF开头,说明是未过滤的Telnet协商字节,按上述步骤关闭Greeting、使用Raw模式连接即可解决;如果输出是无规律随机字节,确认已删除手动添加的IOHandler组件后重启程序测试。
内容的提问来源于stack exchange,提问作者alvaroc
相关产品推荐
相关产品推荐

