Почему Winsock закрывает соединение при работе с реле SR-201 из 1С и как это исправить?

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

При интеграции систем контроля и управления доступом (СКУД) или систем автоматизации с платформой 1С программисты часто сталкиваются с необходимостью прямого низкоуровневого взаимодействия с оборудованием. Одним из популярных и недорогих решений для управления физическими цепями являются двухканальные сетевые реле SR-201. Для работы с ними по сети TCP/IP из среды 1С традиционно используется COM-объект MSWinsock.Winsock.

Однако в процессе эксплуатации разработчики часто сталкиваются со следующей проблемой: соединение успешно устанавливается, первая команда уходит без проблем, но после некоторого периода бездействия (обычно около 1 минуты) сокет автоматически переходит в состояние 8 (sckClosing), сигнализируя о том, что удаленный узел закрывает подключение. При этом в программном коде 1С никаких инструкций на закрытие соединения не передавалось.

В этой статье мы подробно разберем, почему возникает такое поведение, проанализируем аппаратную специфику устройства SR-201 и рассмотрим три практических сценария решения этой проблемы в зависимости от требований вашего проекта.

Выясняем причину: почему сокет падает в состояние sckClosing (8)

Для начала проанализируем базовый код инициализации сокета и отправки данных, который часто используется в 1С:


ВинSock = Новый COMОбъект("MSWinsock.Winsock");
ВинSock.Connect("172.16.0.193", "6722");
// Проверяем состояние сокета. Состояние 7 означает sckConnected (Подключен)
Сообщить(ВинSock.State); 

Если ВинSock.State = 7 Тогда 
    ВинSock.SendData("12"); // Отправка команды включения реле
КонецЕсли;

Этот код отлично отрабатывает при первом запуске, но спустя 15–60 секунд простоя следующая попытка отправить данные вызывает ошибку, так как свойство ВинSock.State возвращает значение 8 (клиент или сервер закрывает подключение). Многие разработчики ошибочно полагают, что виной этому являются настройки операционной системы Windows (например, таймауты TCP-соединений) или особенности работы самой библиотеки MSWinsock.

Истинная причина кроется в аппаратных ограничениях платы SR-201.

Плата реле SR-201 спроектирована на базе микроконтроллера семейства STC и сетевого чипа ENC28J60. Из-за крайне ограниченного объема оперативной памяти микроконтроллер физически может поддерживать не более 6 одновременных TCP-подключений. Если бы устройство позволяло держать соединения открытыми бесконечно долго, то случайные зависшие сессии со стороны клиентов быстро заняли бы все доступные слоты, полностью заблокировав управление устройством.

Чтобы избежать этой ситуации, разработчики прошивки реле внедрили защитный механизм (таймаут неактивности). Если по открытому TCP-соединению на порт 6722 в течение определенного времени (в зависимости от версии прошивки — от 15 до 60 секунд) не поступает никаких управляющих команд, микропрограмма реле принудительно инициирует процедуру закрытия сокета. Именно это действие фиксирует Winsock на стороне 1С, переводя объект в состояние 8.

Попытки решить проблему через редактирование системного реестра Windows (например, изменением параметров KeepAliveTime и KeepAliveInterval по пути HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters) не принесут результата, так как закрытие происходит на прикладном уровне самого микроконтроллера реле, а не на уровне сетевого стека вашей ОС.


Вариант 1. Архитектура "Подключение по запросу" (On-demand) — Рекомендуемый способ

Поскольку ресурсы платы реле ограничены, наиболее правильным архитектурным решением является установление соединения только в момент необходимости совершения действия с последующим явным его закрытием. Это гарантирует, что слоты подключений на устройстве всегда будут свободны для других сервисов или административных утилит.

Рассмотрим по шагам, как реализовать этот алгоритм в 1С:

  1. Создаем и инициализируем объект MSWinsock.Winsock.
  2. Вызываем метод Connect() и ожидаем перехода сокета в состояние 7 (sckConnected). Для этого можно использовать небольшой цикл ожидания с ограничением по времени (таймаут подключения).
  3. Отправляем управляющую команду при помощи метода SendData().
  4. Дожидаемся (при необходимости) ответа от устройства.
  5. Явно закрываем соединение методом Close(), освобождая сокет на устройстве.

Посмотрим на пример реализации этой логики в виде процедуры 1С:


