今天,我使用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 = -1
和AReadUtilDisconnect = 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()
是更好的选择。