2010-07-25 3 views
2

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

Предположим, у нас есть 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)!

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

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

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

С уважением

ответ

5

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

  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

+2

Все методы чтения IOHandler получают свои данные только из InputBuffer. Метод ReadBytes(), который другие методы вызывают внутренне, гарантирует, что InputBuffer имеет достаточное количество байтов, доступных в нем для каждой операции чтения. Если InputBuffer уже имеет 4 байта, то ReadStream() получает их как есть. Если InputBuffer еще не имеет 4 байта, ReadBytes() делает это, то ReadStream() получает их из InputBuffer впоследствии. В любом случае все данные поступают из сокета в InputBuffer по каждому методу чтения по мере необходимости. –