使用 TIdTCPServer/TIdTCPClient 个组件在 Delphi 中丢失数据

Data loss in Delphi using TIdTCPServer/TIdTCPClient components

我正在使用 Delphi 中的 TIdTCPClient/TIdTcpServer indy 组件编写客户端-服务器应用程序。

数据传输通常工作正常,但我经常从服务器读取错误数据;我得到以前请求的答案,而不是当前请求的答案。

在调试期间,两个应用程序都在本地运行,因此在传输过程中数据不会丢失。

超时为 1000-3000 毫秒,这足以避免在收到第一个请求的答复之前发送第二个请求。

我使用简单的数据格式:前4个字节是数据包长度,其余是该长度的二进制数据。

服务器端代码是(为仅发送字符串而简化;我也以相同的方式使用二进制缓冲区,但这段代码更易于理解和检查):

Var
  lng: LongInt;
  ib: TIdBytes;
begin
  // Prepare data to send:
  lng:=length(s);// s is an AnsiString to be sent
  SetLength(ib,lng+4);
  Move(lng,ib[0],4);
  Move(s[1],ib[4],length(s));
  // Send:
  AContext.Connection.IOHandler.WriteDirect(ib);
end;

发送请求的客户端代码相同(在最后一行调用TIdTcpClient.IOHandler.WriteDirect())。 读取服务器答案的客户端代码是:

Var 
  ib: TIdBytes;
  size,done,lng: LongInt;
begin
  Result:=false;
  //  answer length:
  try
    SetLength(ib,0);
    tcp.IOHandler.ReadBytes(ib,4,false);
    Move(ib[0],size,4);
    if length(ib)<0 then Exit;// wrong data
  except
    on E: Exception do;// code skipped
  end;
  //  read answer body:
  done:=0;
  b.Clear;// b is my buffer, TStream descendant
  while done<size do
    begin
    lng:=Min(size-done,MaxBlockSize);
    // read:
    SetLength(ib,0);// to be sure
    tcp.IOHandler.ReadBytes(ib,lng,false);
    if length(ib)=0 then Exit;// error reading
    // append my buffer:
    b.Wr(ib[0],length(ib));
    // progress:
    Inc(done,length(ib));
    end;
end;

数据交换顺序为:

  1. 客户端向服务器发送请求,

  2. 服务器读取请求并将应答发送回客户端,

  3. 客户阅读答案。

第 3 步出现错误数据。

也许我做错了什么?

我已经尝试在向服务器发送请求以清除传入缓冲区之前使用 ReadBytes(),但这也无济于事,因为我已经尝试了很多其他事情...

现在我完全没有想法:(

您的 I/O 逻辑比需要的复杂得多,尤其是在客户端。您正在手动执行 Indy 可以自动为您执行的操作。

在客户端,由于您将数据保存到 TStream 中,您可以让 Indy 直接将数据读取到 TStream 中:

begin
  ...
  b.Clear;// b is my buffer, TStream descendant
  // ReadStream() can read a '<length><bytes>' formatted
  // message.  When its ASize parameter is -1 and its
  // AReadUntilDisconnect parameter is False, it reads
  // the first 4 or 8 bytes (depending on the LargeStream
  // property) and interprets them as the byte count,
  // in network byte order...
  tcp.IOHandler.RecvBufferSize := MaxBlockSize;
  tcp.IOHandler.LargeStream := False; // read 4-byte length
  // read answer:
  try
    tcp.IOHandler.ReadStream(b, -1, false);
  except
    on E: Exception do begin
      // the socket is now in an indeterminate state.
      // You do not know where the reading left off.
      // The only sensible thing to do is disconnect
      // and reconnect...
      tcp.Disconnect;
      ...
    end;
  end;
  ...
end;

在服务器端,您可以通过两种不同的方式发送与上述代码兼容的消息:

var
  lng: LongInt;
  ib: TIdBytes;
begin
  // Prepare data to send:
  // s is an AnsiString to be sent
  lng := Length(s);
  SetLength(ib, lng);
  Move(PAnsiChar(s)^, PByte(ib)^, lng);
  // Send:
  AContext.Connection.IOHandler.Write(lng); // send 4-byte length, in network byte order
  AContext.Connection.IOHandler.Write(ib); // send bytes
end;

或者:

var
  strm: TIdMemoryBufferStream;
begin
  // Prepare data to send:
  // s is an AnsiString to be sent
  strm := TIdMemoryBufferStream.Create(PAnsiChar(s), Length(s));
  try
    // Send:
    // Write(TStream) can send a '<length><bytes>' formatted
    // message.  When its ASize parameter is 0, it sends the
    // entire stream, and when its AWriteByteCount parameter
    // is True, it first sends the byte count as 4 or 8 bytes
    // (depending on the LargeStream property), in network
    // byte order...
    AContext.Connection.IOHandler.LargeStream := False; // send 4-byte lengtb
    AContext.Connection.IOHandler.Write(strm, 0, True);
  finally
    strm.Free;
  end;
end;

如您所见,此代码发送的消息类型与您最初发送的消息类型相同,只是管理消息的代码发生了变化。此外,它强制以 网络字节顺序 发送消息字节数,而您却以 主机字节顺序 发送它。为保持一致性和多平台兼容性,应尽可能始终以网络字节顺序发送多字节整数。