Люди практически перестали использовать SMS для общения, но с точки зрения бизнеса это по-прежнему важный инструмент — для оповещений, авторизации, информирования о статусе заказов и во множестве других ситуаций. Сегодня рассмотрим протокол SMPP и его связь с SMS, а затем настроим свой сервер и интегрируемся с SMS API, чтобы отправить сообщение.

Взаимодействие мобильной сети с интернетом через SMPP

Протокол SMPP нужен для обмена сообщениями между веб-приложениями и мобильными абонентами по сети TCP/IP. Он не доставляет сообщения оператору связи самостоятельно, а служит лишь каналом передачи данных.

Основные элементы системы передачи SMS

  • SMS (Short Message Service) — стандарт телекоммуникаций для отправки коротких текстовых сообщений между мобильными телефонами и другими устройствами через сети мобильной связи.

  • SMSC (Short Message Service Center) — ключевой элемент инфраструктуры мобильной сети. Управляет маршрутизацией сообщения между сетями, отправкой, приёмом, хранением и доставкой SMS, включая уведомления о ней. Когда получатель временно недоступен, SMSC хранит сообщение и передаёт его в момент подключения устройства к сети.

Взаимосвязь SMS и SMPP

Источник

Хотя SMPP и SMS функционируют на разных уровнях, они тесно связаны. SMPP используется для передачи сообщений через серверы и сети к мобильным абонентам. И SMPP, и SMS зависят от SMSC, который управляет процессом передачи сообщений.

Применение SMPP

SMPP эффективен для обмена сообщениями между интернет-серверами и операторами мобильной связи. Для общения только между серверами чаще используют другие протоколы: HTTP, MQTT или AMQP, так как они предназначены для широкого спектра задач серверного взаимодействия и более оптимизированы для таких целей.

Проблемы при работе с SMPP

Работа с протоколом иногда вызывает проблемы, связанные с его техническими особенностями и инфраструктурными ограничениями.

  1. Проблемы с установлением и поддержанием соединения.

Соединение с SMSC прерывается или не устанавливается, особенно при нестабильном интернет-соединении или неверной конфигурации.

  1. Ошибки аутентификации.

При выполнении команды bind_transmitter, bind_receiver или bind_transceiver.

  1. Ограничения по скорости отправки сообщений (throttling).

Кодами ошибок, связанных с превышением лимитов скорости.

  1. Ошибки при обработке длинных сообщений.

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

  1. Проблемы с кодировкой.

Сообщения приходят с неправильными символами или нечитаемым текстом.

  1. Проблемы с подтверждениями доставки (Delivery Receipts).

Приложение не получает подтверждения доставки сообщений или получает их с задержкой.

  1. Неправильная обработка сетевых ошибок.

Приложение зависает или теряет сообщения при возникновении проблем с сетью.

  1. Отсутствие управления потоками сообщений.

Приложение перегружается при одновременной отправке множества SMS.

  1. Проблемы с задержками доставки.

Сообщения доставляются с задержкой, или SMSC долго обрабатывает запросы на отправку.

  1. Несовместимость версий SMPP.

Ошибки взаимодействия с SMSC или другими системами.

  1. Таймауты при отправке сообщений.

Соединение с SMSC может «зависнуть», и сообщения не отправляются.

Но все эти проблемы можно диагностировать и исправить при разработке, а тестирование позволит выпустить стабильную версию.

Создание сервера express.js

Теперь создадим свою систему для отправки SMS через API-платформу МТС Exolve. Развернём базовый сервер на express. Все конфиденциальные данные для подключения будут храниться в файле .env: добавим в него пароль, логин, хост и порт. Сервер будет слушать 3000 порт, а по роуту http://localhost:3000/send-sms мы начнём отправлять запросы через Postman и посылать сообщения.

Также важно установить зависимости: dotenv, express, smpp.

В главный файл app.js добавляем код:

Сейчас у нас есть сервер на express, который можно запустить командой node app.js.

Подключение к SMPP

Далее создадим соединение с SMPP с помощью документации МТС Exolve. В файл smppConnection.js добавляем код:

const smpp = require("smpp");
require("dotenv").config();


let session;
let isBound = false; // Переменная для отслеживания статуса привязки
let isBindingInProgress = false; // Флаг для отслеживания процесса привязки
let bindTimeout; // Таймер для сообщения о проблемах с привязкой


const closeSession = (reason) => {
  console.log(reason);
  session.destroy(); // Принудительное закрытие сессии
  isBound = false; // Обновляем статус сессии
};