Процедура ОтправитьКомандуНаРеле(IPАдрес, Порт, Команда) Экспорт
    
    ВинSock = Новый COMОбъект("MSWinsock.Winsock");
    ВинSock.Connect(IPАдрес, Порт);
    
    // Ожидаем подключения не более 3 секунд
    СекундаСтарта = ТекущаяДата();
    Пока (ВинSock.State <> 7) И (ВинSock.State <> 9) И (ТекущаяДата() - СекундаСтарта < 3) Цикл
        // Небольшая пауза, чтобы не перегружать процессор
        #Если Клиент Тогда
            Предупреждение("Ожидание подключения...", 0.1);
        #Иначе
            // Для серверного кода можно использовать системную паузу через Ping или Sleep
        #КонецЕсли
    КонецЦикла;
    
    Если ВинSock.State = 7 Тогда
        // Соединение успешно установлено, отправляем команду
        ВинSock.SendData(Команда);
        
        // Даем микросекунду на завершение отправки пакета
        СекундаОтправки = ТекущаяДата();
        Пока (ТекущаяДата() - СекундаОтправки < 1) Цикл
            // Пауза
        КонецЦикла;
        
        // Явно закрываем соединение за собой!
        ВинSock.Close();
        Сообщить("Команда успешно отправлена, соединение закрыто.");
    Иначе
        // Если подключиться не удалось, сбрасываем состояние сокета
        ВинSock.Close();
        ВызватьИсключение "Не удалось подключиться к реле SR-201 по адресу " + IPАдрес;
    КонецЕсли;
    
КонецПроцедуры

Вариант 2. Поддержание постоянной активности (Application-level Keep-Alive)

Если в вашей системе критически важно удерживать сессию постоянно открытой (например, для мгновенной отправки команд без микрозадержек на установку TCP-рукопожатия), вам потребуется реализовать механизм опроса активности на уровне приложения.

Для этого необходимо с периодичностью, которая заведомо меньше таймаута отключения реле (например, каждые 10 секунд), отправлять в сокет "пустые" или сервисные запросы. Для платы SR-201 идеальным решением будет отправка команды запроса состояния каналов — строки "00" или "0R" (пустая операция). Эта команда не изменяет физическое состояние реле, но заставляет плату вернуть текущий статус выходов, что сбрасывает внутренний таймер простоя устройства.

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


// Переменная модуля для хранения активного соединения
Перем ГлобальныйСокет Экспорт;

Процедура ИнициализироватьПодключениекРеле() Экспорт
    ГлобальныйСокет = Новый COMОбъект("MSWinsock.Winsock");
    ГлобальныйСокет.Connect("172.16.0.193", "6722");
    
    // Подключаем регулярный опрос устройства для предотвращения разрыва связи
    ПодключитьОбработчикОжидания("ПоддерживатьСоединениеСРеле", 10);
КонецПроцедуры

Процедура ПоддерживатьСоединениеСРеле() Экспорт
    Если ГлобальныйСокет = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Если сокет закрылся или перешел в состояние ошибки, переподключаемся
    Если (ГлобальныйСокет.State = 8) Или (ГлобальныйСокет.State = 9) Или (ГлобальныйСокет.State = 0) Тогда
        Сообщить("Соединение потеряно. Выполняем переподключение...");
        ГлобальныйСокет.Close();
        ГлобальныйСокет.Connect("172.16.0.193", "6722");
    ИначеЕсли ГлобальныйСокет.State = 7 Тогда
        // Отправляем сервисную команду опроса состояния для сброса таймаута реле
        ГлобальныйСокет.SendData("00"); 
    КонецЕсли;
КонецПроцедуры

Данный подход позволяет держать сокет постоянно "прогретым", однако требует постоянного контроля состояния подключения и дополнительно нагружает сеть непрерывным служебным трафиком.


Вариант 3. Переход на протокол UDP (Альтернативный и самый надежный способ)

Если специфика вашей СКУД не требует обязательного подтверждения получения команды от устройства (например, вы просто отправляете команду на открытие замка двери и вам не нужно программно считывать ответный статус), наилучшим решением будет полный отказ от протокола TCP в пользу протокола UDP.

По умолчанию плата SR-201 слушает UDP-пакеты на порту 6723. Протокол UDP работает без установления постоянного соединения (connectionless). Это дает колоссальные преимущества:

Для отправки команд по UDP через Winsock нам необходимо настроить объект соответствующим образом перед отправкой. Рассмотрим пример:


Процедура ОтправитьКомандуПоUDP(IPАдрес, ПортUDP, Команда) Экспорт
    
    СокетUDP = Новый COMОбъект("MSWinsock.Winsock");
    
    // Переводим сокет в режим работы по протоколу UDP (sckUDPProtocol = 1)
    СокетUDP.Protocol = 1; 
    
    // Задаем параметры удаленного узла
    СокетUDP.RemoteHost = IPАдрес;
    СокетUDP.RemotePort = ПортUDP; // Для SR-201 по умолчанию порт 6723
    
    // В режиме UDP метод Connect выполнять не нужно. Сразу отправляем пакет!
    Попытка
        СокетUDP.SendData(Команда);
        Сообщить("Команда " + Команда + " успешно отправлена по UDP на " + IPАдрес);
    Исключение
        Сообщить("Ошибка отправки UDP пакета: " + ОписаниеОшибки());
    КонецПопытки;
    
    // Закрываем локальный сокет для освобождения системных ресурсов ОС
    СокетUDP.Close();
    
КонецПроцедуры

Важное примечание: Помните, что протокол UDP не гарантирует доставку пакетов и не возвращает статус выполнения команды обратно в 1С. Если для вашей бизнес-логики критически важно знать, сработало реле или нет, используйте Вариант 1 (TCP On-demand).

← На главную