Скачать MetaTrader 5

Работа с сокетами в MQL, или Как стать провайдером сигналов

12 июля 2016, 16:48
o_o
33
2 371

Немного пафоса

Сокеты… Что вообще сейчас в нашем информационном мире может без них существовать? Впервые появившиеся в 1982 г. и практически не изменившиеся до настоящего времени, они исправно работают на нас каждую секунду. Это основа сети, нервные окончания нашей Matrix, в которой мы живем.

Утром вы включили терминал MetaTrader, и он сразу создал сокеты и подключился к серверам. Вы открыли браузер, и десятки сокетных коннектов создаются и исчезают ради доставки вам информации из веба или отправки почты, сигналов точного времени, гигабайта распределенных вычислений.

Итак, для начала нам нужно почитать немного теории. Загляните в Wiki  или в MSDN. В соответствующих статьях хорошо описан весь необходимый арсенал структур и функций, а также приведены примеры создания клиента и сервера.

Переносом этих знаний в MQL мы и займемся в данной статье.

1. Портируем структуры и функции из WinAPI

Не секрет, что WinAPI описано для языка C. А язык MQL — практически его кровный брат (и по духу, и по стилю работы). Давайте создадим mqh-файл для этих WinAPI функций, который будем использовать в основной MQL-программе. Порядок наших действий — портировать по мере необходимости.

Для TCP клиента нам необходимо всего несколько функций:

  • инициализировать библиотеку WSAStartup();
  • создать сокет socket();
  • перевести в неблокирующий режим ioctlsocket(), чтобы не зависать намертво при ожидании данных;
  • подключиться к серверу connect();
  • слушать recv() или отправлять send() данные до завершения программы или потери коннекта;
  • после работы закрыть сокет closesocket() и деинициализировать библиотеку WSACleanup().

Для TCP сервера необходимы аналогичные функции, за исключением того, что мы привяжемся к конкретному порту и переведем сокет в режим ожидания подключения. Итого выполняем:

  • инициализировать библиотеку WSAStartup();
  • создать сокет socket();
  • перевести в неблокирующий режим ioctlsocket();
  • привязаться к порту bind();
  • перевести в режим ожидания подключения listen();
  • после успешного создания слушать accept();
  • создавать коннект клиентов и далее работать с ними в режиме recv()/send() до завершения программы или потери коннекта;
  • после работы закрыть слушающий сокет сервера и подключенных клиентов closesocket() и деинициализировать библиотеку WSACleanup().

В случае UDP сокета порядок будет короче (фактически отсутствует "рукопожатие" клиента и сервера). UDP клиент:

  • инициализировать библиотеку WSAStartup();
  • создать сокет socket();
  • перевести в неблокирующий режим ioctlsocket(), чтобы не зависать намертво при ожидании данных;
  • отправить sendto() /принять recvfrom() данные;
  • после работы закрыть сокет closesocket() и деинициализировать библиотеку WSACleanup().

в UDP сервере добавляется только одна bind функция:

  • инициализировать библиотеку WSAStartup();
  • создать сокет socket();
  • перевести в неблокирующий режим ioctlsocket();
  • привязаться к порту bind();
  • принять recvfrom() / отправить sendto();
  • после работы закрыть слушающий сокет сервера  и подключенных клиентов closesocket() и деинициализировать библиотеку WSACleanup().

Как видите, путь не слишком сложный, но для вызова каждой функции понадобится заполнять структуры.


a) WSAStartup()

Смотрим её описание в MSDN:
WINAPI:
int WSAAPI WSAStartup(_In_ WORD wVersionRequested,  _Out_ LPWSADATA lpWSAData);
_In_, _Out_ — это пустые дефайны, которые указывают на область применения параметра. WSAAPI – описывает правило передачи параметров, но для нас его можно тоже сделать пустым.

Как видно из документации, нам понадобится также макрос MAKEWORD, чтобы указать в первый параметр требуемую версию, а также указатель на структуру LPWSADATA. С макросом всё несложно, скопируем его из заголовочного файла:

#define MAKEWORD(a, b)      ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))
При этом все типы данных тоже легко описываются в рамках MQL:
#define BYTE         uchar
#define WORD         ushort
#define DWORD        int
#define DWORD_PTR    ulong
Структуру WSADATA копируем из MSDN. Названия большинства типов данных оставляем для удобства чтения, тем более, что они уже определены у нас выше.
struct WSAData
{
  WORD wVersion;
  WORD  wHighVersion;
  char szDescription[WSADESCRIPTION_LEN+1];
  char szSystemStatus[WSASYS_STATUS_LEN+1];
  ushort iMaxSockets;
  ushort iMaxUdpDg;
  char  lpVendorInfo[];
}
Обратите внимание, что последний параметр lpVendorInfo в MQL описан как массив (в C это был указатель на char*). Константы для размера массивов также внесём в дефайны. Наконец, указатель на структуру опишем как:
#define LPWSADATA        char&

Почему так? Всё просто. Любая структура – это не что иное, как ограниченный кусочек памяти. Его можно представить в любом виде – например, и в виде другой структуры такого же размера, или же как аналогичный по размеру массив. Я использую представление в виде массива, поэтому тип char& во всех функциях будет являться адресом массива, размер которого соответствует размеру требуемой структуры. Итого объявление функции в MQL выглядит как:

MQL:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData[]);
А так выглядит вызов функции и получение результата в структуру WSAData:
char wsaData[]; // массив байт будущей структуры
ArrayResize(wsaData, sizeof(WSAData)); // изменяем его размер на размер структуры
WSAStartup(MAKEWORD(2,2), wsaData); // вызываем функцию

Данные придут в байтовый массив wsaData, из которого мы легко сможем забрать информацию, используя приведение типов.

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

b) socket()

WINAPI:
SOCKET WSAAPI socket(_In_ int af, _In_ int type, _In_ int protocol);

Поступаем аналогично – копируем данные из MSDN.

Так как мы используем TCP сокеты для IPv4, то сразу возьмем константы для параметров этой функции:

#define SOCKET           uint
#define INVALID_SOCKET   (SOCKET)(~0)
#define SOCKET_ERROR            (-1)
#define NO_ERROR         0
#define AF_INET          2 // internetwork: UDP, TCP, etc.
#define SOCK_STREAM      1
#define IPPROTO_TCP      6

c) ioctlsocket()