const connectSMPP = () => {
  console.log("Попытка подключения к SMPP серверу...");
  session = new smpp.Session({
    host: process.env.SMPP_HOST,
    port: process.env.SMPP_PORT,
  });


  session.on("connect", () => {
    console.log("СMPP сервер подключен. Попытка привязки...");
    isBindingInProgress = true; // Указываем, что процесс привязки начался
    // Устанавливаем таймер на 5 секунд для закрытия сессии при задержке привязки
    bindTimeout = setTimeout(() => {
      if (isBindingInProgress) {
        closeSession(
          "Привязка занимает больше 5 секунд. Закрытие сессии. Возможно, неверные данные или проблемы с сервером."
        );
      }
    }, 5000); // Тайм-аут на 5 секунд


    session.bind_transceiver(
      {
        system_id: process.env.SMPP_SYSTEM_ID,
        password: process.env.SMPP_PASSWORD,
      },
      (pdu) => {
        clearTimeout(bindTimeout); // Очищаем таймер, если PDU получен
        isBindingInProgress = false; // Привязка завершена


        if (pdu.command_status === 0) {
          isBound = true; // Успешная привязка
          console.log("Успешное подключение к серверу SMPP");
        }
      }
    );
  });


  session.on("close", () => {
    if (isBindingInProgress) {
      console.error(
        "Соединение SMPP закрыто во время привязки. Вероятно, неверные данные аутентификации."
      );
    } else {
      console.log("Соединение SMPP закрыто.");
    }
    isBound = false; // Соединение закрыто, сессия неактивна
  });


  session.on("error", (error) => {
    clearTimeout(bindTimeout); // Очищаем таймер при ошибке
    closeSession(`Ошибка соединения SMPP: ${error}`);
  });


  session.on("pdu", (pdu) => {
    console.log("Получен PDU:", pdu);
  });
};
const getSession = () => {
  if (!isBound) {
    console.error(
      "Ошибка: SMPP сессия не привязана. Проверьте параметры аутентификации."
    );
  }
  return session;
};


module.exports = { connectSMPP, getSession };

Этот код устанавливает соединение с SMPP. Обратите внимание, что на каждом шаге производится вывод в консоль — для отслеживания ошибок.

Отправка SMS

Заключительная часть кода отправляет сообщения.

Добавляем код в новый файл smsService.js.

const { getSession } = require("./smppConnection");


const sendSMS = (phoneNumber, message, sender) => {
  const session = getSession();


  if (!session) {
    console.error(
      "Сессия SMPP не подключена или не активна. Сообщение не будет отправлено."
    );
    return;
  }


  console.log(
    `Попытка отправить сообщение: "${message}" на номер: ${phoneNumber} от отправителя: ${sender}`
  );


  session.submit_sm(
    {
      source_addr: sender, // Номер отправителя
      destination_addr: phoneNumber, // Номер получателя
      short_message: message, // Текст сообщения
      source_addr_ton: 1, // Тип номера отправителя (1 для международного номера)
      source_addr_npi: 1, // План нумерации отправителя (1 для E.164)
      dest_addr_ton: 1, // Тип номера получателя (1 для международного номера)
      dest_addr_npi: 1, // План нумерации получателя (1 для E.164)
      registered_delivery: 1, // Запрос уведомления о доставке
      data_coding: 0x08, // Кодировка сообщения (0x08 для Unicode, 0x00 для GSM7)
    },
    (pdu) => {
      console.log("Получен ответ PDU на отправку сообщения:", pdu);
      if (pdu.command_status === 0) {
        console.log("Сообщение отправлено успешно");
      } else {
        console.error("Не удалось отправить сообщение", pdu.command_status);
      }
    }
  );
};


module.exports = { sendSMS };

Тестирование подключения

Запустим приложение командой node app.js.

При корректно введённых данных программа выведет сообщение: «Успешное подключение к серверу SMPP». Попробуем ввести заведомо ложный host или port.

Если port неправильный, увидим предупреждение: «Ошибка соединения SMPP: Error: getaddrinfo ENOTFOUND smpp.exolve.ru123213».

Соединение SMPP закрыто.

Если ошибка в указании port, то:
Попытка подключения к SMPP серверу...

node:internal/errors:541
      throw error;
      ^

RangeError [ERR_SOCKET_BAD_PORT]: Port should be >= 0 and < 65536. Received type string ('277511').
    at lookupAndConnect (node:net:1298:5)
    at Socket.connect (node:net:1255:5)
    at Object.connect (node:net:238:17)
    at new Session (C:\work\SMPP\node_modules\smpp\lib\smpp.js:67:33)
    at connectSMPP (C:\work\SMPP\smppConnection.js:17:13)
    at Object.<anonymous> (C:\work\SMPP\app.js:8:1)
    at Module._compile (node:internal/modules/cjs/loader:1469:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1548:10)
    at Module.load (node:internal/modules/cjs/loader:1288:32)
    at Module._load (node:internal/modules/cjs/loader:1104:12) {
  code: 'ERR_SOCKET_BAD_PORT'
}

