Как решить проблему нестабильного FTP-соединения и ошибки Access denied в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с файловым обменом через FTP-серверы разработчики часто сталкиваются с крайне неприятной ситуацией: соединение с сервером устанавливается успешно, однако при попытке скачать или отправить файл метод Получить() или Записать() случайным образом выдает ошибку «Ошибка аутентификации при доступе к ресурсу: Access denied to remote resource». Самое загадочное здесь то, что ошибка имеет «плавающий» характер — например, одна попытка из семи завершается успехом, а остальные завершаются аварийно.

В этой статье мы подробно разберем, почему возникает этот «призрачный глюк», проанализируем сетевые механизмы работы FTP-протокола и выработаем надежные практические решения для платформы 1С:Предприятие.

Выясняем причину: почему ошибка возникает случайным образом?

Для того чтобы найти верное решение, давайте сначала разберем физику процесса. Протокол FTP принципиально отличается от большинства других протоколов (например, HTTP) тем, что использует два типа каналов:

  1. Управляющий канал (Control Channel): по нему передаются команды авторизации, смены директории и запросы на передачу файлов. Этот канал работает на стандартном порту (например, 21 или заданном в настройках, как в нашем случае — 49174).
  2. Канал передачи данных (Data Channel): именно по нему передается содержимое файлов. Для каждого скачивания или получения списка файлов открывается отдельное сетевое соединение на новом порту.

Когда мы инициализируем объект FTPСоединение в режиме пассивного обмена (параметр ПассивныйРежим установлен в значение Истина), события развиваются по следующему сценарию:

  1. 1С успешно авторизуется на сервере через управляющий порт.
  2. При вызове метода Получить() 1С просит сервер открыть порт для передачи данных.
  3. FTP-сервер выделяет случайный порт из своего динамического диапазона (например, из пула 49152–65535) и сообщает его клиенту.
  4. 1С пытается подключиться к этому динамическому порту для скачивания файла.

В этот момент и кроется главная проблема: если на стороне провайдера, сетевого шлюза (NAT) или брандмауэра FTP-сервера открыта только часть портов динамического пула, то подключение к некоторым случайно выбранным портам будет блокироваться. Когда сервер выделяет «открытый» порт — скачивание проходит успешно. Когда сервер выделяет заблокированный порт — 1С незамедлительно возвращает ошибку доступа к ресурсу.

Дополнительными факторами нестабильности могут выступать:

---

Решение 1: Программный обход проблемы с помощью цикла повторения (метод «Попытка — Исключение»)

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

Давайте рассмотрим пример надежной реализации такого механизма:


&НаСервере
Процедура СкачатьФайлСПовторением()

    ПапкаДляСохранения = "C:/FTP_Downloads";
    ИмяФайлаПриемника = ПапкаДляСохранения + "/sprut.mdb";
    
    // Параметры подключения к FTP
    СерверАдрес = "109.195.10.19";
    ПортПодключения = 49174;
    Пользователь = "логин";
    Пароль = "пароль";
    ПассивныйРежим = Истина;
    
    МаксимальноеКоличествоПопыток = 10;
    ИнтервалМеждуПопытками = 3; // время в секундах
    УспешноСкачано = Ложь;
    
    Для НомерПопытки = 1 По МаксимальноеКоличествоПопыток Цикл
        
        Попытка
            // Инициализируем соединение внутри попытки для чистоты сессии
            Соединение = Новый FTPСоединение(СерверАдрес, ПортПодключения, Пользователь, Пароль, , ПассивныйРежим);
            
            // Избегаем жесткой адресации корня, используем относительный путь
            Соединение.Получить("sprut.mdb", ИмяФайлаПриемника);
            
            УспешноСкачано = Истина;
            ЗаписьЖурналаРегистрации("FTP.Скачивание", Систематичность.Информация, , , 
                "Файл успешно скачан на попытке № " + НомерПопытки);
            Прервать; // Выходим из цикла, так как файл успешно получен
            
        Исключение
            // Фиксируем ошибку в журнале для последующего анализа администратором
            ТекстОшибки = ОписаниеОшибки();
            ЗаписьЖурналаРегистрации("FTP.Ошибка", Систематичность.Предупреждение, , , 
                "Не удалось скачать файл на попытке № " + НомерПопытки + ". Причина: " + ТекстОшибки);
            
            // Если это не последняя попытка — делаем паузу перед следующим шагом
            Если НомерПопытки < МаксимальноеКоличествоПопыток Тогда
                ПриостановитьВыполнение(ИнтервалМеждуПопытками);
            КонецЕсли;
        КонецПопытки;
        
    КонецЦикла;
    
    Если Не УспешноСкачано Тогда
        ВызватьИсключение "Не удалось получить файл с FTP-сервера после " + МаксимальноеКоличествоПопыток + " попыток.";
    КонецЕсли;

КонецПроцедуры