MQL:
int ioctlsocket(SOCKET s, int cmd, int &argp);

В ней последний аргумент заменен с указателя на адрес:

d) connect()

WINAPI:
int connect(_In_ SOCKET s, _In_ const struct sockaddr *name, _In_ int namelen);

Небольшую сложность здесь представляет передача структуры sockaddr, но мы ведь уже знаем главный принцип – заменяем структуры на байтовые массивы и используем их для передачи данных в функции WinAPI.

Забираем из MSDN без изменений саму структуру:

struct sockaddr
{
    ushort sa_family; // Address family.
    char sa_data[14]; // Up to 14 bytes of direct address.
};
Указатель на неё, как договорились, делаем через адрес массива:
#define LPSOCKADDR    char&
В примерах MSDN использует структура sockaddr_in. Она аналогичная по размеру, но параметры расписаны по-другому:
struct sockaddr_in
{
    short   sin_family;
    ushort sin_port;
    struct  in_addr sin_addr;
    char    sin_zero[8];
};
Данные для sin_addr  — это union, одним представлением которого является восьмибайтовое целое:
struct in_addr
{
   ulong s_addr;
};
В итоге описание функции в MQL выглядит так:
MQL:
int connect(SOCKET s, LPSOCKADDR name[], int namelen);

На данном этапе мы полностью готовы для создания клиентского сокета. Нам осталось совсем немного – функции приема и передачи данных.

e) recv()  и send() для TCP

Прототипы выглядят как:

WINAPI:
int send(_In_ SOCKET s, _In_ const char* buf, _In_ int len, _In_ int flags);
int recv(_In_  SOCKET s, _Out_     char* buf, _In_ int len, _In_ int flags);
MQL:
int send(SOCKET s, char& buf[], int len, int flags);
int recv(SOCKET s, char& buf[], int len, int flags);
как видно, второй параметр заменен с указателя char* на массив char& []

f) recvfrom()  и sendto() для UPD

Прототипы в MQL выглядят как:

WINAPI:
int recvfrom(_In_  SOCKET s, _Out_ char* buf, _In_ int len, _In_ int flags, _Out_  struct sockaddr *from,
  _Inout_opt_ int *fromlen);
int sendto(_In_ SOCKET s, _In_ const char* buf, _In_ int len, _In_ int flags,  _In_ const struct sockaddr *to,
  _In_       int tolen);
MQL:
int recvfrom(SOCKET s,char &buf[],int len,int flags,LPSOCKADDR from[],int &fromlen);
int sendto(SOCKET s,const char &buf[],int len,int flags,LPSOCKADDR to[],int tolen);

И в завершение — две немаловажные функции очистки и закрытия хендлов после работы:

g) closesocket() и WSACleanup()

MQL:
int closesocket(SOCKET s);
int WSACleanup();

Итоговый файл портированных WinAPI функций:
#define BYTE              uchar
#define WORD              ushort
#define DWORD             int
#define DWORD_PTR         ulong
#define SOCKET            uint

#define MAKEWORD(a, b)      ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))

#define WSADESCRIPTION_LEN      256
#define WSASYS_STATUS_LEN       128

#define INVALID_SOCKET  (SOCKET)(~0)
#define SOCKET_ERROR    (-1)
#define NO_ERROR        0
#define SOMAXCONN       128

#define AF_INET         2 // internetwork: UDP, TCP, etc.
#define SOCK_STREAM     1
#define IPPROTO_TCP     6

#define SD_RECEIVE      0x00
#define SD_SEND         0x01
#define SD_BOTH         0x02

#define IOCPARM_MASK    0x7f            /* parameters must be < 128 bytes */
#define IOC_IN          0x80000000      /* copy in parameters */
#define _IOW(x,y,t)     (IOC_IN|(((int)sizeof(t)&IOCPARM_MASK)<<16)|((x)<<8)|(y))
#define FIONBIO         _IOW('f', 126, int) /* set/clear non-blocking i/o */
//------------------------------------------------------------------    struct WSAData
struct WSAData
  {
   WORD              wVersion;
   WORD              wHighVersion;
   char              szDescription[WSADESCRIPTION_LEN+1];
   char              szSystemStatus[WSASYS_STATUS_LEN+1];
   ushort            iMaxSockets;
   ushort            iMaxUdpDg;
   char              lpVendorInfo[];
  };

#define LPWSADATA               char&
//------------------------------------------------------------------    struct sockaddr_in
struct sockaddr_in
  {
   ushort            sin_family;
   ushort            sin_port;
   ulong             sin_addr; //struct in_addr { ulong s_addr; };
   char              sin_zero[8];
  };
//------------------------------------------------------------------    struct sockaddr
struct sockaddr
  {
   ushort            sa_family; // Address family.
   char              sa_data[14]; // Up to 14 bytes of direct address.
  };
#define LPSOCKADDR      char&

struct ref_sockaddr { char ref[2+14]; };

//------------------------------------------------------------------    import Ws2_32.dll
#import "Ws2_32.dll"
int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData[]);
int WSACleanup();
int WSAGetLastError();

ushort htons(ushort hostshort);
ulong inet_addr(char& cp[]);
string inet_ntop(int Family,ulong &pAddr,char &pStringBuf[],uint StringBufSize);
ushort ntohs(ushort netshort);

SOCKET socket(int af,int type,int protocol);
int ioctlsocket(SOCKET s,int cmd,int &argp);
int shutdown(SOCKET s,int how);
int closesocket(SOCKET s);

// функции сервера
int bind(SOCKET s,LPSOCKADDR name[],int namelen);
int listen(SOCKET s,int backlog);
SOCKET accept(SOCKET s,LPSOCKADDR addr[],int &addrlen);

// функции клиента
int connect(SOCKET s,LPSOCKADDR name[],int namelen);
int send(SOCKET s,char &buf[],int len,int flags);
int recv(SOCKET s,char &buf[],int len,int flags);

#import


2. Создание клиента и сервера

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

Важно! Во всех ваших экспериментах не забывайте, что забинденный порт не освобождается автоматически при аварийном завершении кода сервера. Это приведёт к тому, что вторичное создание сокета и попытка вызвать bind приведет к ошибке – Address already in use. Для решения этой проблемы необходимо или использовать на сокете опцию SO_REUSEADDR, или (что проще всего) перезагрузить терминал. Используйте утилиты мониторинга, например, TCPViewer, для отслеживания созданных сокетов в вашей ОС.

