Как я могу дождаться строки с сервера с IdTCPClient?

#delphi #sockets #tcpclient #indy

#delphi #сокеты #tcpclient #indy

Вопрос:

У меня проблема с IdTelnet (indy 10.1). Я не могу прочитать данные с сервера в режиме Unicode. и теперь я хочу написать терминал telnet с IdTCPClient.

Сервер иногда отправляет одну строку, а иногда все больше и больше строк. Но между отправкой нет фиксированного времени.

Теперь моя проблема в том, что когда я должен считывать данные из InBuffer.

Или когда я должен использовать функцию ReadLn для чтения данных с сервера, сколько раз я должен запускать ReadLn?

Ответ №1:

TIdTelnet является многопоточным компонентом. У него есть внутренний поток, который непрерывно считывает данные из сокета, вызывая TIdTelnet.OnDataAvailable событие всякий раз, когда доступен буфер данных.

TIdTelnet является TIdTCPClient потомком. Посмотрите в исходном файле IdTelnet.pas, чтобы увидеть, как это реализовано. Вы можете сделать что-то подобное в своем собственном коде, вызывая TIdIOHandler.ReadLn() свой собственный поток, например:

 type
  TMyThread = class(TThread)
  private
    FConn: TIdTCPConnection;
  protected
    procedure Execute; override;
  public
    constructor Create(AConn: TIdTCPConnection); reintroduce;
  end;

constructor TMyThread.Create(AConn: TIdTCPConnection);
begin
  inherited Create(False);
  FConn := AConn;
end;

procedure TMyThread.Execute;
var
  S: String;
begin
  while not Terminated do
  begin
    S := FConn.IOHandler.ReadLn(...);
    ...
  end;
end;

var
  Thread: TMyThread = nil;

procedure TForm1.ConnectButtonClick(Sender: TObject);
begin
  IdTCPClient1.Connect;
  try
    Thread := TMyThread.Create(IdTCPClient1);
  except
    IdTCPClient1.Disconnect;
    raise;
  end;
end;

procedure TForm1.DisconnectButtonClick(Sender: TObject);
begin
  if Assigned(Thread) then Thread.Terminate;
  try
    IdTCPClient1.Disconnect;
  finally
    if Assigned(Thread) then
    begin
      Thread.WaitFor;
      FreeAndNil(Thread);
    end;
  end;
end;
  

Если вы не хотите использовать поток, вместо этого вы можете использовать таймер. Чтобы убедиться, что ваш поток таймера (например, основной поток) не заблокирован без необходимости, используйте TIdIOHandler.CheckForDataOnSource() метод с небольшим таймаутом всякий TIdIOHandler.InputBuffer раз, когда он пуст, а затем вызывайте TIdIOHandler.ReadLn() только тогда, когда данные доступны, например:

 procedure TForm1.ConnectButtonClick(Sender: TObject);
begin
  IdTCPClient1.Connect;
  ReadTimer.Enabled := True;
end;

procedure TForm1.DisconnectButtonClick(Sender: TObject);
begin
  ReadTimer.Enabled := False;
  IdTCPClient1.Disconnect;
end;

procedure TForm1.ReadTimerElapsed(Sender: TObject);
var
  S: String;
begin
  if IdTCPClient1.IOHandler.InputBufferIsEmpty then
  begin
    IdTCPClient1.IOHandler.CheckForDataOnSource(10);
    if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
  end;
  S := IdTCPClient1.IOHandler.ReadLn(...);
  ...
end;
  

Комментарии:

1. Большое вам спасибо, если я хочу использовать idTelnet, как я могу изменить кодировку буфера? в icTCPClienet я использую ReadLn с TEncoding . UTF8 в качестве аргумента, и он работает хорошо.

2. TIdTelnet предоставляет вам необработанные буферы (после декодирования последовательностей Telnet) по мере их поступления. У буферов нет кодировок, у строк есть. Вы должны скопировать необработанные данные в свой собственный буфер, а затем прочитать из него завершенные строки / строки по мере необходимости после определения того, когда завершенные строки / строки закончили буферизацию. Indy имеет несколько функций для извлечения строк / строк из буферов TIdBuffer, TStream и TidBytes. В противном случае не используйте TIdTelnet, используйте TIdTCPClient с вашим собственным потоком / таймером ReaLn(), как я вам показал.

Ответ №2:

Синхронное решение для IdTCPClient

Я искал решение, которое будет «ждать» ответа, похоже, это работает достаточно хорошо для того, что я делаю, если я играю с настройкой ReadTimeout и отключаю клиент от серверной части после записи с сервера. На основе ReadTimeout это отключится после того, как это будет достигнуто с помощью IOHandler.ReadLn();

 function sendTCPCommand(command: string): string;
var
 lines, lineInput: String;
 tcpClient : TIdTcpClient;

begin
  tcpClient := TidTcpClient.Create(nil);
  try
    with tcpClient do
    begin
      Port := 80;
      Host := 127.0.0.1;

      try
        ReadTimeOut := 5000; //Adjust according to demands, each readln event uses it
        lines := '';
        connect;
        IoHandler.WriteLn(command);
        lineInput := IoHandler.ReadLn();
        while (lineInput <> '') do
        begin
          lines := lines   lineInput;
          lineInput := IoHandler.ReadLn();
          if (lineInput = '') then
          begin
            Disconnect; //ignores timeout when finished reading or getting nothing
          end;
        end;
        Result := lines;
      except
        On E:Exception do
        begin
          Result := E.Message;
        end;
      end;
    end;
  finally
    tcpClient.Free;
  end;
end;