Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

Асинхронное выполнение команд с использованием библиотеки ssh-pascal для Delphi: решение проблем с таймаутами и производительностью

Delphi , Интернет и Сети , Интернет

 

В этой статье мы рассмотрим решение проблем, связанных с асинхронным выполнением команд через SSH в приложениях Delphi, используя библиотеку ssh-pascal. Основное внимание будет уделено проблеме таймаутов и способам повышения производительности при работе с большими объемами данных.

Проблема синхронного выполнения команд

Как отмечал пользователь dummzeuch, стандартный метод ISshExec.Exec в библиотеке ssh-pascal работает синхронно, что создает две основные проблемы:

  1. Приложение должно ждать завершения выполнения команды, что может занять значительное время (до 30 минут)
  2. Возникают проблемы с таймаутами при работе с большими файлами
// Стандартный синхронный вызов
procedure TForm1.Button1Click(Sender: TObject);
var
  Output, Error: string;
  ExitCode: Integer;
begin
  FSshExec.Exec('checksum_command', Output, Error, ExitCode);
  // Код здесь не выполнится, пока команда не завершится
  Memo1.Lines.Text := Output;
end;

Решение: асинхронный подход с коллбэками

Сообщество предложило несколько решений, включая модификацию исходного кода библиотеки для поддержки асинхронного получения данных. Вот один из вариантов реализации:

type
  TOnSshExecDataRecieve = procedure(const AData: TBytes; ADataLen: Int64) of object;

  ISshExec = interface
    ['{CA97A730-667A-4800-AF5D-77D5A4DDB192}']
    procedure SetBufferSize(Size: Int64);
    procedure Cancel;
    procedure SetOnStdErrReceive(ACallback: TOnSshExecDataRecieve);
    procedure SetOnStdOutReceive(ACallback: TOnSshExecDataRecieve);
    procedure Exec(const Command: string; var Output, ErrOutput: string; 
      var ExitCode: Integer);
    property BufferSize: Int64 write SetBufferSize;
  end;

Полная реализация асинхронного обработчика

Вот как может выглядеть полная реализация асинхронного обработчика команд:

procedure TSshExec.Exec(const Command: string; var Output, ErrOutput: string;
  var ExitCode: Integer);
var
  Channel: PLIBSSH2_CHANNEL;
  ReadBuffer, OutBuffer, ErrBuffer: TBytes;
  StdStream, ErrStream: TBytesStream;
  TimeVal: TTimeVal;
  ReadFds: TFdSet;
  BytesRead: ssize_t;
  ReturnCode: Integer;
  OldBlocking: Boolean;