Также вы должны понимать, что клиент может подключиться к серверу при условии, что сервер не скрыт за NAT или клиенту/серверу не блокируется данный порт в ОС или роутере.

Поэтому вы поначалу можете экспериментировать с сервером и клиентом локально на одном компьютере. Но для полноценной работы с множеством клиентов сервер надо запускать как минимум на VPS с "белым" внешним IP-адресом и открытым используемым портом наружу.

Пример 1. Рассылка разметки чарта клиентам

Начнем с простого взаимодействия – одноразовой передачи клиенту файла tpl от сервера.

В данном случае нет необходимости поддерживать цикл send/recv у клиента, так как нам нужно принять только одну порцию данных при подключении и затем разорвать связь. Причем разрывать связь будет сервер сразу же после отправки данных.

То есть, сервер при подключении к нему клиента делает Send и завершает сокет, а клиент в это время делает Recv и аналогично завершает сокет. Конечно, в более интересных случаях можно сделать постоянную трансляцию изменения чарта — скажем, моментальную синхронизацию чарта клиента и сервера. Это было бы полезным для гуру трейдинга, которые смогут показывать своим юным падаванам чарты в онлайн. Но на сегодняшний день это выполняется трансляцией видеопотока с экрана через разный вебинарный софт или же скайп. Поэтому оставим эту тему для обсуждения на форуме.

Кому и при каких обстоятельствах будет полезен этот пример кода? Например, вы ежедневно, ежечасно или ежеминутно (нужное подчеркнуть) расставляете свои индикаторы или графические объекты на чарте. При этом на отдельном графике у вас работает бежит эксперт-сервер, который слушает подключения клиентов и выдаёт им текущий tpl нужного символа и периода.

Довольные клиенты будут теперь осведомлены про цели и торговые сигналы от вас. Им достаточно будет периодически запускать скрипт, стягивающий с сервера tpl и применяющий его на чарте.

Итак, начнём с сервера. Всё работает в событии OnTimer, который играет роль функции "потока" эксперта. Раз в секунду проверяются ключевые блоки сервера: Ожидание клиента -> Отправка ему данных -> Закрытие подключения. Также проверяется активность самого сокета сервера и в случае разрыва – создание серверного сокета повторно.

К сожалению, сохранённый шаблон tpl недоступен из файловой песочницы. Поэтому чтобы его забрать из папки Profiles\Templates, необходимо снова использовать WinAPI. Эту часть подробно описывать я не стану, полный листинг вы можете увидеть ниже.

//+------------------------------------------------------------------+
//|                                                        TplServer |
//|                       programming & development - Alexey Sergeev |
//+------------------------------------------------------------------+
#property copyright "© 2006-2016 Alexey Sergeev"
#property link      "profy.mql@gmail.com"
#property version   "1.00"

#include "SocketLib.mqh"

input string Host="0.0.0.0";
input ushort Port=8080;

uchar tpl[];
int iCnt=0;
string exname="";
SOCKET server=INVALID_SOCKET;
//------------------------------------------------------------------    OnInit
int OnInit()
  {
   EventSetTimer(1);
   exname=MQLInfoString(MQL_PROGRAM_NAME)+".ex5";
   return 0;
  }
//------------------------------------------------------------------    OnDeinit
void OnDeinit(const int reason)
  {
   EventKillTimer();
   CloseClean();
  }
//------------------------------------------------------------------    OnChartEvent
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(iCnt==0) // ограничение на создание файла шаблона - не чаще раза в секунду
     {
      Print("Create TPL");
      uchar buf[];
      CreateTpl(buf);
      uchar smb[]; StringToCharArray(Symbol(),smb); ArrayResize(smb,10);
      uchar tf[]; StringToCharArray(IntegerToString(Period()),tf); ArrayResize(tf,10);

      // создаем данные на отправку
      ArrayCopy(tpl,smb, ArraySize(tpl)); // добавили имя символа
      ArrayCopy(tpl, tf, ArraySize(tpl)); // добавили значение период
      ArrayCopy(tpl,buf, ArraySize(tpl)); // добавил сам шаблон
     }
   iCnt++;
  }
//------------------------------------------------------------------    OnTimer
void OnTimer()
  {
   iCnt=0; // сбросили счетчик созданий щаблона

   if(server==INVALID_SOCKET)
      StartServer(Host,Port);
   else
     {
      // в цикле получаем всех клиентов и оправляем каждому текущий шаблон чарта
      SOCKET client=INVALID_SOCKET;
      do
        {
         client=AcceptClient(); // Accept a client socket
         if(client==INVALID_SOCKET) return;

         int slen=ArraySize(tpl);
         int res=send(client,tpl,slen,0);
         if(res==SOCKET_ERROR) Print("-Send failed error: "+WSAErrorDescript(WSAGetLastError()));
         else printf("Sent %d bytes of %d",res,slen);

         if(shutdown(client,SD_BOTH)==SOCKET_ERROR) Print("-Shutdown failed error: "+WSAErrorDescript(WSAGetLastError()));
         closesocket(client);
        }
      while(client!=INVALID_SOCKET);
     }
  }
//------------------------------------------------------------------    StartServer
void StartServer(string addr,ushort port)
  {
// инициализируем библиотеку
   char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
   int res=WSAStartup(MAKEWORD(2,2), wsaData);
   if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

// создаём сокет
   server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   if(server==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

// биндимся к адресу и порту
   Print("try bind..."+addr+":"+string(port));

   char ch[]; StringToCharArray(addr,ch);
   sockaddr_in addrin;
   addrin.sin_family=AF_INET;
   addrin.sin_addr=inet_addr(ch);
   addrin.sin_port=htons(port);
   ref_sockaddr ref=(ref_sockaddr)addrin;
   if(bind(server,ref.ref,sizeof(addrin))==SOCKET_ERROR)
     {
      int err=WSAGetLastError();
      if(err!=WSAEISCONN) { Print("-Connect failed error: "+WSAErrorDescript(err)+". Cleanup socket"); CloseClean(); return; }
     }

// ставим в неблокирующий режим
   int non_block=1;
   res=ioctlsocket(server,(int)FIONBIO,non_block);
   if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); CloseClean(); return; }