Если мы неправильно ввели system_id или password, то соединение не будет установлено, и через 5 секунд отобразится ошибка:

Попытка привязки…

Привязка занимает больше 5 секунд. Закрытие сессии. Возможно, неверные данные или проблемы с сервером.

Соединение SMPP закрыто во время привязки. Вероятно, неверные данные аутентификации.

Тестирование отправки SMS

Удобнее всего тестировать через Postman. Отправим POST-запрос по API http://localhost:3000/send-sms. Тело запроса: 

{
  "phoneNumber": "Ваш номер телефона",
  "message": "Ваше сообщение",
  "sender": "Номер телефона Exolve"
}

Если введём неправильные номера, консоль сразу предупредит об этом. Если ошибок нет, то мы получим сообщение на указанный номер.

Получаем сообщение
Получаем сообщение

Обращайте внимание на PDU при отлавливании ошибок.

В этих отчётах есть вся нужная информация.

Тщательное изучение и умение работать с консолью поможет выявить и решить даже самую низкоуровневую проблему при работе с SMPP. А такие сервисы, как МТС Exolve, позаботились о подробной документации для работы с этим протоколом.

Комментарии (2)


  1. Vamp
    03.12.2024 11:38

    Как человек, съевший собаку на СМС вообще и на SMPP в частности, советую тем, кто собирается внедрять у себя смски, трижды подумать: так уж сильно ли вашему бизнесу нужны именно они. Любой канал для коммуникаций будет лучше смс - email, мессенджеры, пуши. Особенно для передачи чувствительной информации - одноразовых кодов, паролей, списаний со счёта и т.п.

    Смс - крайне ненадежный вариант в плане качества доставки. Одна из популярных причин недоставки - нехватка свободной памяти в телефоне. Свободной памяти, Карл! На современных телефонах с 256 ГБ памяти! И ещё много других прикольных и не очень ситуаций.

    Смс - крайне ненадёжный вариант в плане безопасности. За примерами далеко ходить не нужно. А ещё большинство сайтов, требующих номер телефона, очень слабо реализуют валидацию по смс. Из-за этого появилось такое печально известное явление как смс-бомбинг. Для бизнеса есть ещё риск попасть на бабки через тот же самый смс бомбинг или утечку пароля от смс сервиса, что мощно накрутит счета у поставщика смс услуг. Таких случаев тоже видел немало. И поставщики не стесняются посудиться.

    Однажды я давал советы как можно улучшить защиту своих смс форм.

    Но если вы всё же решитесь внедрить смс, то старайтесь выбрать какой угодно протокол, только не SMPP. Казалось бы, SMPP - стандартный протокол для передачи смс, поддерживаемый всеми операторами и агрегаторами. Реализуй один раз и можешь безболезненно менять поставщиков хоть каждый день. Но это неправда. Выбирая SMPP вы выбираете путь боли и страданий. Реализация SMPP у разных поставщиков отличается, а сам по себе протокол очень сложный и низкоуровневый. Начать хотя бы с того, что он бинарный, что само по себе становится непреодолимой преградой при траблшутинге для большинства программистов. Ещё в SMPP большое количество нюансов и тонкостей. Взять хотя бы эту статью: "data_coding: 0x08, // Кодировка сообщения (0x08 для Unicode, 0x00 для GSM7)". 0x08 - это unicode, но не указана какая именно кодировка из всего семейства юникодов. По спецификации SMPP - это древневековый UCS-2, но по факту все на рынке поддерживают UTF-16BE. А 0x00 - это "message center specific" по спецификации. То есть кодировка, которая может отличаться от поставщика к поставщику. У кого-то это GSM 03.38, у кого-то latin1, у кого-то ASCII или даже юникод какой-нибудь. Да и что значит GSM7? Это упакованный в 7 бит вариант GSM 03.38? Ну и ещё возникают вопросы про параметры source_addr_ton/npi. Значения 1/1 используются только если отправитель указывается в виде номер телефона, что в РФ уже много лет как запрещено, а для альфанумерического отправителя надо задавать 5/0. Так что в статье скорее всего ошибка в примере. Раз уж даже специалисты не смогли написать нормальный пример для этой статьи, то впервые сталкивающиеся с SMPP протоколом либо сойдут с ума, либо познают дзен. И это я ещё не затрагиваю крайне хитрый вопрос с отправкой длинных многосегментных смс.

    Поэтому оказывается проще отдельно реализовать HTTP API каждого отдельного поставщика, чем мучиться с SMPP.

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


    1. michabramov Автор
      03.12.2024 11:38

      Ценная информация, и тонкостей в SMPP действительно хватает. Новичкам советы точно пригодятся. Свои примеры доработаем, спасибо за комментарий :)