3

今天,我在使用 Indy 10(随 Delphi 2010 提供)时遇到了一个奇怪的行为。这是问题所在:

假设我们的客户端中有一个 IdTcpClient,我们的服务器应用程序中有一个 IdTcpServer,并且我们的 IdTcpServer 的 OnExecute 事件处理程序中的这段代码:

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
  AStream: TStringStream;
  S: string;
begin
  AStream := TStringStream.Create;
  try
    AContext.Connection.IOHandler.ReadStream(AStream);
    S := AStream.DataString;
  finally
    AStream.Free;
  end;
end;

现在,当客户端尝试连接到服务器时,使用 TIdTcpClient.Connect;在服务器上,调用 TIdTcpServer.OnExecute,当执行到达 AContext.Connection.IOHandler.ReadStream(AStream) 行时,在 OnExecute 事件处理程序中运行的线程被阻塞!

当我跟踪代码时,问题是在 ReadStream 中调用 ReadLongInt 以获取字节数引起的。ReadLongInt 调用 ReadBytes。在 ReadBytes 中,FInputBuffer.Size 为零。在那里,在一个循环中调用 ReadFromSource,最终执行到达 TIdSocketListWindows.FDSelect,它从 WinSock2 调用“select”函数,并且执行在这里停止,并且不会从该客户端连接接收任何内容。我也尝试为 AByteCount 和 AReadUntilDisconnect 参数赋值,但它并没有改变行为。

如果我将 ReadStream 替换为 ReadLn,则连接到服务器不会阻止代码执行,并且从客户端发送的数据由服务器读取。

代码有什么问题吗?或者这是一个错误?

问候

4

1 回答 1

7

问题出在您的代码中,而不是在ReadStream(). 它按设计运行。

它接受 3 个输入参数:

procedure ReadStream(AStream: TStream; AByteCount: TIdStreamSize = -1; AReadUntilDisconnect: Boolean = False); virtual;

您只为第一个参数提供值,因此其他两个参数使用默认值。

AByteCount参数设置为 -1 且AReadUntilDisconnect参数设置为 False 时,ReadStream()旨在假设接收到的前 4 个字节(或 8 个字节,如果该IOHandler.LargeStream属性设置为 True)是正在发送的数据的长度,后面是之后的实际数据。这就是为什么ReadStream()调用ReadLongInt(). 这不仅告诉ReadStream()何时停止读取,而且还允许ReadStream()在接收数据之前预先调整目标 TStream 的大小以便更好地管理内存。

如果客户端实际上并未在其数据之前发送 4 字节(或 8 字节)长度值,则ReadStream()仍会将实际数据的开始字节解释为长度。这通常(但不总是,取决于数据)导致ReadLongInt()(或ReadInt64())返回一个大整数值,这将导致ReadStream()预期大量数据实际上永远不会到达,从而无限期地阻止读取(或直到超时)发生,如果IOHandler.ReadTimeout属性设置为非无限超时)。

为了ReadStream()有效地使用,它需要知道何时停止读取,或者通过提前被告知需要多少数据(即:)AByteCount >= 0,或者通过要求发送方在发送数据后断开连接(即:)AReadUtilDisconnect = TrueAByteCount = -1和的组合AReadUtilDisconnect = False是一种特殊情况,当长度直接在流中编码时。这主要用于(但不限于)当发送方调用IOHandler.Write(TStream)AWriteByteCount参数设置为 True(默认为 False)时。

在处理非文本数据时,尽可能在实际数据之前发送数据长度总是一个好主意。它优化了读取操作。

ReadStream() 的不同参数组合产生以下逻辑:

  1. AByteCount = -1, AReadUtilDisconnect = False:读取 4/8 个字节,解释为一个长度,然后继续读取直到收到该长度。

  2. AByteCount < -1,AReadUtilDisconnect = False:假设 AReadUntilDisconnect 为 True 并继续读取直到断开连接。

  3. AByteCount > -1, AReadUtilDisconnect = False:预先确定目标 TStream 的大小并继续读取,直到收到 AByteCount 个字节。

  4. AByteCount <= -1,AReadUtilDisconnect = True:继续读取直到断开连接。

  5. AByteCount > -1, AReadUtilDisconnect = True:预先设置目标 TStream 的大小并继续读取直到断开连接。

根据客户端实际上首先发送到服务器的数据类型,这ReadStream()可能不是读取该数据的最佳选择。IOHandler 有许多不同类型的可用读取方法。例如,如果客户端发送分隔文本(尤其是使用 发送IOHandler.WriteLn()),那么ReadLn()是更好的选择。

于 2010-07-27T06:41:37.240 回答