1С Сервер взаимодействия. Взаимодействие сервера и клиента

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

Покажу два независимых способа доставить прогресс фонового задания с сервера на клиент: через штатную систему взаимодействия 1С и через WebSocket с внешним ws-сервером. Код ниже — из реальной демо-конфигурации, её можно развернуть у себя и повторить.

Как прогресс попадает с сервера на клиент

Фоновое задание крутится на сервере в отдельном сеансе и напрямую до формы пользователя «дотянуться» не может: клиент уже не ждёт ответа синхронно. Значит, серверу нужен канал, по которому он инициативно шлёт сообщения на клиент, пока задание считает. Оба способа ниже решают ровно это, но каналы разные:

  • Система взаимодействия — платформенный механизм 1С поверх WebSocket. Фоновое задание пишет сообщения в скрытое обсуждение, клиент подписан на новые сообщения и двигает индикатор. Сторонней инфраструктуры в коде нет — нужен только зарегистрированный сервер взаимодействия.
  • Свой WebSocket — вы поднимаете внешний ws-сервер (например, на Django Channels), 1С подключается к нему Native-компонентой, а форма слушает тот же сокет через поле HTML. Больше кода и инфраструктуры, зато канал общий с веб- и мобильными клиентами.

Способ 1. Через систему взаимодействия 1С

Идея: при открытии формы создаём скрытое обсуждение с ключом, привязанным к пользователю, и подписываемся на новые сообщения в нём. Фоновое задание пишет в это же обсуждение счётчик прогресса, а обработчик на клиенте двигает индикатор.

Клиентская часть формы — создаём описания оповещений, формируем ключ обсуждения и подключаем обработчик новых сообщений:

&НаКлиенте
Процедура ПриОткрытии(Отказ)

    ПослеВыполненияПодключения = Новый ОписаниеОповещения(
        "ВыполнитьПослеОкончания", ЭтаФорма, , "ВыполнитьПриОшибке", ЭтаФорма);
    ОбработкаСообщенийСервера = Новый ОписаниеОповещения(
        "ОбработкаСообщенийСервера", ЭтаФорма);

    ИдентификаторТекущегоПользователя = СистемаВзаимодействия.ИдентификаторТекущегоПользователя();
    КлючОбсуждения = "ДемоФоновоеЗадание " + ИдентификаторТекущегоПользователя;

    СоздатьОбсуждение(КлючОбсуждения, ИдентификаторТекущегоПользователя);
    СистемаВзаимодействия.НачатьПодключениеОбработчикаНовыхСообщений(
        ПослеВыполненияПодключения, КлючОбсуждения, ОбработкаСообщенийСервера);

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

Обсуждение создаём один раз и делаем его невидимым (Отображаемое = Ложь), чтобы оно не мозолило глаза в списке чатов — это чисто транспортный канал под конкретного пользователя:

&НаСервереБезКонтекста
Процедура СоздатьОбсуждение(КлючОбсуждения, ИдентификаторТекущегоПользователя)
    Если СистемаВзаимодействия.ПолучитьОбсуждение(КлючОбсуждения) = Неопределено Тогда
        Обсуждение = СистемаВзаимодействия.СоздатьОбсуждение();
        Обсуждение.Отображаемое = Ложь;
        Обсуждение.Ключ = КлючОбсуждения;
        Обсуждение.Участники.Добавить(ИдентификаторТекущегоПользователя);
        Обсуждение.Записать();
    КонецЕсли;
КонецПроцедуры

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

Процедура ДемоФоновоеЗадание() Экспорт

    ИдентификаторТекущегоПользователя = СистемаВзаимодействия.ИдентификаторТекущегоПользователя();
    КлючОбсуждения = "ДемоФоновоеЗадание " + ИдентификаторТекущегоПользователя;
    Обсуждение = СистемаВзаимодействия.ПолучитьОбсуждение(КлючОбсуждения);

    Для Счетчик = 1 По 10 Цикл
        Данные = Новый Структура("Счетчик", Счетчик);

        Сообщение = СистемаВзаимодействия.СоздатьСообщение(Обсуждение.Идентификатор);
        Сообщение.Данные = Данные;
        Сообщение.Записать();

        Пауза(); // здесь у вас — реальный шаг длительной операции
    КонецЦикла;

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

Замыкает всё обработчик новых сообщений на клиенте — вытаскивает счётчик из структуры и двигает индикатор на форме:

&НаКлиенте
Процедура ОбработкаСообщенийСервера(Сообщение, ДополнительныеПараметры) Экспорт
    ИндикаторФоновогоЗадания = Сообщение.Данные.Счетчик * 10;
КонецПроцедуры

Всё. Никакого своего сервера, JSON и парсинга — платформа доставляет структуру 1С «как есть». Это самый быстрый способ прикрутить живой прогресс-бар, если сервер взаимодействия у вас уже зарегистрирован.

