Что может быть причиной сбоя IdTCPServer перед чтением всех данных события OnExecute? - PullRequest
0 голосов
/ 19 октября 2019

Этот код работает нормально, когда я отправляю данные через локальную сеть с клиентским компонентом Indy, но когда я получаю данные из внешнего приложения из Интернета, он вызывает сбой. Может ли что-то на стороне клиента вызывать отключение IdTCPServer до того, как все данные будут прочитаны? В среднем клиент отправляет в среднем 33 000 символов. Любые предложения?

procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
  strm: TMemoryStream;
  RxBuf: TIdBytes;
begin
  Memo1.Clear;
  strm := TMemoryStream.Create;
  try
    // read until disconnected
    AContext.Connection.IOHandler.ReadStream(strm, -1, true);
    strm.Position := 0;
    ReadTIdBytesFromStream(strm, RxBuf, strm.Size);
  finally
    strm.Free;
  end;
  Memo1.Lines.Add(BytesToString(RxBuf));
  AContext.Connection.IOHandler.WriteLn('000');
end;

Я также попробовал этот другой код, в этом случае, в отличие от первого кода, он читает только часть отправляемых данных. Есть ли способ заставить обработчик IdTCPServer ждать, пока все данные не будут собраны?

procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
  RxBuf: TIdBytes;
begin
  RxBuf := nil;
  with AContext.Connection.IOHandler do
  begin
    CheckForDataOnSource(10);
    if not InputBufferIsEmpty then
    begin
      InputBuffer.ExtractToBytes(RxBuf);
    end;
  end;
  AContext.Connection.IOHandler.WriteLn('000');
  Memo1.Lines.Add( BytesToString(RxBuf) );
end;


Ответы [ 2 ]

0 голосов
/ 24 октября 2019

Этот код, который вы отправили как ответ, неверен.

Во-первых, вы не можете использовать BytesToString() для произвольных байтовых блоков, которые не будут корректно обрабатывать многобайтовые кодировки, такие как UTF-8. .

Кроме того, вы неправильно ищете терминатор EOT. Нет гарантии, что это будет последний байт RxBuf после каждого чтения, если клиент отправляет несколько сообщений XML. И даже если бы это было так, использование Copy(BytesToString(), ...) для извлечения его в string никогда не приведет к пустой строке, как ожидает ваш код.

Если клиент отправляет терминатор EOT в концеXML, нет необходимости в ручном цикле чтения. Просто вызовите TIdIOHandler.ReadLn() с помощью терминатора EOT и позвольте ему обрабатывать циклы чтения внутри, пока не будет достигнут EOT.

Кроме того, вызовы CoInitialize() и CoUninitialize() должны выполняться в OnConnect иСобытия OnDisconnect соответственно (на самом деле их лучше вызывать в потомке TIdThreadWithTask, назначенном свойству TIdSchedulerOfThread.ThreadClass, но это уже более сложная тема в другой раз).

Попробуйте что-то похожееэто:

procedure TFrmMain.IdTCPServer1Connect(AContext: TIdContext);
begin
  CoInitialize(nil);
  AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;

procedure TFrmMain.IdTCPServer1Disconnect(AContext: TIdContext);
begin
  CoUninitialize();
end;

procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
  XML: string;
begin
  cdsSurescripts.Close;
  XML := AContext.Connection.IOHandler.ReadLn(#4);
  Display('CLIENT', XML);
  AContext.Connection.IOHandler.WriteLn('000');
end;

Лично я бы выбрал другой подход. Я бы предложил использовать синтаксический анализатор XML, который поддерживает push-модель. Затем вы можете прочитать произвольные блоки байтов из соединения и вставить их в анализатор, позволяя ему запускать события для завершенных элементов XML, пока не будет достигнут терминатор. Таким образом, вам не нужно тратить время и память на буферизацию всего XML в памяти, прежде чем вы сможете затем обработать его.

0 голосов
/ 22 октября 2019

Для дальнейшего обращения к кому-либо, мне пришлось создать цикл и дождаться отправки клиентом EOT chr (4), чтобы собрать все данные на IdTCPServer1Execute. Это происходит потому, что данные фрагментированы Indy, код выглядит примерно так:

procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
  Len: Integer;
  Loop: Boolean;
begin
  CoInitialize(nil);
  cdsSurescripts.Close;
  Loop := True;
  while Loop = true do
  begin
    if AContext.Connection.IOHandler.Readable then
    begin
      AContext.Connection.IOHandler.ReadBytes( RxBuf,-1, True);
      Len := Length(BytesToString(RxBuf));
      if Copy(BytesToString(RxBuf), Len, 1) =  '' then
      begin
        loop := False;
      end;
    end;
  end;
  Display('CLIENT', BytesToString(RxBuf));
  AContext.Connection.IOHandler.WriteLn('000');
  CoUninitialize();
end;
...