Люди практически перестали использовать 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
Работа с протоколом иногда вызывает проблемы, связанные с его техническими особенностями и инфраструктурными ограничениями.
Проблемы с установлением и поддержанием соединения.
Соединение с SMSC прерывается или не устанавливается, особенно при нестабильном интернет-соединении или неверной конфигурации.
Ошибки аутентификации.
При выполнении команды bind_transmitter, bind_receiver или bind_transceiver.
Ограничения по скорости отправки сообщений (throttling).
Кодами ошибок, связанных с превышением лимитов скорости.
Ошибки при обработке длинных сообщений.
SMS с длиной более 160 символов могут не доставляться или приходить по частям, которые не собираются в единое сообщение на устройстве получателя.
Проблемы с кодировкой.
Сообщения приходят с неправильными символами или нечитаемым текстом.
Проблемы с подтверждениями доставки (Delivery Receipts).
Приложение не получает подтверждения доставки сообщений или получает их с задержкой.
Неправильная обработка сетевых ошибок.
Приложение зависает или теряет сообщения при возникновении проблем с сетью.
Отсутствие управления потоками сообщений.
Приложение перегружается при одновременной отправке множества SMS.
Проблемы с задержками доставки.
Сообщения доставляются с задержкой, или SMSC долго обрабатывает запросы на отправку.
Несовместимость версий SMPP.
Ошибки взаимодействия с SMSC или другими системами.
Таймауты при отправке сообщений.
Соединение с 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, позаботились о подробной документации для работы с этим протоколом.
Vamp
Как человек, съевший собаку на СМС вообще и на 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 прямо сейчас.
michabramov Автор
Ценная информация, и тонкостей в SMPP действительно хватает. Новичкам советы точно пригодятся. Свои примеры доработаем, спасибо за комментарий :)