当客户端连接到Indy中的服务器时,为什么IOHandler.ReadStream会阻塞线程?

vcl*_*per 2 delphi indy delphi-2010 indy10

今天我使用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;
Run Code Online (Sandbox Code Playgroud)

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

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

如果我用ReadLn替换ReadStream,那么连接到服务器不会阻止代码执行,并且服务器会读取从客户端发送的数据.

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

问候

Rem*_*eau 6

问题在于您的代码,而不是ReadStream().它按照设计行事.

它接受3个输入参数:

procedure ReadStream(AStream: TStream; AByteCount: TIdStreamSize = -1; AReadUntilDisconnect: Boolean = False); virtual;
Run Code Online (Sandbox Code Playgroud)

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

当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 = True.当长度直接在流式传输中编码时,AByteCount = -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()是一个更好的选择.

  • 所有IOHandler的读取方法都只从InputBuffer获取数据.ReadBytes()方法(其他方法在内部调用)确保InputBuffer在每次读取操作中都有足够的可用字节.如果InputBuffer中已经有4个字节,那么ReadStream()按原样接收它们.如果InputBuffer还没有4个字节,ReadBytes()会确保它完成,然后ReadStream()从InputBuffer接收它们.无论哪种方式,所有数据都根据需要从套接字进入InputBuffer到每个读取方法. (2认同)