begin
  if FSession.SessionState <> session_Authorized then
    raise ESshError.CreateRes(@Err_SessionAuth);

  FCancelled := False;
  Channel := libssh2_channel_open_session(FSession.Addr);
  if Channel = nil then
    CheckLibSsh2Result(libssh2_session_last_errno(FSession.Addr), FSession,
      'libssh2_channel_open_session');

  TimeVal.tv_sec := 1;
  TimeVal.tv_usec := 0;

  StdStream := TBytesStream.Create(OutBuffer);
  ErrStream := TBytesStream.Create(ErrBuffer);
  SetLength(ReadBuffer, FBufferSize);
  OldBlocking := FSession.Blocking;
  FSession.Blocking := False;

  try
    repeat
      ReturnCode := libssh2_channel_exec(Channel,
        M.AsAnsi(Command, FSession.CodePage).ToPointer);
      CheckLibSsh2Result(ReturnCode, FSession, 'libssh2_channel_exec');
    until ReturnCode <> LIBSSH2_ERROR_EAGAIN;

    while not FCancelled do
    begin
      repeat
        FD_ZERO(ReadFds);
        _FD_SET(FSession.Socket, ReadFds);
        ReturnCode := select(0, @ReadFds, nil, nil, @TimeVal);
        if ReturnCode < 0 then CheckSocketResult(WSAGetLastError, 'select');
        if libssh2_channel_eof(Channel) = 1 then Break;
      until (ReturnCode > 0) or FCancelled;

      try
        BytesRead := libssh2_channel_read(Channel, PAnsiChar(ReadBuffer), FBufferSize);
        CheckLibSsh2Result(BytesRead, FSession, 'libssh2_channel_read_ex');
        if BytesRead > 0 then 
        begin
          StdStream.WriteBuffer(ReadBuffer, BytesRead);
          DoOnStdOutReceive(ReadBuffer, BytesRead);
        end;

        BytesRead := libssh2_channel_read_stderr(Channel, PAnsiChar(ReadBuffer), FBufferSize);
        CheckLibSsh2Result(BytesRead, FSession, 'libssh2_channel_read_ex');
        if BytesRead > 0 then 
        begin
          ErrStream.WriteBuffer(ReadBuffer, BytesRead);
          DoOnStdOutReceive(ReadBuffer, BytesRead);
        end;
      except
        on E: Exception do
        begin
          OutputDebugString(PChar(E.Message));
          Break;
        end;
      end;

      if BytesRead = 0 then Break;
    end;

    Output := AnsiToUnicode(PAnsiChar(StdStream.Memory), StdStream.Size, FSession.CodePage);
    ErrOutput := AnsiToUnicode(PAnsiChar(ErrStream.Memory), ErrStream.Size, FSession.CodePage);
    libssh2_channel_close(Channel);

    if FCancelled then
      ExitCode := 130
    else
      ExitCode := libssh2_channel_get_exit_status(Channel);
  finally
    StdStream.Free;
    ErrStream.Free;
    libssh2_channel_free(Channel);
    FSession.Blocking := OldBlocking;
  end;
end;

Альтернативное решение с использованием событий

pyscripter предложил альтернативный подход с использованием событий для обработки выходных данных:

type
  TExecOutput = (eoStdout, eoStdErr);

  TOnSshExecOutput = procedure(OutputType: TExecOutput; const Data: TBytes) of object;

  ISshExec = interface
    procedure SetOnOutput(ACallback: TOnSshExecOutput);
    procedure Exec(const Command: string; var Output, ErrOutput: string; 
      var ExitCode: Integer);
  end;

Реализация обработчика событий:

procedure TForm1.SshExecOutput(OutputType: TExecOutput; const Data: TBytes);
var
  Text: string;
begin
  Text := TEncoding.UTF8.GetString(Data);
  case OutputType of
    eoStdout: Memo1.Lines.Add(Text);
    eoStdErr: Memo2.Lines.Add(Text);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Output, Error: string;
  ExitCode: Integer;
begin
  FSshExec.SetOnOutput(SshExecOutput);
  FSshExec.Exec('checksum_command', Output, Error, ExitCode);
  // Данные будут появляться в Memo1 и Memo2 по мере поступления
end;

Рекомендации по настройке производительности

  1. Размер буфера: Увеличьте размер буфера для уменьшения количества операций ввода-вывода  
    FSshExec.BufferSize := 65536; // 64KB

  2. Таймауты: Настройте таймауты для предотвращения преждевременного разрыва соединения  
    TimeVal.tv_sec := 5; // 5 секунд между проверками TimeVal.tv_usec := 0;

  3. Потоковая обработка: Обрабатывайте данные по мере поступления, не накапливая их в памяти

Заключение

Представленные решения позволяют эффективно решить проблемы с асинхронным выполнением команд через SSH в Delphi-приложениях. Выбор между коллбэками и событиями зависит от конкретных требований вашего проекта. Оба подхода обеспечивают:

  • Отсутствие блокировки основного потока приложения
  • Возможность обработки данных по мере их поступления
  • Устойчивость к таймаутам при работе с большими файлами
  • Гибкость в настройке производительности

Для максимальной эффективности рекомендуется использовать последнюю версию библиотеки ssh-pascal и учитывать предложенные рекомендации по настройке производительности.

Создано по материалам из источника по ссылке.

Использование библиотеки ssh-pascal в Delphi для асинхронного выполнения SSH-команд с обработкой данных в реальном времени и настройкой производительности.


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.


:: Главная :: Интернет ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 20:14:06
2025-06-04 06:44:06/0.0060079097747803/0