// слушаем порт и акцептируем корннекты клиентов
   if(listen(server,SOMAXCONN)==SOCKET_ERROR) { Print("Listen failed with error: ",WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

   Print("start server ok");
  }
//------------------------------------------------------------------    Accept
SOCKET AcceptClient() // Accept a client socket
  {
   if(server==INVALID_SOCKET) return INVALID_SOCKET;
   ref_sockaddr ch;
   int len=sizeof(ref_sockaddr);
   SOCKET new_sock=accept(server,ch.ref,len);
//sockaddr_in aclient=(sockaddr_in)ch; преобразовываем в структуру, если надо получить доп.информацию о подключении
   if(new_sock==INVALID_SOCKET)
     {
      int err=WSAGetLastError();
      if(err==WSAEWOULDBLOCK) Comment("\nWAITING CLIENT ("+string(TimeCurrent())+")");
      else { Print("Accept failed with error: ",WSAErrorDescript(err)); CloseClean(); return INVALID_SOCKET; }
     }
   return new_sock;
  }
//------------------------------------------------------------------    CloseClean
void CloseClean() // close socket
  {
   if(server!=INVALID_SOCKET) { closesocket(server); server=INVALID_SOCKET; }
   WSACleanup();
   Print("stop server");
  }

//------------------------------------------------------------------
#import "kernel32.dll"
int CreateFileW(string lpFileName,uint dwDesiredAccess,uint dwShareMode,uint lpSecurityAttributes,uint dwCreationDisposition,uint dwFlagsAndAttributes,int hTemplateFile);
bool ReadFile(int h,ushort &lpBuffer[],uint nNumberOfBytesToRead,uint &lpNumberOfBytesRead,int lpOverlapped=0);
uint SetFilePointer(int h,int lDistanceToMove,int,uint dwMoveMethod);
bool CloseHandle(int h);
uint GetFileSize(int h,int);
#import

#define FILE_BEGIN                              0
#define OPEN_EXISTING                   3
#define GENERIC_READ                    0x80000000
#define FILE_ATTRIBUTE_NORMAL           0x00000080
#define FILE_SHARE_READ_         0x00000001  
//------------------------------------------------------------------    LoadTpl
bool CreateTpl(uchar &abuf[])
  {
   string path=TerminalInfoString(TERMINAL_PATH);
   string name="tcpsend.tpl";

// создали шаблон
   ChartSaveTemplate(0,name);

// прочитали шаблон в массив
   path+="\\Profiles\\Templates\\"+name;
   int h=CreateFileW(path, GENERIC_READ, FILE_SHARE_READ_, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
   if(h==INVALID_HANDLE) return false;
   uint sz=GetFileSize(h,NULL);
   ushort rbuf[];
   ArrayResize(rbuf,sz); ArrayInitialize(rbuf,0);
   SetFilePointer(h,0,NULL,FILE_BEGIN); // двинули в начало
   int r; ReadFile(h,rbuf,sz,r,NULL);
   CloseHandle(h);

// убрали из шаблона имя эксперта
   string a=ShortArrayToString(rbuf);
   ArrayResize(rbuf,0);
   StringReplace(a,exname," ");
   StringToShortArray(a,rbuf);

// скопировали файл в байтовый массив (сохраняя Unicode)
   sz=ArraySize(rbuf);
   ArrayResize(abuf,sz*2);
   for(uint i=0; i<sz;++i) { abuf[2*i]=(uchar)rbuf[i]; abuf[2*i+1]=(uchar)(rbuf[i]>>8); }

   return true;
  }


Код клиента чуть проще. Так как мы уже определились, что это будет однократное получение файла, то постоянно бегущий эксперт с активным сокетом нам не нужен.

Клиент выполнен в виде скрипта. Всё происходит в событии OnStart.

//+------------------------------------------------------------------+
//|                                                        TplClient |
//|                       programming & development - Alexey Sergeev |
//+------------------------------------------------------------------+
#property copyright "© 2006-2016 Alexey Sergeev"
#property link      "profy.mql@gmail.com"
#property version   "1.00"

#include "..\Experts\SocketLib.mqh"

input string Host="127.0.0.1";
input ushort Port=8080;
SOCKET client=INVALID_SOCKET;
//------------------------------------------------------------------    OnStart
void OnStart()
  {
// инициализируем библиотеку
   char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
   int res=WSAStartup(MAKEWORD(2,2), wsaData);
   if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

// создаём сокет
   client=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   if(client==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

// подключаемся к серверу
   char ch[]; StringToCharArray(Host,ch);
   sockaddr_in addrin;
   addrin.sin_family=AF_INET;
   addrin.sin_addr=inet_addr(ch);
   addrin.sin_port=htons(Port);

   ref_sockaddr ref=(ref_sockaddr)addrin;
   res=connect(client,ref.ref,sizeof(addrin));
   if(res==SOCKET_ERROR)
     {
      int err=WSAGetLastError();
      if(err!=WSAEISCONN) { Print("-Connect failed error: "+WSAErrorDescript(err)); CloseClean(); return; }
     }

// ставим в неблокирующий режим
   int non_block=1;
   res=ioctlsocket(client,(int)FIONBIO,non_block);
   if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); CloseClean(); return; }

   Print("connect OK");

// принимаем данные
   uchar rdata[];
   char rbuf[512]; int rlen=512; int rall=0; bool bNext=false;
   while(true)
     {
      res=recv(client,rbuf,rlen,0);
      if(res<0)
        {
         int err=WSAGetLastError();
         if(err!=WSAEWOULDBLOCK) { Print("-Receive failed error: "+string(err)+" "+WSAErrorDescript(err)); CloseClean(); return; }
        }
      else if(res==0 && rall==0) { Print("-Receive. connection closed"); break; }
      else if(res>0) { rall+=res; ArrayCopy(rdata,rbuf,ArraySize(rdata),0,res); }

      if(res>=0 && res<rlen) break;
     }

// закрыли сокет
   CloseClean();

   printf("receive %d bytes",ArraySize(rdata));

// забрали из файла символ и период
   string smb=CharArrayToString(rdata,0,10);
   string tf=CharArrayToString(rdata,10,10);

// сохранили файл шаблона
   int h=FileOpen("tcprecv.tpl", FILE_WRITE|FILE_SHARE_WRITE|FILE_BIN); if(h<=0) return;
   FileWriteArray(h,rdata,20);
   FileClose(h);

// применили на чарте
   ChartSetSymbolPeriod(0,smb,(ENUM_TIMEFRAMES)StringToInteger(tf));
   ChartApplyTemplate(0,"\\Files\\tcprecv.tpl");
  }
//------------------------------------------------------------------    CloseClean
void CloseClean() // close socket
  {
   if(client!=INVALID_SOCKET)
     {
      if(shutdown(client,SD_BOTH)==SOCKET_ERROR) Print("-Shutdown failed error: "+WSAErrorDescript(WSAGetLastError()));
      closesocket(client); client=INVALID_SOCKET;
     }
   WSACleanup();
   Print("connect closed");
  }


Демонстрация работы этих кодов между собой:


Внимательный читатель заметит, что клиентский сокет можно вполне заменить на вызов MQL функции WebRequest. Для этого надо в сервер добавить лишь пару строк HTTP заголовков, а у клиентского терминала добавить в настройках разрешенный веб-адрес. Вы можете самостоятельно поэкспериментировать с этим.

Важно! В некоторых случаях замечено особое поведение терминала: при вызове функции WSACleanup MetaTrader закрывает свои собственные соединения.

Если в ваших экспериментах будет такая проблема, то закомментируйте WSAStartup и WSACleanup в коде.


Пример 2. Синхронизация торговли по символу

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

За основу возьмем код сервера и клиента из прошлого примера. Добавим функции работы с позициями.

Начнем с сервера:

//+------------------------------------------------------------------+
//|                                                     SignalServer |
//|                       programming & development - Alexey Sergeev |
//+------------------------------------------------------------------+
#property copyright "© 2006-2016 Alexey Sergeev"
#property link      "profy.mql@gmail.com"
#property version   "1.00"

#include "SocketLib.mqh"

input string Host="0.0.0.0";
input ushort Port=8081;

bool bChangeTrades;
uchar data[];
SOCKET server=INVALID_SOCKET;
SOCKET conns[];

//------------------------------------------------------------------    OnInit
int OnInit() { OnTrade(); EventSetTimer(1); return 0; }
//------------------------------------------------------------------    OnDeinit
void OnDeinit(const int reason) { EventKillTimer(); CloseClean(); }
//------------------------------------------------------------------    OnTrade
void OnTrade()
  {
   double lot=GetSymbolLot(Symbol());
   StringToCharArray("<<"+Symbol()+"|"+DoubleToString(lot,2)+">>",data); // преобразовали строку в байтовый массив
   bChangeTrades=true;
  }
//------------------------------------------------------------------    OnTimer
void OnTimer()
  {
   if(server==INVALID_SOCKET) StartServer(Host,Port);
   else
     {
      AcceptClients(); // добавили ожидающих клиентов
      if(bChangeTrades)
        {
         Print("send new posinfo to clients");
         Send(); bChangeTrades=false;
        }
     }
  }
//------------------------------------------------------------------    StartServer
void StartServer(string addr,ushort port)
  {
// инициализируем библиотеку
   char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
   int res=WSAStartup(MAKEWORD(2,2), wsaData);
   if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

// создаём сокет
   server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   if(server==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

// биндимся к адресу и порту
   Print("try bind..."+addr+":"+string(port));

   char ch[]; StringToCharArray(addr,ch);
   sockaddr_in addrin;
   addrin.sin_family=AF_INET;
   addrin.sin_addr=inet_addr(ch);
   addrin.sin_port=htons(port);
   ref_sockaddr ref=(ref_sockaddr)addrin;
   if(bind(server,ref.ref,sizeof(addrin))==SOCKET_ERROR)
     {
      int err=WSAGetLastError();
      if(err!=WSAEISCONN) { Print("-Connect failed error: "+WSAErrorDescript(err)+". Cleanup socket"); CloseClean(); return; }
     }

// ставим в неблокирующий режим
   int non_block=1;
   res=ioctlsocket(server,(int)FIONBIO,non_block);
   if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); CloseClean(); return; }

// слушаем порт и акцептируем корннекты клиентов
   if(listen(server,SOMAXCONN)==SOCKET_ERROR) { Print("Listen failed with error: ",WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

   Print("start server ok");
  }
//------------------------------------------------------------------    Accept
void AcceptClients() // Accept a client socket
  {
   if(server==INVALID_SOCKET) return;

// добавляем всех ожидающих клиентов
   SOCKET client=INVALID_SOCKET;
   do
     {
      ref_sockaddr ch; int len=sizeof(ref_sockaddr);
      client=accept(server,ch.ref,len);
      if(client==INVALID_SOCKET)
        {
         int err=WSAGetLastError();
         if(err==WSAEWOULDBLOCK) Comment("\nWAITING CLIENT ("+string(TimeCurrent())+")");
         else { Print("Accept failed with error: ",WSAErrorDescript(err)); CloseClean(); }
         return;
        }

      // ставим в неблокирующий режим
      int non_block=1;
      int res=ioctlsocket(client, (int)FIONBIO, non_block);
      if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); continue; }

      // добавляем сокет клиента в массив
      int n=ArraySize(conns); ArrayResize(conns,n+1);
      conns[n]=client;
      bChangeTrades=true; // ставим флаг, что надо отправить инфу о позиции

                          // показали инфу о клиенте
      char ipstr[23]={0};
      sockaddr_in aclient=(sockaddr_in)ch; //преобразовываем в структуру, чтоб получить доп.информацию о подключении
      inet_ntop(aclient.sin_family,aclient.sin_addr,ipstr,sizeof(ipstr)); // узнали адрес
      printf("Accept new client %s : %d",CharArrayToString(ipstr),ntohs(aclient.sin_port));
     }
   while(client!=INVALID_SOCKET);
  }
