Почему IOHandler.ReadStream блокирует поток, когда клиент подключается к серверу в Indy? - PullRequest
2 голосов
/ 25 июля 2010

Сегодня я столкнулся со странным поведением при использовании Indy 10 (поставляется с Delphi 2010). Вот проблема:

Предположим, у нас есть 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, и поток, работающий внутри обработчика событий OnExecute, блокируется, когда выполнение достигает строки AContext.Connection.IOHandler.ReadStream (AStream)!

Когда я отслеживаю код, проблема возникает, когда внутри ReadStream вызывается ReadLongInt для получения количества байтов. ReadLongInt вызывает ReadBytes. Внутри ReadBytes FInputBuffer.Size равен нулю. Там в цикле вызывается ReadFromSource, и в конце концов выполнение достигает TIdSocketListWindows.FDSelect, который вызывает функцию «select» из WinSock2, и выполнение останавливается здесь, и от этого клиентского соединения ничего не будет получено. Я также пытался присвоить значения параметрам AByteCount и AReadUntilDisconnect, но это не изменило его поведение.

Если я заменю ReadStream на ReadLn, то подключение к серверу не блокирует выполнение кода, и данные, отправленные с клиента, читаются сервером.

Что-то не так с кодом? Или это ошибка?

Привет

1 Ответ

6 голосов
/ 27 июля 2010

Проблема в вашем коде, а не в 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 = 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 и продолжает считывать до отключения. </p>

  3. AByteCount> -1, AReadUtilDisconnect = False: предварительно измерьте целевой TStream и продолжайте чтение, пока не будет получено число байтов AByteCount.

  4. AByteCount <= -1, AReadUtilDisconnect = True: держать чтение до отключения. </p>

  5. AByteCount> -1, AReadUtilDisconnect = True: предварительно измерьте целевой TStream и продолжайте чтение до отключения.

В зависимости от типа данных, которые клиент фактически отправляет на сервер, есть вероятность, что ReadStream(), вероятно, не лучший выбор для чтения этих данных. В IOHandler доступно много разных методов чтения. Например, если клиент отправляет текст с разделителями (особенно если он отправляется с IOHandler.WriteLn()), тогда ReadLn() является лучшим выбором.

...