Indy FTP, большие файлы и NAT-маршрутизаторы - PullRequest
7 голосов
/ 19 апреля 2010

Я уже несколько лет использую Indy для передачи файлов по FTP, но не смог найти удовлетворительного решения для следующей проблемы.

Когда пользователь загружает большой файл за маршрутизатором, иногда происходит следующее: файл загружен нормально, но в то же время командный канал отключается из-за истечения времени ожидания. Обычно этого не происходит при прямом подключении к серверу, поскольку сервер «знает», что происходит передача по каналу данных. Некоторые маршрутизаторы не знают об этом, и канал команд закрыт.

Многие программы периодически отправляют команду NOOP, чтобы поддерживать канал команд в активном состоянии, даже если это не является частью стандартной спецификации FTP. Мой вопрос: как мне это сделать? Отправить ли команду NOOP в событии OnWork? Причиняет ли это какой-либо побочный ущерб, например, мне нужно обработать какой-нибудь ответ? Как мне лучше всего решить эту проблему?

1 Ответ

3 голосов
/ 19 апреля 2010

Мы используем несколько подходов, чтобы справиться с этим: (1) включить TCP / IP keepalive на канале управления во время передачи и (2) корректно восстановить после разрыва соединения, (3) поддерживать возобновление прерванных передач ,

Многие FTP-клиенты будут отправлять NOOP, пока все не используется, но я не знаю, отправляют ли они их во время передачи данных, потому что в этом случае вам придется обрабатывать ответы, а многие серверы не будут отправлять их обратно до завершения передачи данных.

  1. Indy 10.5.8 (Delphi XE2) поддерживает протоколы TCP / IP для естественного хранения. Просто используйте свойство NATKeepAlive в TIdFTP.

    Для предыдущих выпусков назначьте события OnDataChannelCreate / OnDataChannelDestroy:

    const
      KeepAliveIdle = 2 * SecsPerMin;
      KeepAliveInterval = 2 * SecsPerMin;
      IOC_VENDOR = $18000000;
      SIO_KEEPALIVE_VALS = DWORD(IOC_IN or IOC_VENDOR or 4);
    
    type
      tcp_keepalive = record
        onoff: u_long;
        keepalivetime: u_long;
        keepaliveinterval: u_long;
      end;
    
    procedure TFtpConnection.DataChannelCreated(Sender: TObject;
      ADataChannel: TIdTCPConnection);
    var
      Socket: TIdSocketHandle;
      ka: tcp_keepalive;
      Bytes: DWORD;
    begin
      // Enable/disable TCP/IP keepalives.  They're very small (40-byte) packages
      // and will be sent every KeepAliveInterval seconds after the connection has
      // been idle for KeepAliveIdle seconds.  In Win9x/NT4 the idle and timeout
      // values are system wide and have to be set in the registry;  the default is
      // idle = 2 hours, interval = 1 second.
      Socket := (FIdFTP.IOHandler as TIdIOHandlerSocket).Binding;
      if Win32MajorVersion >= 5 then begin
        ka.onoff := 1;
        ka.keepalivetime := KeepAliveIdle * MSecsPerSec;
        ka.keepaliveinterval := KeepAliveInterval * MSecsPerSec;
        WSAIoctl(Socket.Handle, SIO_KEEPALIVE_VALS, @ka, SizeOf(ka), nil, 0, @Bytes,
          nil, nil)
      end
      else
        Socket.SetSockOpt(Id_SOL_SOCKET, Id_SO_KEEPALIVE, Id_SO_True)
    end;
    
    procedure TFtpConnection.DataChannelDestroy(ASender: TObject;
      ADataChannel: TIdTCPConnection);
    var
      Socket: TIdSocketHandle;
    begin
      Socket := (FIdFTP.IOHandler as TIdIOHandlerSocket).Binding;
      Socket.SetSockOpt(Id_SOL_SOCKET, Id_SO_KEEPALIVE, Id_SO_False)
    end;
    
  2. Чтобы изящно восстановиться, если файл был успешно передан, просто переподключите в конце и выполните SIZE или LIST , чтобы получить размер файла. Если они совпадают, файл был успешно передан, и вам больше ничего не нужно делать. Если сервер поддерживает его, вы также можете отправить команду XCRC , чтобы получить значение CRC, чтобы вы могли сравнить его с локальным файлом.

  3. Если вы хотите быть действительно крепким, вы также можете проверить TIdFTP.CanResume. Если он установлен, сервер поддерживает команду REST , так что вы можете начать передачу с того места, где остановились, передав true параметру AResume TIdFTP.Get/Put.

...