//------------------------------------------------------------------    SendClient
void Send()
  {
   int len=ArraySize(data);
   for(int i=ArraySize(conns)-1; i>=0; --i) // отправляем инфу по клиентам
     {
      if(conns[i]==INVALID_SOCKET) continue; // пропускаем закрытые
      int res=send(conns[i],data,len,0); // отправляем
      if(res==SOCKET_ERROR) { Print("-Send failed error: "+WSAErrorDescript(WSAGetLastError())+". close socket"); Close(conns[i]); }
     }
  }
//------------------------------------------------------------------    CloseClean
void CloseClean() // закрываем и очищаем работу
  {
   printf("Shutdown server and %d connections",ArraySize(conns));
   if(server!=INVALID_SOCKET) { closesocket(server); server=INVALID_SOCKET; } // закрываем сервер
   for(int i=ArraySize(conns)-1; i>=0; --i) Close(conns[i]); // закрываем клиентов
   ArrayResize(conns,0);
   WSACleanup();
  }
//------------------------------------------------------------------    Close
void Close(SOCKET &asock) // закрываем один сокет
  {
   if(asock==INVALID_SOCKET) return;
   if(shutdown(asock,SD_BOTH)==SOCKET_ERROR) Print("-Shutdown failed error: "+WSAErrorDescript(WSAGetLastError()));
   closesocket(asock);
   asock=INVALID_SOCKET;
  }
