Установление и разрыв соединения сетевого сокета

В предыдущих разделах мы познакомились с высокоуровневыми сетевыми функциями MQL5: каждая из них предоставляет поддержку прикладному протоколу конкретной направленности. Например, SMTP — для отправки электронной почты (SendMail), FTP — для передачи файлов (SendFTP) или HTTP — для получения веб-документов (WebRequest). Все упомянутые стандарты опираются на более низкий, транспортный уровень TCP (Transmission Control Protocol). Он не последний в иерархии — есть и более низкие, но здесь мы не станем их касаться.

Стандартная реализация прикладных протоколов скрывает внутри много технических нюансов и освобождает программиста от лишних часов рутинного следования спецификациям. Однако, она не обладает гибкостью, не учитывает расширенные возможности, заложенные в стандарты. Поэтому иногда требуется запрограммировать сетевое взаимодействие на уровне TCP — на уровне сокетов.

Сокет можно рассматривать как аналог файла на диске: сокет тоже описывается целочисленным дескриптором, по которому можно читать или записывать данные, но происходит это в распределенной сетевой инфраструктуре. В отличие от файлов, количество сокетов на компьютере ограничено, и потому дескриптор сокета нужно заранее запросить у системы, прежде чем связать его с сетевым ресурсом (адресом, URL). Также заранее скажем, что доступ к информации через сокет — поточный, то есть нельзя "перемотать" некий "указатель" на начало, как в файле.

Потоки записи и чтения не пересекаются, но могут влиять на будущие читаемые или записываемые данные, поскольку передаваемая информация часто трактуется серверами и клиентскими программами как управляющие команды. Когда поток содержит команды, а когда данные — определяют стандарты протоколов.

Для создания "пустого" дескриптора сокета в MQL5 предназначена функция SocketCreate.

int SocketCreate(uint flags = 0)

Единственный параметр зарезервирован на будущее для указания битовой комбинации флагов, определяющих режим работы сокета, однако в данный момент поддерживается только один флаг-заглушка — SOCKET_DEFAULT — он соответствует текущему режиму, и его можно не указывать. На системном уровне это эквивалентно сокету в блокирующем режиме (это может быть интересно специалистам в области сетевого программирования).

В случае успешного выполнения функция возвращает дескриптор сокета, а иначе — INVALID_HANDLE.

Из одной MQL-программы можно создать максимум 128 сокетов. При превышении лимита в _LastError записывается ошибка 5271 (ERR_NETSOCKET_TOO_MANY_OPENED).

После того как мы открыли сокет, его следует связать с сетевым адресом.

bool SocketConnect(int socket, const string server, uint port, uint timeout)

Функция SocketConnect выполняет подключение сокета к серверу по указанному адресу и порту (например, веб-сервера обычно работают на портах 80 или 443 для протоколов HTTP и HTTPS, соответственно, а SMTP — на порту 25). Адресом может выступать как доменное имя, так и IP-адрес.

Параметр timeout позволяет задать таймаут в миллисекундах для ожидания ответа сервера.

Функция возвращает признак успешного подключения (true) или ошибки (false). Код ошибки записывается в _LastError, например, 5272 (ERR_NETSOCKET_CANNOT_CONNECT).

Напоминаем: адрес для подключения должен быть добавлен в список разрешенных в настройках терминала (диалог Сервис -> Настройки -> Советники).

После завершения работы с сетью следует освободить сокет с помощью SocketClose.

bool SocketClose(const int  socket)

Функция SocketClose закрывает сокет по его дескриптору, открытому ранее с помощью функции SocketCreate. Если для сокета ранее было создано соединение через SocketConnect, оно будет разорвано.

Функция также возвращает признак успеха (true) или ошибки (false). В частности, при передаче неверного дескриптора в _LastError записывается ошибка 5270 (ERR_NETSOCKET_INVALIDHANDLE).

Напоминаем, что все функции данного и последующих разделов запрещены в индикаторах: там попытка работать с сокетами приведет к ошибке 4014 (ERR_FUNCTION_NOT_ALLOWED, "Системная функция не разрешена для вызова").

Рассмотрим вводный пример — скрипт SocketConnect.mq5. Во входных параметрах можно задать адрес и порт сервера. Предполагается, что мы начнем тестирование с обычных веб-серверов, таких как mql5.com.

input string Server = "www.mql5.com";
input uint Port = 443;

В функции OnStart просто создадим сокет и свяжем его с сетевым ресурсом.

void OnStart()
{
   PRTF(Server);
   PRTF(Port);
   const int socket = PRTF(SocketCreate());
   if(PRTF(SocketConnect(socketServerPort5000)))
   {
      PRTF(SocketClose(socket));
   }
}

Если все настройки в терминале сделаны правильно, и он подключен к Интернет, получим следующий "отчет".

Server=www.mql5.com / ok
Port=443 / ok
SocketCreate()=1 / ok
SocketConnect(socket,Server,Port,5000)=true / ok
SocketClose(socket)=true / ok