2010-07-25 110 views
2

今天我遇到了一个奇怪的行为,使用Indy 10(随Delphi 2010一起发布)。这里的问题是:当客户端连接到Indy的服务器时,为什么IOHandler.ReadStream会阻塞线程?

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

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

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

问候

回答

5

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

它接受输入3个参数:

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

你只提供用于所述第一参数的值,因此另外两个参数使用默认值。

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

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

为了有效地使用ReadStream(),它需要知道什么时候停止阅读,无论是被告知有多少数据预期提前的时间(即:AByteCount >= 0),或者通过要求发送方发送其数据后断开(即:AReadUtilDisconnect = True)。当长度直接在流中编码时,AByteCount = -1AReadUtilDisconnect = False的组合是特殊情况。这主要用于(但不限于)发件人呼叫IOHandler.Write(TStream)并将其AWriteByteCount参数设置为True(默认为False)。

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

不同参数ReadStream的()的组合计算出的以下逻辑:

  1. AByteCount = -1,AReadUtilDisconnect =假:读4/8字节,解释为长度,则继续读,直到那个长度被接收。

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

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

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

  5. AByteCount> -1,AReadUtilDisconnect = True:预先设定目标TStream的大小并保持读取直到断开。

根据其种类数据的客户实际上是在向服务器发送摆在首位的,机会是ReadStream()很可能不是最好的选择用于读取数据。 IOHandler有许多不同类型的可用阅读方法。例如,如果客户端发送分隔文本(特别是在发送时使用IOHandler.WriteLn()),那么ReadLn()是更好的选择。

+0

感谢您的描述。在Indy文档中提到,如果AByteCount为-1​​且AReadUntilDisconnect为False,则字节数将从IOHandler读取为整数,但我不知道它将从接收数据的前4个或8个字节中读取。我认为当AReadUntilDisconnect为False且AByteCount为-1​​时,只要输入缓冲区中存在某些内容,ReadStream就会读取。无论如何,再次感谢您的帮助。 – vcldeveloper 2010-07-27 13:58:36

+2

所有的IOHandler的读取方法只从InputBuffer获取数据。其他方法在内部调用的ReadBytes()方法确保InputBuffer对每个读操作都有足够的可用字节。如果InputBuffer已经有4个字节,那么ReadStream()会按原样接收它们。如果InputBuffer还没有4个字节,ReadBytes()可以确保它的确如此,然后ReadStream()从InputBuffer接收它们。无论哪种方式,所有数据都会根据需要从套接字进入InputBuffer到每个读取方法。 – 2010-07-27 20:31:08

相关问题