//------------------------------------------------------------------    GetSymbolLot
double GetSymbolLot(string smb)
  {
   double slot=0;
   int n=PositionsTotal();
   for(int i=0; i<n;++i)
     {
      PositionSelectByTicket(PositionGetTicket(i));
      if(PositionGetString(POSITION_SYMBOL)!=smb) continue; // фильтруем позицию текущего символа, где стоит сервер
      double lot=PositionGetDouble(POSITION_VOLUME); // взяли объём
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) lot=-lot; // учли направление
      slot+=lot; // довабили в сумму
     }
   return slot;
  }


Раз в секунду проверяются ключевые блоки сервера: подключение клиента и добавление его в общий массив -> Отправка всем новых данных. Также проверяется активность самого сокета сервера и в случае разрыва – пересоздание серверного сокета повторно.

Клиентам мы отправляем имя символа, на котором бежит эксперт и объём его позиции.

Каждая торговая операция будет отправлять символ и объём в виде сообщений:

<<GBPUSD|0.25>>
<<GBPUSD|0.00>>

Отправка происходит при каждом торговом событии, а также при подключении нового клиента.

Код клиента выполнен уже в виде эксперта, так как надо поддерживать связь постоянно. Клиент принимает новую порцию данных от сервера и добавляет её к существующей. Далее он ищет признак начала << и окончания сообщения >>, парсит его и выравнивает свой объём до серверного по указанному символу.

//+------------------------------------------------------------------+
//|                                                     SignalClient |
//|                       programming & development - Alexey Sergeev |
//+------------------------------------------------------------------+
#property copyright "© 2006-2016 Alexey Sergeev"
#property link      "profy.mql@gmail.com"
#property version   "1.00"

#include "SocketLib.mqh"
#include <Trade\Trade.mqh>

input string Host="127.0.0.1";
input ushort Port=8081;

SOCKET client=INVALID_SOCKET; // клиентский сокет
string msg=""; // очередь принятых сообщений
//------------------------------------------------------------------    OnInit
int OnInit()
  {
   if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
     {
      Alert("Client work only with Netting accounts"); return INIT_FAILED;
     }

   EventSetTimer(1); return INIT_SUCCEEDED;
  }
//------------------------------------------------------------------    OnInit
void OnDeinit(const int reason) { EventKillTimer(); CloseClean(); }
//------------------------------------------------------------------    OnInit
void OnTimer()
  {
   if(client==INVALID_SOCKET) StartClient(Host,Port);
   else
     {
      uchar data[];
      if(Receive(data)>0) // принимаем данные
        {
         msg+=CharArrayToString(data); // если что то было принято, то добавляем к общей строке
         printf("received msg from server: %s",msg);
        }
      CheckMessage();
     }
  }
//------------------------------------------------------------------    CloseClean
void StartClient(string addr,ushort port)
  {
// инициализируем библиотеку
   int res=0;
   char wsaData[]; ArrayResize(wsaData, sizeof(WSAData));
   res=WSAStartup(MAKEWORD(2,2), wsaData);
   if (res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

// создаём сокет
   client=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   if(client==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

// подключаемся к серверу
   char ch[]; StringToCharArray(addr,ch);
   sockaddr_in addrin;
   addrin.sin_family=AF_INET;
   addrin.sin_addr=inet_addr(ch);
   addrin.sin_port=htons(port);

   ref_sockaddr ref=(ref_sockaddr)addrin;
   res=connect(client,ref.ref,sizeof(addrin));
   if(res==SOCKET_ERROR)
     {
      int err=WSAGetLastError();
      if(err!=WSAEISCONN) { Print("-Connect failed error: "+WSAErrorDescript(err)); CloseClean(); return; }
     }

// ставим в неблокирующий режим
   int non_block=1;
   res=ioctlsocket(client,(int)FIONBIO,non_block);
   if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); CloseClean(); return; }

   Print("connect OK");
  }
//------------------------------------------------------------------    Receive
int Receive(uchar &rdata[]) // Receive until the peer closes the connection
  {
   if(client==INVALID_SOCKET) return 0; // если сокет еще не открыт

   char rbuf[512]; int rlen=512; int r=0,res=0;
   do
     {
      res=recv(client,rbuf,rlen,0);
      if(res<0)
        {
         int err=WSAGetLastError();
         if(err!=WSAEWOULDBLOCK) { Print("-Receive failed error: "+string(err)+" "+WSAErrorDescript(err)); CloseClean(); return -1; }
         break;
        }
      if(res==0 && r==0) { Print("-Receive. connection closed"); CloseClean(); return -1; }
      r+=res; ArrayCopy(rdata,rbuf,ArraySize(rdata),0,res);
     }
   while(res>0 && res>=rlen);
   return r;
  }
//------------------------------------------------------------------    CloseClean
void CloseClean() // close socket
  {
   if(client!=INVALID_SOCKET)
     {
      if(shutdown(client,SD_BOTH)==SOCKET_ERROR) Print("-Shutdown failed error: "+WSAErrorDescript(WSAGetLastError()));
      closesocket(client); client=INVALID_SOCKET;
     }

   WSACleanup();
   Print("close socket");
  }
//------------------------------------------------------------------    CheckMessage
void CheckMessage()
  {
   string pos;
   while(FindNextPos(pos)) { printf("server position: %s",pos); };  // забрали самое последнее изменение у сервера
   if(StringLen(pos)<=0) return;
// получили данные из сообщения
   string res[]; if(StringSplit(pos,'|',res)!=2) { printf("-wrong pos info: %s",pos); return; }
   string smb=res[0]; double lot=NormalizeDouble(StringToDouble(res[1]),2);

// синхронизируем объём
   if(!SyncSymbolLot(smb,lot)) msg="<<"+pos+">>"+msg; // если ошибка, то возвращаем сообщение в начало "потока"
  }
