Начнем с того, что работодателем мне была поставлена задача - создать механизм по отправке смс-сообщений фиксированной длины с помощью сервиса, который предоставляется компанией Билайн.
После получения необходимых реквизитов от провайдера: таких как логин, пароль, сетевой адрес, номер порта и небольшого томика документации, стало понятно, что дело придется иметь с так называемым сетевым протоколом SMPP v 3.4.
Надо признаться, что до этого момента у меня не было опыта работы с каким-либо сетевым протоколом, и кроме языка 1С уровня программиста-внедренца, более никаким программным языком не владею.
ActiveХ: Winsock
Главная и основная задача была найти программные средства, которые бы позволили работать с сетевым протоколом. В 1С таких средств не оказалось, может быть я их просто не нашел, и после тщательных поисков возникла идея воспользоваться АктивХ компонентой предоставляемой компанией Microsoft - Winsock.
Из википедии... Windows Sockets API (WSA), название которого было укорочено до Winsock. Это техническая спецификация, которая определяет, как сетевое программное обеспечение Windows будет получать доступ к сетевым сервисам, в том числе, TCP/IP. Он определяет стандартный интерфейс между клиентским приложением (таким как FTP-клиент или веб-браузер) и внешним стеком протоколов TCP/IP. Он основывается на API модели сокетов Беркли, использующейся в BSD для установки соединения между программами.
Для установки и регистрации в ОС Windows необходмых файлов данной компоненты воспользовался советом и просто установил на компьютер пакет http://www.microsoft.com/ru-ru/softmicrosoft/VisualStudioExpress.aspx
После установки пакета (я установил версию visual basic 2012 express) появилась возможность разместить на форме winsock компоненту и использовать ее свойства и методы.
Рис. 1 Расположение компоненты
Рис. 2 Методы winsock
Первым шагом алгоритма будет процедура инициализации настроек и выполнение метода подключения
Процедура Иницилизация() Экспорт
мWinSocketActiveX = ПолучитьФорму("Форма").ЭлементыФормы.WinSocket;
//иницилизация настроек для подключения
мWinSocketActiveX.RemotePort = 3334;
мWinSocketActiveX.RemoteHost = "217.118.84.12";
ВыполнитьПодключение();
КонецПроцедуры // Иницилизация()
Процедура ВыполнитьПодключение()
Если мWinSocketActiveX.State = 0 Тогда
мWinSocketActiveX.Connect();
КонецЕсли;
КонецПроцедуры
После успешной установки подключения к серверу клиенту необходимо в течении 10 секунд отправить команду BIND_TRANSMITTER или BIND_TRANSCEIVER иначе соединение будет разорвано сервером. Поэтому в коде проверяем статус подключения, собираем и отправляем пакет - PDU (Protocol Data Units (Bind_Transceiver).
Процедура ОпроситьСоединение() Экспорт
Если мWinSocketActiveX.State = 7 Тогда
PDU = СобратьПакет_BIND_Transceiver();
мWinSocketActiveX.SendData(PDU);
Иначе //обработать другие состояния!!!
КонецЕсли;
КонецПроцедуры // УстановитьСоединение()
Для сборки PDU используем многомерный массив 1С COMSafeArray.
Функция СобратьПакет_BIND_Transceiver()
Матрица = Новый COMSafeArray("VT_UI1",37);
Матрица.SetValue(0,0);
Матрица.SetValue(1,0);
Матрица.SetValue(2,0);
Матрица.SetValue(3,37);
//код сокращен для этой статьи***
//***
Матрица.SetValue(32,0);
Матрица.SetValue(33,52);
Матрица.SetValue(34,1);
Матрица.SetValue(35,1);
Матрица.SetValue(36,0);
Возврат Матрица;
КонецФункции // СобратьПакет_BIND_Transceiver()
Клиент обязан отвечать на все пакеты отправленные сервером соответствующим resp пакетом в течение 1 минуты. Иначе соединение будет разорвано сервером без отсылки UNBIND.
После установки подключения и авторизации сервер будет отправлять ENQUIRE_LINK пакеты каждую минуту. На этот пакет клиент также обязан ответить в течение 1 минуты.
Методом DataArrival() принимаем и обрабатываем входящие пакеты:
Процедура WinSocketDataArrival(Элемент, bytesTotal)
ВходящийПакет = Неопределено;
мWinSocketActiveX.GetData(ВходящийПакет);
Если ТипЗнч(ВходящийПакет) = Тип("COMSafeArray") Тогда
ОбработатьВходящийПакет(ВходящийПакет);
КонецЕсли;
КонецПроцедуры
Процедура ОбработатьВходящийПакет(Пакет) Экспорт
//пришел пакет ENQUIRE_LINK
Если Пакет.GetValue(4) = 0 И Пакет.GetValue(7) = 21 Тогда
PDU = Фабрика_PDU_ENQUIRE_LINK_RESP(Пакет);
ОтправитьПакет(PDU);
Если мФлагОтправитьСМС Тогда
СобратьИОтправитьПакет_CMC();//отсюда отправялем смс-сообщение
мФлагОтправитьСМС = Ложь;
КонецЕсли;
КонецЕсли;
//пришел пакет bind_transceiver_resp
Если Пакет.GetValue(4) = 128 И Пакет.GetValue(7) = 9 Тогда
ОбработатьПакет_bind_transceiver_resp(Пакет);
КонецЕсли;
//пришел пакет submit_sm_resp
Если Пакет.GetValue(4) = 128 И Пакет.GetValue(7) = 4 Тогда
ОбработатьПакет_submit_sm_resp(Пакет);
КонецЕсли;
//пришел пакет Query_sm_resp
Если Пакет.GetValue(4) = 128 И Пакет.GetValue(7) = 3 Тогда
ОбработатьПакет_Query_sm_resp(Пакет);
КонецЕсли;
//пришел пакет UNBIND_resp
Если Пакет.GetValue(4) = 128 И Пакет.GetValue(7) = 6 Тогда
мWinSocketActiveX = Неопределено;
КонецЕсли;
КонецПроцедуры // ОбработатьВходящийПакет()
После того как клиент и сервер успешно обменялись пакетами ENQUIRE_LINK можно отправить текст смс-сообщения
Процедура СобратьИОтправитьПакет_CMC()
Перем PDU;
Если ЗаполнитьТаблицуЗначенийСМС() Тогда
НомерТел = мТабЗнДиспетчерСМС[0].НомерТелефона;
НомерДок = мТабЗнДиспетчерСМС[0].НомерДокумента;
ДатаДок = мТабЗнДиспетчерСМС[0].ДатаДокумента;
СтатусСМС = мТабЗнДиспетчерСМС[0].СтатусСМС;
ИдентификаторСМС = мТабЗнДиспетчерСМС[0].ИдентификаторСМС;
ТипДокумента = мТабЗнДиспетчерСМС[0].ЗаказПокупателя;
Если СтатусСМС = 0 Тогда
PDU = СобратьПакет_Submit_SM(НомерТел,НомерДок,ДатаДок,ТипДокумента);
СтрокаСообщения = Формат(ТекущаяДата(),"ДЛФ=DT")+" <= Отправляю СМС на номер: " +НомерТел+". Inf...";
Лог.ДобавитьСтроку(СтрокаСообщения);
ИначеЕсли СтатусСМС = 10 Тогда
PDU = СобратьПакет_Query_SM(ИдентификаторСМС);
СтрокаСообщения = Формат(ТекущаяДата(),"ДЛФ=DT")+" <= Запрашиваю статус СМС для номера: "+НомерТел + " . Inf...";
Лог.ДобавитьСтроку(СтрокаСообщения);
КонецЕсли;
Иначе
СтрокаСообщения = Формат(ТекущаяДата(),"ДЛФ=DT")+" <= Нет данных для отправки смс. Закрываю канал связи. Inf...";
Лог.ДобавитьСтроку(СтрокаСообщения);
PDU = СобратьПакет_UNBIND(); // отправлять больше нечего. Закрываем соединение;
КонецЕсли;
ОтправитьПакет(PDU);
КонецПроцедуры // СобратьИОтправитьПакет_CMC()
// <Описание функции>
//
// Параметры
// <Параметр1> - <Тип.Вид> - <описание параметра>
// <продолжение описания параметра>
// <Параметр2> - <Тип.Вид> - <описание параметра>
// <продолжение описания параметра>
//
// Возвращаемое значение:
// <Тип.Вид> - <описание возвращаемого значения>
//
Функция СобратьПакет_Submit_SM(НомерТелефона,НомерДокумента,ДатаДокумента,Объект)
Перем ИспользоватьРусский;
ИспользоватьРусский = Истина; //настройка
Если ТипЗнч(Объект) = Тип("ДокументСсылка.ЗаказПокупателя") Тогда
СообщениеТекстЛатиница = "Vash tovar po zakazu N "+НомерДокумента+" ot " + Формат(ДатаДокумента,"ДФ=dd.MM.yyyy")+" dostavlen";
СообщениеТекстРусский = "Ваш товар по заказу № "+НомерДокумента+" от " + Формат(ДатаДокумента,"ДФ=dd.MM.yyyy")+" доставлен";
ИначеЕсли ТипЗнч(Объект) = Тип("ДокументСсылка.АктПретензий") Тогда
СообщениеТекстЛатиница = "Vash tovar gotov k vydachi. Akt pretenzii N "+НомерДокумента+" ot " + Формат(ДатаДокумента,"ДФ=dd.MM.yyyy");
СообщениеТекстРусский = "Ваш товар готов к выдаче. Акт претензии № "+НомерДокумента+" от " + Формат(ДатаДокумента,"ДФ=dd.MM.yyyy");
КонецЕсли;
Если ИспользоватьРусский Тогда
СообщениеТекст = СообщениеТекстРусский;
МассивКодовРусскихБукв = ЗакодироватьРусскийТекст(СообщениеТекстРусский);
ДлинаСообщения = СтрДлина(СообщениеТекст)*2;
DataCod = 8;
Иначе
СообщениеТекст = СообщениеТекстЛатиница;
ДлинаСообщения = СтрДлина(СообщениеТекст);
DataCod = 0;
КонецЕсли;
ДлинаНомераТелефона = СтрДлина(НомерТелефона);
ДлинаПакета = 32+ДлинаНомераТелефона+11+ДлинаСообщения;
МатрицаДанных = Новый COMSafeArray("VT_UI1",ДлинаПакета-1);
//Заголовок
МатрицаДанных.SetValue(0, 0);
МатрицаДанных.SetValue(1, 0);
МатрицаДанных.SetValue(2, 0);
МатрицаДанных.SetValue(3, ДлинаПакета-1);
МатрицаДанных.SetValue(4, 0);
МатрицаДанных.SetValue(5, 0);
МатрицаДанных.SetValue(6, 0);
МатрицаДанных.SetValue(7, 4);
МатрицаДанных.SetValue(8, 0);
МатрицаДанных.SetValue(9, 0);
МатрицаДанных.SetValue(10, 0);
МатрицаДанных.SetValue(11, 0);
МатрицаДанных.SetValue(12, 0);
МатрицаДанных.SetValue(13, 0);
МатрицаДанных.SetValue(14, 0);
МатрицаДанных.SetValue(15, 2); //номер пакета
//тело
МатрицаДанных.SetValue(16, 0);
МатрицаДанных.SetValue(17, 5);
МатрицаДанных.SetValue(18, 1);
МатрицаДанных.SetValue(19, КодСимвола("k")); //K
МатрицаДанных.SetValue(20, КодСимвола("."));
МатрицаДанных.SetValue(21, КодСимвола("a")); //
МатрицаДанных.SetValue(22, КодСимвола("p")); //
МатрицаДанных.SetValue(23, КодСимвола("e")); //
МатрицаДанных.SetValue(24, КодСимвола("l"));//
МатрицаДанных.SetValue(25, КодСимвола("s"));
МатрицаДанных.SetValue(26, КодСимвола("i"));
МатрицаДанных.SetValue(27, КодСимвола("n")); //
МатрицаДанных.SetValue(28, 0);
МатрицаДанных.SetValue(29, 1);
МатрицаДанных.SetValue(30, 1);
Индекс = 31;
Для НомерСтроки = 1 По ДлинаНомераТелефона Цикл
МатрицаДанных.SetValue(Индекс,КодСимвола(НомерТелефона,НомерСтроки));
Индекс = Индекс+1;
КонецЦикла;
МатрицаДанных.SetValue(42, 0);
МатрицаДанных.SetValue(43, 0);
МатрицаДанных.SetValue(44, 0);
МатрицаДанных.SetValue(45, 0);
МатрицаДанных.SetValue(46, 0);
МатрицаДанных.SetValue(47, 0);
МатрицаДанных.SetValue(48, 1);
МатрицаДанных.SetValue(49, 1);
МатрицаДанных.SetValue(50, DataCod);//data coding
МатрицаДанных.SetValue(51, 1);
МатрицаДанных.SetValue(52, ДлинаСообщения);
Индекс = 53;
Если ИспользоватьРусский Тогда
Для НомерСтроки1 = 0 По ДлинаСообщения-1 Цикл
СимВ = МассивКодовРусскихБукв[НомерСтроки1];
МатрицаДанных.SetValue(Индекс,СимВ);
Индекс = Индекс+1;
КонецЦикла;
Иначе
Для НомерСтроки2 = 1 По ДлинаСообщения Цикл
ЗначениеСимвола = КодСимвола(СообщениеТекст,НомерСтроки2);
МатрицаДанных.SetValue(Индекс,ЗначениеСимвола);
Индекс = Индекс+1;
КонецЦикла;
КонецЕсли;
Возврат МатрицаДанных;
КонецФункции // СобратьПакет_Submit_SM()
Процедура ОтправитьПакет(PDU)
Если мWinSocketActiveX.State = 7 Тогда
мWinSocketActiveX.SendData(PDU);
КонецЕсли;
КонецПроцедуры // ОтправитьПакет()
В правилах хорошего тона будет считаться отправка UNBIND пакета, если канал связи вы больше не будете использовать.