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

TIdTCPServer的OnExecute事件携带相同数据流多次触发问题求解

问题根因
  • Indy的TIdTCPServer.OnExecute是原生循环触发的事件:只要客户端连接保持正常,该事件会被框架持续调用,这是Indy的标准设计,不属于异常。
  • 成员变量FTCPBytes存在数据残留:TransferTCPBytesToString中调用ReadBytes时第三个参数传入True,代表将新读取的字节追加到FTCPBytes末尾而非覆盖原有内容。你仅在业务处理完成后清空了Indy的输入缓冲区,从未主动清空FTCPBytes,只要该变量中曾经出现过终止符#28#13,后续每次生成的字符串s都会命中终止符判断,导致业务逻辑重复执行。
  • 多线程数据竞争风险:FTCPBytes是类成员变量,TIdTCPServer的每个连接对应独立线程,多连接并发时会出现数据互相覆盖、串扰的问题,也是重复请求的常见诱因。
  • 流处理逻辑不规范:TCP是字节流协议,当前读逻辑仅读取当前缓冲区可用内容,未处理粘包、半包场景,可能出现一个请求被拆分多次读取,或一次读取到多个请求的情况。
修复方案
  1. 移除全局共享的FTCPBytes成员变量,改用连接上下文存储每个连接的私有临时数据,避免多线程串扰。
  2. 每次读取生成完整请求后,清空临时存储的字节数据,避免残留命中终止符判断。
  3. 调整读逻辑,持续读取数据直到匹配到终止符再执行业务逻辑,避免半包、粘包问题。

修复后参考代码

// 首先定义每个连接的私有上下文数据结构
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 19:36:01