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

时间:2015-01-16 12:37:59

标签: delphi tcp indy loss

我正在使用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. 客户阅读答案。

  4. 步骤3中显示错误的数据。

    也许我做错了什么?

    在向服务器发送请求以清除传入缓冲区之前,我已经尝试过ReadBytes(),但这并没有帮助,就像我尝试过的许多其他事情一样......

    现在我只是出于想法:(

1 个答案:

答案 0 :(得分:2)

您的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;

正如您所看到的,此代码发送的是您最初发送的相同类型的消息,更改的是管理消息的代码。此外,它强制消息字节计数以网络字节顺序发送,而您以主机字节顺序发送它。为了保持一致性和多平台兼容性,应尽可能以网络字节顺序发送多字节整数。