如何在Indy IdTCPServer的OnExecute方法中处理未知格式与长度的客户端连接
问题背景
服务端现有代码逻辑为:
procedure TFServer.IdTCPServerExecute(AContext: TIdContext); begin ... size:=AContext.Connection.IOHandler.ReadLongInt; Context.Connection.IOHandler.ReadStream(msgStream, size); ... end;
该逻辑仅适配客户端用IdTCPClient.IOHandler.Write(MemoryStream1,0,True)发送的二进制流数据(此方法会先发送流长度,再发送内容)。但如果客户端发送IdTCPClient.IOHandler.WriteLn(string_message)这类不符合协议的文本数据,服务端读取的前4字节会被解析成一个异常的size值,后续ReadStream会尝试读取远超实际的数据量,直接导致崩溃。即使改用ReadStream(msgStream,-1)也无法解决,因为这种情况下会持续读取直到连接断开,同样会耗尽内存。
核心应对思路
TCP是无边界的字节流协议,服务端必须通过严格的协议校验+异常防护来处理非法输入,不能假设客户端会遵守协议。
具体实现方案
给所有IO操作加异常捕获
Indy的IO方法在遇到数据异常、连接中断、超时等情况时都会抛出异常,必须用try...except包裹,避免崩溃:procedure TFServer.IdTCPServerExecute(AContext: TIdContext); var size: Integer; msgStream: TMemoryStream; begin msgStream := TMemoryStream.Create; try try // 设置读取超时,防止恶意客户端占着连接不发数据 AContext.Connection.IOHandler.ReadTimeout := 5000; // 先读取长度 size := AContext.Connection.IOHandler.ReadLongInt; // 校验长度合理性:禁止负数,同时限制最大可处理的字节数(比如10MB) if (size < 0) or (size > 10 * 1024 * 1024) then begin AContext.Connection.Disconnect; Exit; end; // 按指定长度读取流,第三个参数设为False(因为我们已经自己处理了长度) AContext.Connection.IOHandler.ReadStream(msgStream, size, False); // 正常处理数据的逻辑... except on E: Exception do begin // 记录错误日志,直接断开异常连接 // LogError('客户端异常: ' + E.Message); AContext.Connection.Disconnect; end; end; finally msgStream.Free; end; end;添加协议校验位
在协议头部增加固定的魔法标识(比如4字节的$DEADBEEF),服务端先读取该标识,不匹配则直接断开连接,过滤完全不符合协议的请求:// 服务端先读取魔法头 magic := AContext.Connection.IOHandler.ReadLongInt; if magic <> $DEADBEEF then begin AContext.Connection.Disconnect; Exit; end; // 再读取长度和流数据...如果需要同时兼容不同类型的请求,可以再加一个类型标识(比如1字节),服务端根据类型选择对应的读取逻辑(比如0=二进制流,1=文本行)。
限制资源占用
- 设置合理的
ReadTimeout,避免恶意客户端长期占用连接。 - 严格限制单个请求的最大数据长度,防止内存被耗尽。
- 设置合理的
正确使用ReadStream参数
ReadStream(msgStream, -1)会持续读取直到连接关闭,这在恶意攻击场景下非常危险,绝对不能在无防护的情况下使用。必须结合协议约定的长度来控制读取量,或者配合超时机制使用。
内容的提问来源于stack exchange,提问作者R.Schirru

