【问题标题】:TIdTCPServer OnExecute fires multiple timesTIdTCPServer OnExecute 多次触发
【发布时间】:2021-12-12 03:01:20
【问题描述】:

我有一个应用程序从另一个应用程序接收 TCP 套接字连接,处理通过套接字流式传输的内容,根据接收到的数据构建请求,使用 TIdHTTP 将请求发送到外部服务器,处理响应,并将 ack 发送回 tcp 客户端。

procedure TMyClass.TCPExecute(AContext: TIdContext);
var
  s: AnsiString;
begin
  s := TransferTCPBytesToString(AContext.Connection.IOHandler);
  // test string for terminating characters
  if Pos(#28#13, s) > 0 then
  begin
  //********PROBLEM: THIS WILL BE CALLED MULTIPLE TIMES 
  //********WITH THE SAME COMPLETE DATA STREAM CONTENT!
    BuildAndSendExternalRequest(s);
    AContext.Connection.IOHandler.InputBuffer.Clear;
    // send the response back to tcp client
    WriteTCPResponse(AContext.Connection.IOHandler);
  end;
end;

function TMyClass.TransferTCPBytesToString(AnIOHandler: TIdIOHandler): AnsiString;
begin
  if AnIOHandler.InputBufferIsEmpty then
    Exit;
  // read message from tcp client connection
  AnIOHandler.ReadBytes(FTCPBytes, AnIOHandler.InputBuffer.Size, True);
  SetLength(Result, Length(FTCPBytes));
  // copy message bytes to string
  Move(FTCPBytes[0], Result[1], Length(FTCPBytes));
end;

procedure TMyClass.WriteTCPResponse(AnIOHandler: TIdIOHandler);
var
  bytes: TBytes;
begin
  // FTCPBytes contains data from tcp client
  // bytes will contain processed response from external server
  BuildACK(FTCPBytes,bytes);
  // FTCPBytes now contains ACK data
  AnIOHandler.WriteDirect(FTCPBytes, Length(FTCPBytes));
end;

请求/响应周期成功完成;发送回 tcp 客户端请求者的响应是正确的,进程按预期执行和完成。

但是,由于 OnExecute 事件会使用相同的数据多次触发,因此会接收、构建、发送、确认两个请求等。

在查看 tcp 客户端应用程序上的日志时,日志仅显示正在发送的一个请求。不知道这里发生了什么。但我需要确保请求/响应周期是 1-1,而不是 1-many!

谢谢。

【问题讨论】:

    标签: delphi indy


    【解决方案1】:

    OnExecute 事件多次触发 ...

    它应该是,因为它是一个 循环 事件。只要连接处于活动状态,循环就会运行。 OnExecute 事件处理程序中的代码负责决定每次循环迭代会发生什么。

    理想情况是事件处理程序从客户端接收到 1 条消息,将 1 个响应发送回客户端,然后退出,以便下一次循环迭代准备好处理下一个请求/响应对。

    根据您的描述,您正在尝试这样做,但显示的代码处理得不是很好。最值得注意的是,该代码没有考虑 TCP 的字节流性质。它不会区分一个传入消息与另一个消息。它只是将整个IOHandler.InputBuffer 内容原样转储到AnsiString 中(为什么是AnsiString?),如果AnsiString 恰好包含分隔符#28#13,那么您正在处理整个AnsiString 和然后将其丢弃,丢失分隔符之后收到的任何内容(即下一条消息的一部分)。如果AnsiString 不包含分隔符,那么您仍然会丢弃AnsiString,从而在分隔符到达之前丢失当前收到的所有数据。

    如果你所有的消息都被#28#13分隔,那么我建议你使用IOHandler.ReadLn()IOHandler.WaitFor()的方法来正确阅读每条消息。

    此外,您在输入和输出中重复使用相同的FTCPBytes 成员,这本身就是错误的,但如果您有多个客户端连接,您也没有为其提供任何并发访问保护。

    话虽如此,请尝试更多类似的东西:

    procedure TMyClass.TCPConnect(AContext: TIdContext);
    begin
      AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
    end;
    
    procedure TMyClass.TCPExecute(AContext: TIdContext);
    var
      s: String;
      response: TIdBytes:
    begin
      s := TransferTCPBytesToString(AContext.Connection.IOHandler);
      BuildAndSendExternalRequest(s, response);
      // send the response back to tcp client
      WriteTCPResponse(AContext.Connection.IOHandler, response);
    end;
    
    function TMyClass.TransferTCPBytesToString(AnIOHandler: TIdIOHandler): String;
    begin
      // read message from tcp client connection
      Result := AnIOHandler.ReadLn(#28#13);
      or:
      Result := AnIOHandler.WaitFor(#28#13);
    end;
    
    procedure TMyClass.BuildAndSendExternalRequest(const Msg: String; var Response: TIdBytes);
    begin
      // send Msg to external server as needed...
      // store response bytes in Response...
    end;
    
    procedure TMyClass.WriteTCPResponse(AnIOHandler: TIdIOHandler; const Response: TIdBytes);
    var
      bytes: TIdBytes;
    begin
      // Response contains processed response from external server
      BuildACK(bytes, Response);
      // bytes now contains ACK data
      AnIOHandler.Write(bytes);
    end;
    

    ...使用相同的数据

    鉴于您显示的代码,这是不可能的。在TransferTCPBytesToString() 中对IOHandler.ReadBytes() 的调用正在读取并丢弃来自IOHandler.InputBuffer 的所有字节,因此当稍后再次调用TransferTCPBytesToString() 时,相同的字节不可能仍然存在于该缓冲区中。因此,您可以一遍又一遍地看到相同数据的唯一方法是客户端一开始就一遍又一遍地发送相同的数据。

    【讨论】:

    • 优秀的解释/澄清(像往常一样!)。谢谢!
    • 一些事情,因为我没有提供太多信息。流数据约为 64k,因此尝试执行 readln() 将导致超出最大行长度错误;调用 waitfor 永远不会结束。一次只有 1 个客户端同步连接,因此无需阻塞 FTCPBytes 变量。唯一的客户端连接、发送、等待 ACK、断开连接。所以 FTCPBytes 变量中的任何剩余数据都应该能够在 OnDisconnect 事件中清除,对吗?
    • @DeCoder "流数据约为 64k,因此尝试执行 readln() 将导致超出最大行长度错误" - 默认值 行长度为 16K,但您可以使用IOHandler.MaxLineLength 属性或ReadLn() 本身的可选AMaxLineLength 参数来更改它。 IOHandler.WaitFor() 没有长度限制。 “调用 waitfor 永远不会完成” - 唯一可能发生的方法是永远不会收到指定的分隔符。这也会影响IOHandler.ReadLn()。是否每条消息都以相同的分隔符终止?
    • @DeCoder "唯一的客户端连接、发送、等待 ACK、断开连接。所以 FTCPBytes 变量中的任何剩余数据都应该能够在 OnDisconnect 事件中清除,对吗?"- 我想是这样,但我建议完全消除 FTCPBytes。正如我的示例所示,您并不需要它。
    • 所有消息都以#28#13为后缀。这使我们能够识别应用程序之间的完整消息。另外,此 URL:ww2.indyproject.org/Sockets/Docs/index.EN.aspx 不再有效。在哪里可以下载 Indy 的最新文档?
    猜你喜欢
    • 1970-01-01
    • 2014-01-26
    • 1970-01-01
    • 1970-01-01
    • 2018-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多