//------------------------------------------------------------------    SyncSymbolLot
bool SyncSymbolLot(string smb,double nlot)
  {
// синхронизируем объем сервера и клиента
   CTrade trade;
   double clot=GetSymbolLot(smb); // получили текущий лот символа
   if(clot==nlot) { Print("nothing change"); return true; } // если объемы равны, то ничего не делаем

                                                            // сначала проверили частный случай отсутствия позиции на сервере
   if(nlot==0 && clot!=0) { Print("full close position"); return trade.PositionClose(smb); }

// если на сервере позиция есть, то изменяем на клиенте
   double dif=NormalizeDouble(nlot-clot,2);
// покупаем или продаем в зависимости от разницы
   if(dif>0) { Print("add Buy position"); return trade.Buy(dif,smb); }
   else { Print("add Sell position"); return trade.Sell(-dif,smb); }
  }
//------------------------------------------------------------------    FindNextPos
bool FindNextPos(string &pos)
  {
   int b=StringFind(msg, "<<"); if(b<0) return false; // нет начала сообщения
   int e=StringFind(msg, ">>"); if(e<0) return false; // нет конца сообщения

   pos=StringSubstr(msg,b+2,e-b-2); // забрали блок информации
   msg=StringSubstr(msg,e+2); // удалили его из сообщения
   return true;
  }
//------------------------------------------------------------------    GetSymbolLot
double GetSymbolLot(string smb)
  {
   double slot=0;
   int n=PositionsTotal();
   for(int i=0; i<n;++i)
     {
      PositionSelectByTicket(PositionGetTicket(i));
      if(PositionGetString(POSITION_SYMBOL)!=smb) continue; // фильтруем позицию текущего символа, где стоит сервер
      double lot=PositionGetDouble(POSITION_VOLUME); // взяли объём
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) lot=-lot; // учли направление
      slot+=lot; // довабили в сумму
     }
   return NormalizeDouble(slot,2);
  }


И, наконец, демонстрация работы сервера и клиента в паре:


Пример 3. Сборщик тиков

Этот пример демонстрирует UDP сокеты. В нём сервер будет ждать данных от клиента по символу.

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

input string Host="0.0.0.0";
input ushort Port=8082;

SOCKET server=INVALID_SOCKET;

//------------------------------------------------------------------    OnInit
int OnInit() { EventSetMillisecondTimer(300); return 0; }
//------------------------------------------------------------------    OnDeinit
void OnDeinit(const int reason) { EventKillTimer(); CloseClean(); }
//------------------------------------------------------------------    OnTimer
void OnTimer()
  {
   if(server!=INVALID_SOCKET)
     {
      char buf[1024]={0};
      ref_sockaddr ref={0}; int len=ArraySize(ref.ref);
      int res=recvfrom(server,buf,1024,0,ref.ref,len);
      if (res>=0) // принимаем и показываем данные
         Print("receive tick from client: ", CharArrayToString(buf));
        else
        {
         int err=WSAGetLastError();
         if(err!=WSAEWOULDBLOCK) { Print("-receive failed error: "+WSAErrorDescript(err)+". Cleanup socket"); CloseClean(); return; }
        }

     }
   else // иначе стартуем сервер
     {
      // инициализируем библиотеку
      char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
      int res=WSAStartup(MAKEWORD(2,2), wsaData);
      if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

      // создаём сокет
      server=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
      if(server==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

      // биндимся к адресу и порту
      Print("try bind..."+Host+":"+string(Port));

      char ch[]; StringToCharArray(Host,ch);
      sockaddr_in addrin;
      addrin.sin_family=AF_INET;
      addrin.sin_addr=inet_addr(ch);
      addrin.sin_port=htons(Port);
      ref_sockaddr ref=(ref_sockaddr)addrin;
      if(bind(server,ref.ref,sizeof(addrin))==SOCKET_ERROR)
        {
         int err=WSAGetLastError();
         if(err!=WSAEISCONN) { Print("-Connect failed error: "+WSAErrorDescript(err)+". Cleanup socket"); CloseClean(); return; }
        }

      // ставим в неблокирующий режим
      int non_block=1;
      res=ioctlsocket(server,(int)FIONBIO,non_block);
      if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); CloseClean(); return; }

      Print("start server ok");
     }
  }
//------------------------------------------------------------------    CloseClean
void CloseClean() // закрываем и очищаем работу
  {
   printf("Shutdown server");
   if(server!=INVALID_SOCKET) { closesocket(server); server=INVALID_SOCKET; } // закрываем сервер
   WSACleanup();
  }


Код клиента тоже простой. Вся работа происходит в событии прихода тика:

input string Host="127.0.0.1";
input ushort Port=8082;

SOCKET client=INVALID_SOCKET; // клиентский сокет
ref_sockaddr srvaddr={0}; // структура для подключения к серверу
//------------------------------------------------------------------    OnInit
int OnInit()
  {
// заполняем структуру для сервера
   char ch[]; StringToCharArray(Host,ch);
   sockaddr_in addrin;
   addrin.sin_family=AF_INET;
   addrin.sin_addr=inet_addr(ch);
   addrin.sin_port=htons(Port);
   srvaddr=(ref_sockaddr)addrin;

   OnTick(); // сразу создаём сокет

   return INIT_SUCCEEDED;
  }
//------------------------------------------------------------------    OnDeinit
void OnDeinit(const int reason) { CloseClean(); }
//------------------------------------------------------------------    OnTick
void OnTick()
  {
   if(client!=INVALID_SOCKET) // если сокет уже создан, то отправляем
     {
      uchar data[]; StringToCharArray(Symbol()+" "+DoubleToString(SymbolInfoDouble(Symbol(),SYMBOL_BID),Digits()),data);
      if(sendto(client,data,ArraySize(data),0,srvaddr.ref,ArraySize(srvaddr.ref))==SOCKET_ERROR)
        {
         int err=WSAGetLastError();
         if(err!=WSAEWOULDBLOCK) { Print("-Send failed error: "+WSAErrorDescript(err)); CloseClean(); }
        }
      else
         Print("send "+Symbol()+" tick to server");
     }
   else // создаем клиентский сокет
     {
      int res=0;
      char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
      res=WSAStartup(MAKEWORD(2,2),wsaData);
      if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

      // создаём сокет
      client=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
      if(client==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

      // ставим в неблокирующий режим
      int non_block=1;
      res=ioctlsocket(client,(int)FIONBIO,non_block);
      if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); CloseClean(); return; }

      Print("create socket OK");
     }
  }