// Вспомогательный метод для организации задержки (паузы)
Процедура ПриостановитьВыполнение(Секунды)
    #Если Сервер Тогда
        // Безопасная пауза на стороне сервера без перегрузки процессора
        Система = Новый СистемнаяИнформация;
        // Альтернативный кроссплатформенный вариант ожидания на сервере:
        // Используем фоновое задание или ping-ожидание, если платформа ниже 8.3.18
        // Для современных платформ доступен метод ожидания через сокеты или стандартную команду:
        ВыполнитьПереход = ТекущаяДата() + Секунды;
        Пока ТекущаяДата() < ВыполнитьПереход Цикл
            // Пустой цикл ожидания (для коротких интервалов до 5 секунд допустимо)
        КонецЦикла;
    #Иначе
        // На клиенте можно использовать предупреждение с таймаутом
        ПоказатьПредупреждение(, "Ожидание повторной попытки...", Секунды);
    #КонецЕсли
КонецПроцедуры

---

Решение 2: Оптимизация работы с путями и домашним каталогом

В процессе анализа проблемы выяснилось, что у пользователя ограничен доступ к родительскому каталогу, и он авторизуется сразу в папку otchet. В этом случае указание абсолютного пути /otchet/sprut.mdb в методе Получить() является грубой ошибкой.

Для FTP-сервера домашняя папка пользователя уже является логическим корнем /. Попытка обратиться к /otchet/sprut.mdb заставляет сервер искать папку otchet внутри самой папки otchet (то есть по пути /otchet/otchet/...), к которой прав гарантированно нет.

Давайте перепишем код с использованием правильного позиционирования и относительных путей:


// Настраиваем соединение без явного указания корневых слешей в методах получения
Соединение = Новый FTPСоединение("109.195.10.19", 49174, "логин", "пароль", , Истина);

// Поскольку авторизация сразу перенаправляет в нужную папку, файл лежит прямо в текущем каталоге
ИмяИсточника = "sprut.mdb"; 
ИмяПриемника = ПапкаДляСохранения + "/sprut.mdb";

Соединение.Получить(ИмяИсточника, ИмяПриемника);

Важное примечание по поводу слешей: Всегда помните, что на FTP-серверах (которые в большинстве своем работают под управлением ОС Linux/Unix) в качестве разделителя каталогов используется исключительно прямой слеш /. Обратный слеш \ допускается использовать только при формировании локального пути сохранения в операционной системе Windows.

---

Решение 3: Перевод соединения в активный режим

Если брандмауэр на стороне клиента (то есть там, где запущена 1С) настроен лояльно и имеет статический «белый» IP-адрес, имеет смысл попробовать переключить соединение в активный режим передачи данных. Для этого в конструкторе достаточно передать параметр Ложь в шестом аргументе:


// Шестой параметр отвечает за пассивный режим. Установим его в Ложь (Активный режим)
Соединение = Новый FTPСоединение("109.195.10.19", 49174, "логин", "пароль", , Ложь);

В активном режиме клиент (1С) сам открывает порт для передачи данных и сообщает его серверу, а сервер инициирует входящее подключение к клиенту. Если проблема была связана с некорректным NAT на стороне FTP-сервера, этот шаг полностью устранит сбои.

---

Решение 4: Использование внешних утилит (cURL) для абсолютной стабильности

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

Давайте посмотрим, как можно реализовать гарантированное скачивание файла через консольную утилиту curl, запустив ее прямо из кода 1С:


&НаСервере
Функция СкачатьФайлЧерезCurl() Экспорт

    ПапкаДляСохранения = "C:\FTP_Downloads";
    ИмяФайлаПриемника = ПапкаДляСохранения + "\sprut.mdb";
    
    // Формируем параметры запуска curl
    // -u задает учетные данные
    // -o указывает путь назначения для скачиваемого файла
    // --silent отключает прогресс-бар в консоли
    // --connect-timeout ограничивает время ожидания ответа
    ШаблонКоманды = "curl --connect-timeout 15 --retry 5 --silent -u ""%1:%2"" -o ""%3"" ""ftp://%4:%5/sprut.mdb""";
    
    СтрокаКоманды = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(ШаблонКоманды,
        "логин",
        "пароль",
        ИмяФайлаПриемника,
        "109.195.10.19",
        "49174"
    );
    
    // Запускаем приложение в синхронном режиме (ждем завершения работы)
    КодВозврата = Неопределено;
    ЗапуститьПриложение(СтрокаКоманды, , Истина, КодВозврата);
    
    Если КодВозврата = 0 Тогда
        ЗаписьЖурналаРегистрации("FTP.Curl", Систематичность.Информация, , , "Файл успешно скачан через curl.");
        Возврат Истина;
    Иначе
        ЗаписьЖурналаРегистрации("FTP.Curl", Систематичность.Ошибка, , , "Ошибка выполнения curl. Код возврата: " + КодВозврата);
        Возврат Ложь;
    КонецЕсли;

КонецФункции

Использование параметра --retry 5 на уровне самой утилиты curl заставляет ее автоматически повторять попытки скачивания в случае сетевых сбоев, избавляя вас от необходимости писать сложные циклы в коде 1С.

← На главную