今天,我使用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,则连接到服务器不会阻止代码执行,并且从客户端发送的数据将由服务器读取。

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

问候

最佳答案

问题出在您的代码中,而不是在ReadStream()中。它按照设计的方式工作。

它接受3个输入参数:

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


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

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

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

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

在处理非文本数据时,总是最好在实际数据之前发送数据长度。它优化了读取操作。

ReadStream()的不同参数组合适用于以下逻辑:


AByteCount = -1,AReadUtilDisconnect = False:读取4/8字节,解释为一个长度,然后继续读取直到接收到该长度。
AByteCount AByteCount> -1,AReadUtilDisconnect = False:预先调整目标TStream的大小并保持读取状态,直到收到AByteCount字节数为止。
AByteCount AByteCount> -1,AReadUtilDisconnect = True:预先调整目标TStream的大小,并保持读取直到断开连接。


首先,取决于客户端实际发送到服务器的数据类型,ReadStream()可能不是读取该数据的最佳选择。 IOHandler有多种可用的读取方法。例如,如果客户端正在发送带分隔符的文本(尤其是使用IOHandler.WriteLn()发送的文本),则ReadLn()是更好的选择。

07-24 09:26