//------------------------------------------------------------------    CloseClean
void CloseClean() // close socket
  {
   if(client!=INVALID_SOCKET) { closesocket(client); client=INVALID_SOCKET; }
   WSACleanup();
   Print("close socket");
  }

А здесь демонстрируем его итоговую работу:



3. Дальнейшие пути усиления сервера

Очевидно, что данные примеры сервера, рассылающие любому клиенту информацию, не являются оптимальными. К примеру, вы наверняка захотите ограничить доступ к своей информации.  Значит, как минимум, к обязательным требованиям необходимо отнести:

  • авторизацию клиента (логин/пароль);
  • защиту от подбора пароля (бан/блокирование логина или IP).

Также вы заметили, что вся работа сервера находится только в одном потоке (в таймере одного эксперта). Это критично при большом числе подключений или объеме информации. Поэтому для оптимизации сервера стоит добавить как минимум пул экспертов (каждый со своим таймером), в которых будет происходить взаимодействие клиентских коннектов. Это в какой-то мере сделает сервер многопоточным.

Делать ли это всё в рамках MQL – решать вам. Для этого есть и другие средства, может быть более удобные. Но то, что MQL даёт преимущество в прямом доступе к торговле счета и котировкам, — это неоспоримый факт, как и открытость MQL-кода, не использующего самописные DLL.


Заключение

Как можно ещё применить сокеты в MetaTrader? Перед написанием статьи у меня было несколько идей в качестве примеров для рассмотрения:

  • индикатор настроения рынка (когда подключенные клиенты отправляют объемы своих позиций и в ответ получают итоговый объём, который пришел от всех клиентов);
  • или, например, выдача клиентам расчета индикатора с сервера (по подписке);
  • или же наоборот – клиенты будут помогать делать тяжелые расчёты (агентская сеть тестирования);
  • можно сделать сервер просто промежуточным "прокси" для обмена данными между клиентами.

Вариантов можно придумать множество. Если у вас тоже есть идеи применения — поделитесь ими в комментариях к статье. Возможно, если они окажутся интересными, мы сможем реализовать их вместе.

Удачи и профитов!


В приложенных примерах используются 64-битные обявления типов.

Прикрепленные файлы |
signalclient.mq5 (6.46 KB)
signalserver.mq5 (6.34 KB)
tickclient.mq5 (2.86 KB)
tickserver.mq5 (3.05 KB)
tplclient.mq5 (3.27 KB)
tplserver.mq5 (6.83 KB)
socketlib.mqh (33.81 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (33)
Алексей Барбашин
Алексей Барбашин | 31 янв 2017 в 20:56
А возможно ли таким же образом написать класс для работы с веб соккетами?
Sergey Lebedev
Sergey Lebedev | 22 фев 2017 в 17:39
Статья очень интересная и познавательная. Планирую использовать сокетную технологию для передачи с VPS сервера с МТ-5 теминалами на сервер с БД MS-SQL информации по финансовому состоянию счетов в конце дня/недели/месяца.
Реализовал на практике, но практика показывает что по прошествии некоторого времени пакеты начинают теряться. Причем судя по логам вначале "глохнет" серверная часть, а клиентские части в это время продолжают считать что все Ок, т.к. проведение в коде клиентской части проверки вида
if(client==INVALID_SOCKET) StartClient(Host,Port);
ничего не дает. Т.е. клиенты не могут диагностировать потерю коннекта с сервером до начала отпраки и даже в момент отправки пакетов.

Возможно необходимо дополнительно какую-то функция типа "CheckSocket" для предварительной проверки работоспособности серверного сокета? Как ее тогда реализовать ?
o_o
o_o | 22 фев 2017 в 18:23
Sergey Lebedev:

вам нужно у себя в классах добавить две вещи

1) Если клиент редко шлёт серверу данные (реже чем раз в час) то обязательно нужен пинг канала. например от клиента к сереру раз в несколько минут,  чтоб активность соединения поддерживалось в ОС. 

2) при ошибке отправки / приёма делать CloseClean() чтоб сокет ушёл в INVALID_SOCKET и вы переподключились.
Sergey Lebedev
Sergey Lebedev | 22 фев 2017 в 19:59
Правильно я понимаю что под пингом канала вы понимаете выполнение функции select для сокета, который планируется использовать?
o_o
o_o | 22 фев 2017 в 20:33
Sergey Lebedev:
Правильно я понимаю что под пингом канала вы понимаете выполнение функции select для сокета, который планируется использовать?
под пингом я понимаю отправку хотя бы одного байта с клиента на сервер
Графические интерфейсы VIII: Элемент "Файловый навигатор" (Глава 3) Графические интерфейсы VIII: Элемент "Файловый навигатор" (Глава 3)

В предыдущих главах восьмой части серии наша библиотека пополнилась несколькими классами для создания указателей для курсора мыши, календарей и древовидных списков. В настоящей статье рассмотрим элемент «Файловый навигатор», который тоже можно будет использовать в качестве части графического интерфейса MQL-приложения.

Графические интерфейсы VIII: Элемент "Древовидный список" (Глава 2) Графические интерфейсы VIII: Элемент "Древовидный список" (Глава 2)

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

Графические интерфейсы IX: Элемент "Палитра для выбора цвета" (Глава 1) Графические интерфейсы IX: Элемент "Палитра для выбора цвета" (Глава 1)

Этой статьей мы открываем девятую часть серии о разработке библиотеки для создания графических интерфейсов в среде торговых терминалов MetaTrader. Она состоит из двух глав, в которых представлены новые элементы управления и интерфейса: «Палитра для выбора цвета», «Кнопка для вызова цветовой палитры», «Индикатор выполнения» и «Линейный график».

Какие проверки должен пройти торговый робот перед публикацией в Маркете Какие проверки должен пройти торговый робот перед публикацией в Маркете

Все продукты Маркета перед публикацией проходят обязательную предварительную проверку для обеспечения единого стандарта качества. В этой статье мы расскажем о наиболее частых ошибках, которые допускают разработчики в своих технических индикаторах и торговых роботах. А также покажем как самостоятельно проверить свой продукт перед отправкой в Маркет.