Способ 2. Через WebSocket и внешний ws-сервер

Второй способ пригодится, когда канал реального времени нужен не только внутри 1С, но и общий с веб-клиентами: тот же сокет слушают и толстый клиент, и браузер. Здесь 1С выступает обычным ws-клиентом внешнего сервера (в демо — ws://localhost:8000/ws/server1s/1/, по адресу видно Django Channels).

Серверная часть подключает Native-компоненту ws-клиента и шлёт прогресс уже строкой JSON — внешний сервер про типы 1С ничего не знает:

Процедура ДемоФоновоеЗаданиеWebSocket() Экспорт

    ПутьКомпоненты = "ОбщийМакет.КомпонентаWebSocket";
    Если НЕ ПодключитьВнешнююКомпоненту(ПутьКомпоненты, "WebSocket", ТипВнешнейКомпоненты.Native) Тогда
        ВызватьИсключение "Ошибка подключения внешней компоненты";
    КонецЕсли;

    Клиент = Новый("AddIn.WebSocket.Client");
    АдресСервера = "ws://localhost:8000/ws/server1s/1/"; // вынесите в настройки, не хардкодьте

    Попытка
        Клиент.Подключиться(АдресСервера);
    Исключение
        ВызватьИсключение ОписаниеОшибки() + ": " + Клиент.ОписаниеОшибки();
    КонецПопытки;

    Для Счетчик = 1 По 10 Цикл
        ТелоСообщения = ОбъектВJSON(Новый Структура("count", Счетчик));
        Клиент.Отправить(ТелоСообщения);
        Пауза();
    КонецЦикла;

    Клиент.Отключиться();

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

На форме прогресс-бар живёт в поле HTML: внутри — JS-клиент того же сокета. По входящему сообщению он двигает <progress> и прокидывает значение обратно в 1С через «мост» — клик по скрытой кнопке:

<script>
  var interactionVariable = 0;
  const socket1S = new WebSocket('ws://localhost:8000/ws/server1s/1/');

  socket1S.onmessage = function(event) {
    const data = JSON.parse(event.data);
    document.getElementById('progressbar').value = data.message['count'] * 10;
    interactionVariable = data.message['count'];
    interactionButton.click(); // сигналим в 1С
  };
</script>

А со стороны 1С событие нажатия скрытой кнопки читает значение из окна документа поля HTML — это и есть мост JS → 1С:

&НаКлиенте
Процедура ПолеJSПриНажатии(Элемент, ДанныеСобытия, СтандартнаяОбработка)
    ОкноДокумента = ЭтаФорма.Элементы.ПолеJS.Документ.defaultView;
    Если НЕ ОкноДокумента = Неопределено Тогда
        НажатыйЭлемент = ДанныеСобытия.Element;
        Если НажатыйЭлемент.id = "interactionButton" Тогда
            ИндикаторФоновогоЗадания = ОкноДокумента.interactionVariable * 10;
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Ради этого моста и приходится городить скрытую кнопку: прямого способа «дёрнуть» серверную процедуру 1С из JS в поле HTML нет, поэтому JS меняет переменную и «нажимает» кнопку, а обработчик ПриНажатии уже штатно уходит на сервер.

Какой способ выбрать

Если прогресс нужен только внутри 1С и сервер взаимодействия уже развёрнут — берите способ 1: минимум кода, никакой внешней инфраструктуры, платформа сама тащит структуру. Если канал реального времени нужен общий с веб-частью (сайт, мобильное приложение на том же сокете) или вы и так держите свой backend — способ 2 на WebSocket ложится естественнее, но потребует внешнего ws-сервера и Native-компоненты. Разворачивание самого сервера взаимодействия под оба сценария разобрано в обзорной статье кластера.

Подводные камни

  • Пауза в примере — учебная эмуляция. В демо задержка сделана запуском vbs-скрипта со Sleep(1000) — это платформенно-зависимый костыль под Windows чисто чтобы «растянуть» демо. В бою вместо Пауза() у вас реальные шаги операции; ничего эмулировать не нужно.
  • Адрес ws-сервера не хардкодьте. В примере он строкой в коде — вынесите в константу или регистр сведений, иначе перенос между контурами (dev/prod) превратится в правку кода.
  • Система взаимодействия требует регистрации. Обсуждения и push не заработают, пока сервер взаимодействия не зарегистрирован в информационной базе (1С:Диалог или свой сервер).
  • WebSocket-способ требует Native-компоненту и живой ws-сервер. Без поднятого внешнего сервера и зарегистрированной компоненты AddIn.WebSocket.Client подключение упадёт на Подключиться.

Разобрать оба способа на живом стенде, поднять сервер взаимодействия и связать 1С с внешним Python-бэкендом мы подробно проходим на курсе «Сервер взаимодействия 1С на Python» — там и Native-компонента, и Django Channels, и продовая обвязка.

Частые вопросы