TIdTCPServer的OnExecute事件携带相同数据流多次触发问题求解
问题根因
- Indy的
TIdTCPServer.OnExecute是原生循环触发的事件:只要客户端连接保持正常,该事件会被框架持续调用,这是Indy的标准设计,不属于异常。 - 成员变量
FTCPBytes存在数据残留:TransferTCPBytesToString中调用ReadBytes时第三个参数传入True,代表将新读取的字节追加到FTCPBytes末尾而非覆盖原有内容。你仅在业务处理完成后清空了Indy的输入缓冲区,从未主动清空FTCPBytes,只要该变量中曾经出现过终止符#28#13,后续每次生成的字符串s都会命中终止符判断,导致业务逻辑重复执行。 - 多线程数据竞争风险:
FTCPBytes是类成员变量,TIdTCPServer的每个连接对应独立线程,多连接并发时会出现数据互相覆盖、串扰的问题,也是重复请求的常见诱因。 - 流处理逻辑不规范:TCP是字节流协议,当前读逻辑仅读取当前缓冲区可用内容,未处理粘包、半包场景,可能出现一个请求被拆分多次读取,或一次读取到多个请求的情况。
修复方案
- 移除全局共享的
FTCPBytes成员变量,改用连接上下文存储每个连接的私有临时数据,避免多线程串扰。 - 每次读取生成完整请求后,清空临时存储的字节数据,避免残留命中终止符判断。
- 调整读逻辑,持续读取数据直到匹配到终止符再执行业务逻辑,避免半包、粘包问题。
修复后参考代码
// 首先定义每个连接的私有上下文数据结构 type TConnContext = class public RecvBuffer: TBytes; end; // 连接创建时初始化上下文 procedure TMyClass.TCPConnect(AContext: TIdContext); begin AContext.Data := TConnContext.Create; end; // 连接断开时释放上下文 procedure TMyClass.TCPDisconnect(AContext: TIdContext); begin if Assigned(AContext.Data) then TConnContext(AContext.Data).Free; end; procedure TMyClass.TCPExecute(AContext: TIdContext); var s: AnsiString; RecvBytes: TIdBytes; NewByteCount: Integer; ConnCtx: TConnContext; TerminatorPos: Integer; begin ConnCtx := TConnContext(AContext.Data); // 读取当前缓冲区所有可用字节 if AContext.Connection.IOHandler.InputBufferIsEmpty then Exit; NewByteCount := AContext.Connection.IOHandler.InputBuffer.Size; AContext.Connection.IOHandler.ReadBytes(RecvBytes, NewByteCount, False); // 追加到连接私有缓冲区 SetLength(ConnCtx.RecvBuffer, Length(ConnCtx.RecvBuffer) + NewByteCount); Move(RecvBytes[0], ConnCtx.RecvBuffer[Length(ConnCtx.RecvBuffer) - NewByteCount], NewByteCount); // 转成字符串查找终止符 SetLength(s, Length(ConnCtx.RecvBuffer)); Move(ConnCtx.RecvBuffer[0], s[1], Length(ConnCtx.RecvBuffer)); TerminatorPos := Pos(#28#13, s); if TerminatorPos > 0 then begin // 仅取终止符之前的内容作为请求数据 BuildAndSendExternalRequest(Copy(s, 1, TerminatorPos + 1)); // 清空当前请求数据,若有剩余字节(粘包的下一个请求)保留在缓冲区 if Length(ConnCtx.RecvBuffer) > TerminatorPos + 1 then begin // 保留剩余未处理的字节 Move(ConnCtx.RecvBuffer[TerminatorPos + 1], ConnCtx.RecvBuffer[0], Length(ConnCtx.RecvBuffer) - (TerminatorPos + 1)); SetLength(ConnCtx.RecvBuffer, Length(ConnCtx.RecvBuffer) - (TerminatorPos + 1)); end else begin SetLength(ConnCtx.RecvBuffer, 0); end; AContext.Connection.IOHandler.InputBuffer.Clear; WriteTCPResponse(AContext.Connection.IOHandler, Copy(s, 1, TerminatorPos + 1)); end; end; // 修改WriteTCPResponse,传入原始请求数据,避免依赖全局变量 procedure TMyClass.WriteTCPResponse(AnIOHandler: TIdIOHandler; const RequestData: AnsiString); var bytes: TBytes; begin BuildACK(TBytes(RequestData), bytes); // 生成ACK后直接写入,不依赖全局变量存储 AnIOHandler.WriteDirect(bytes, Length(bytes)); end;
内容的提问来源于stack exchange,提问作者DeCoder
相关产品推荐
相关产品推荐

