Working with sockets in MQL, or How to become a signal provider

--- | 20 July, 2016

A bit of pathos

Sockets… What in our IT world could possibly exist without them? Dating back to 1982, and hardly changed up to the present time, they smoothly work for us every second. This is the foundation of network, the nerve endings of the Matrix we all live in.

In the morning, you turn on the MetaTrader terminal, and it immediately creates sockets and connects to the servers. You open a browser, and dozens of socket connections are created and closed in order to deliver the information from the Web to you, or to send e-mails, accurate time signals, gigabytes of distributed computing.

First, a little theory is required. Take a look at Wiki or MSDN. The corresponding articles describe all the necessary arsenal of structures and functions, as well as provide examples of setting up a client and a server.

In this article, translation of this knowledge into MQL will be considered.

1. Porting structures and functions from WinAPI

It is no secret that the WinAPI was designed for the C language. And the MQL language has practically become its blood brother (both in spirit and working style). Let us create an mqh file for these WinAPI functions, which will be used in the main MQL program. The order of our actions is to port them as necessary.

For the TCP client only a few functions are required:

A TCP server requires similar functions, with the exception that it will be bound to a specific port and will put the socket into listening mode. The necessary steps are:

In the case of a UDP socket, there will be fewer steps (in fact there is no "handshake" between client and server). UDP client:

Only a single bind function is added in a UDP server:

As you can see, the path is not too complicated, but the structures will need to be filled to call each function.


a) WSAStartup()

See the full description in MSDN:
WINAPI:
int WSAAPI WSAStartup(_In_ WORD wVersionRequested,  _Out_ LPWSADATA lpWSAData);
_In_, _Out_ are empty defines, which point to the scope of parameter. WSAAPI describes the rule for passing parameters, but for our purposes, it can also be left blank.

As can be seen from the documentation, a MAKEWORD macro will also be necessary to specify the required version in the first parameter, as well as a pointer to the LPWSADATA structure. The macro is not difficult to create, copy it from the header file:

#define MAKEWORD(a, b)      ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))
Moreover, all data types can also be easily defined in terms of MQL:
#define BYTE         uchar
#define WORD         ushort
#define DWORD        int
#define DWORD_PTR    ulong
Copy the WSADATA structure from MSDN. The names of most data types should be left unchanged for ease of reading, especially since they have already been defined above.
struct WSAData
{
  WORD wVersion;
  WORD  wHighVersion;
  char szDescription[WSADESCRIPTION_LEN+1];
  char szSystemStatus[WSASYS_STATUS_LEN+1];
  ushort iMaxSockets;
  ushort iMaxUdpDg;
  char  lpVendorInfo[];
}
Note that the last parameter lpVendorInfo is defined as an array in MQL (in C it was a pointer to char*). Move the array size constants to the defines as well. Finally, define the pointer to a structure as:
#define LPWSADATA        char&

Why so? It's simple. Any structure is nothing more than a limited chunk of memory. It can be represented in any way - for example, as another structure with the same size or as an array of the same size. Here, the representation as an array will be used, therefore, in all functions the char& type will be the address of the array with size corresponding to the size of the required structure. The resulting declaration of the function in MQL looks as follows:

MQL:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData[]);
This is how the function call and passing the obtained result to the WSAData structure looks like:
char wsaData[]; // byte array of the future structure
ArrayResize(wsaData, sizeof(WSAData)); // resize it to the size of the structure 
WSAStartup(MAKEWORD(2,2), wsaData); // call the function

The data will be passed to the wsaData byte array, from which it is easy to collect information using casts.

Hopefully, this part was not too difficult — after all, it is only the first function, and already so much work needs to be done. But now the basic principle is clear, so it will get easier and more interesting.

b) socket()

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

Do the same - copy data from MSDN.

Since we are using TCP sockets for IPv4, set the constants for the parameters of this function right away:

#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);

It has its last argument changed from a pointer to an address:

d) connect()

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

There is a small difficulty with passing the sockaddr structure, but the main principle is already known – replace the structures with byte arrays and use them to pass data to the WinAPI functions.

Take the structure from MSDN with no changes:

struct sockaddr
{
    ushort sa_family; // Address family.
    char sa_data[14]; // Up to 14 bytes of direct address.
};
As agreed, the pointer to it will be implemented using the array address:
#define LPSOCKADDR    char&
Examples in MSDN use the sockaddr_in structure. It is similar in size, but the parameters are declared differently:
struct sockaddr_in
{
    short   sin_family;
    ushort sin_port;
    struct  in_addr sin_addr;
    char    sin_zero[8];
};
The data for the sin_addr is a 'union', one representation of which is an eight-byte integer:
struct in_addr
{
   ulong s_addr;
};
This is how the resulting declaration of the function looks in MQL:
MQL:
int connect(SOCKET s, LPSOCKADDR name[], int namelen);

At this stage, we are fully prepared to create a client socket. Only a little is left to do - the function to receive and send data.

e) recv() and send() for TCP

The prototypes look like:

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);
as it can be seen, the second parameter was changed from a char* pointer to a char& [] array

f) recvfrom() and sendto() for UPD

The prototypes in MQL look like:

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);

And finally, the two important functions for clearing and closing the handles after work:

g) closesocket() and WSACleanup()

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

The resulting file of the ported WinAPI functions:
#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);

// server function
int bind(SOCKET s,LPSOCKADDR name[],int namelen);
int listen(SOCKET s,int backlog);
SOCKET accept(SOCKET s,LPSOCKADDR addr[],int &addrlen);

// client function
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. Creating a client and a server

After reflecting for some time on the way to implement the work with sockets for further experiments, the choice fell on the demonstration of working with its functions without classes. First, this will give a better understanding of the fact that only linear non-branching programming is involved here. Second, it will allow to refactor the functions according to any needs and any OOP ideology. Experience shows that programmers tend to thoroughly go through simple classes to understand how everything works.

Important! In all your experiments do not forget that a bound port is not released automatically when the server code is aborted. This would cause the repeated creation of a socket and an attempt of 'bind' call to result in an error – Address already in use. To solve this problem, either use the SO_REUSEADDR option on the socket, or simply restart the terminal. Use monitoring utilities, such as TCPViewer, to track the sockets created in your OS.

It is also necessary to understand that client can connect to server, provided that the server is not hidden behind a NAT, or the port for the client/server is not blocked by the OS or router.

Therefore, it is possible to experiment with the server and client locally on a single computer. But to fully operate with multiple clients, the server must be run at least on a VPS with "white" external IP address and using an open outgoing port.

Example 1. Sending the chart layout to clients

Start with a simple interaction – one-time transfer of a tpl file from the server to the client.

In this case, there is no need to maintain the send/recv loop on the client side, as it is necessary to receive only one data portion once connected and then disconnect. The connection will be closed by the server immediately once the data is sent.

That is, when a client connects to the server, the server makes a Send call and shuts down the socket. At the same time, the client makes a Recv call and similarly shuts down the socket. Of course, in the more interesting cases, it is possible to create a constant broadcast of the chart changes, such as instant synchronization of the client and the server charts. This would be useful for trading guru who can show their charts to young padawans online. But today it is done by broadcasting a video stream from the screen through different webinar software, or Skype. Therefore, this topic is best discussed on the forum.

Who and when would find this code example useful? For instance, you place your indicators or graphic objects on the chart on a daily, hourly or minutely basis. At the same time, you have a server EA running on a separate chart, which listens to client connections and gives them the current tpl of the required symbol and period.

Satisfied customers will now be informed about the targets and trading signals from you. It will be sufficient for them to periodically run a script that downloads tpl from the server and applies it to the chart.

So, let's start with the server. Everything works in the OnTimer event, which serves as the thread of the EA. Every second it checks the key blocks of the server: Listening for client -> Sending the data -> Closing connection. It also checks for activeness of the server socket itself, and in case of a connection loss - creates a server socket again.

Unfortunately, the saved tpl template is not available from the file sandbox. Therefore, in order to retrieve it from the Profiles\Templates folder, the WinAPI must be used once more. This time it will not be described in details, the full listing can be seen below.

//+------------------------------------------------------------------+
//|                                                        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) // limit on creating template file - no more than once per second
     {
      Print("Create TPL");
      uchar buf[];
      CreateTpl(buf);
      uchar smb[]; StringToCharArray(Symbol(),smb); ArrayResize(smb,10);
      uchar tf[]; StringToCharArray(IntegerToString(Period()),tf); ArrayResize(tf,10);

      // create data for sending
      ArrayCopy(tpl,smb, ArraySize(tpl)); // add symbol name
      ArrayCopy(tpl, tf, ArraySize(tpl)); // add period value
      ArrayCopy(tpl,buf, ArraySize(tpl)); // add the template itself
     }
   iCnt++;
  }
//------------------------------------------------------------------    OnTimer
void OnTimer()
  {
   iCnt=0; // reset the template creation counter

   if(server==INVALID_SOCKET)
      StartServer(Host,Port);
   else
     {
      // get all clients in a loop and send the current chart template to each client
      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)
  {
// initialize the library
   char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
   int res=WSAStartup(MAKEWORD(2,2), wsaData);
   if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

// create a socket
   server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   if(server==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

// bind to address and port
   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; }
     }

// set to nonblocking mode
   int non_block=1;
   res=ioctlsocket(server,(int)FIONBIO,non_block);
   if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); CloseClean(); return; }

// listen port and accept client connections
   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; convert into structure, if it is necessary to get additional information about the connection
   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";

// create template
   ChartSaveTemplate(0,name);

// read the template to the array
   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); // move to the top
   int r; ReadFile(h,rbuf,sz,r,NULL);
   CloseHandle(h);

// remove the EA name from the template
   string a=ShortArrayToString(rbuf);
   ArrayResize(rbuf,0);
   StringReplace(a,exname," ");
   StringToShortArray(a,rbuf);

// copy the file to a byte array (keeping 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;
  }


The client code a little simpler. Since this has been planned as a one-time receipt of a file, there is no need for a constantly running EA with active socket.

The client is implemented as a script. Everything happens within the OnStart event.

//+------------------------------------------------------------------+
//|                                                        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()
  {
// initialize the library
   char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
   int res=WSAStartup(MAKEWORD(2,2), wsaData);
   if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

// create a socket
   client=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   if(client==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

// connect to server
   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; }
     }

// set to nonblocking mode
   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 data
   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;
     }

// close socket
   CloseClean();

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

// take the symbol and period from the file
   string smb=CharArrayToString(rdata,0,10);
   string tf=CharArrayToString(rdata,10,10);

// save the template file
   int h=FileOpen("tcprecv.tpl", FILE_WRITE|FILE_SHARE_WRITE|FILE_BIN); if(h<=0) return;
   FileWriteArray(h,rdata,20);
   FileClose(h);

// apply to the chart
   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");
  }


Demonstration of interoperation of these codes:


The attentive reader would note that the client socket can be replaced with a call to the WebRequest MQL function. To do this, add only a couple of HTTP header lines to the server and allow the URL in the client terminal settings. You are free to experiment with this.

Important! In some cases, a specific behavior of the terminal has been observed: when calling the WSACleanup function, the MetaTrader closes its own connections.

If you encounter such problem in your experiments, comment WSAStartup and WSACleanup in the code.


The example 2. Synchronization of trading by symbol

In this example, the server will not close the connection when sending information. The client connection will be kept stable. Thus, any data about changes in trading on the server will be immediately sent via the client sockets. In its turn, the client that accepted a data packet immediately synchronizes its position with the position incoming from the server.

The code of the server and client from the previous example will serve as a basis. With functions for working with the positions added to it.

Let's start with the server:

//+------------------------------------------------------------------+
//|                                                     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); // convert the string to byte array
   bChangeTrades=true;
  }
//------------------------------------------------------------------    OnTimer
void OnTimer()
  {
   if(server==INVALID_SOCKET) StartServer(Host,Port);
   else
     {
      AcceptClients(); // add pending clients
      if(bChangeTrades)
        {
         Print("send new posinfo to clients");
         Send(); bChangeTrades=false;
        }
     }
  }
//------------------------------------------------------------------    StartServer
void StartServer(string addr,ushort port)
  {
// initialize the library
   char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
   int res=WSAStartup(MAKEWORD(2,2), wsaData);
   if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

// create a socket
   server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   if(server==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

// bind to address and port
   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; }
     }

// set to nonblocking mode
   int non_block=1;
   res=ioctlsocket(server,(int)FIONBIO,non_block);
   if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); CloseClean(); return; }

// listen port and accept client connections
   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;

// add all pending clients
   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;
        }

      // set to nonblocking mode
      int non_block=1;
      int res=ioctlsocket(client, (int)FIONBIO, non_block);
      if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); continue; }

      // add client socket to the array
      int n=ArraySize(conns); ArrayResize(conns,n+1);
      conns[n]=client;
      bChangeTrades=true; // flag to indicate that information about the position must be sent

                          // show client information
      char ipstr[23]={0};
      sockaddr_in aclient=(sockaddr_in)ch; // convert into structure to get additional information about the connection
      inet_ntop(aclient.sin_family,aclient.sin_addr,ipstr,sizeof(ipstr)); // get the address
      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) // send out the information to clients
     {
      if(conns[i]==INVALID_SOCKET) continue; // skip closed
      int res=send(conns[i],data,len,0); // send
      if(res==SOCKET_ERROR) { Print("-Send failed error: "+WSAErrorDescript(WSAGetLastError())+". close socket"); Close(conns[i]); }
     }
  }
//------------------------------------------------------------------    CloseClean
void CloseClean() // close and clear operation
  {
   printf("Shutdown server and %d connections",ArraySize(conns));
   if(server!=INVALID_SOCKET) { closesocket(server); server=INVALID_SOCKET; } // close the server
   for(int i=ArraySize(conns)-1; i>=0; --i) Close(conns[i]); // close the clients
   ArrayResize(conns,0);
   WSACleanup();
  }
//------------------------------------------------------------------    Close
void Close(SOCKET &asock) // close one socket
  {
   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; // filter the position of the current symbol, where the server is running
      double lot=PositionGetDouble(POSITION_VOLUME); // get the volume
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) lot=-lot; // consider the direction
      slot+=lot; // add to the sum
     }
   return slot;
  }


Every second it checks the key blocks of the server: connecting client and adding it to the total array -> sending the data. It also checks for activeness of the server socket itself, and in case of a connection loss - creates a server socket.

The name of the symbol the EA is running on and the volume of its position are sent to the clients.

Each trade operation will send the symbol and volume as messages:

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

Messages are sent at each trade event, and also when a new client connects.

This time the code of the client is implemented as an expert, as it is necessary to keep the connection active. The client accepts new portion of data from the server and adds it to the existing data. Then it looks for signs of beginning << and end >> of the message, parses it and adjusts its volume according to the one on the server for the specified symbol.

//+------------------------------------------------------------------+
//|                                                     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; // client socket
string msg=""; // queue of received messages
//------------------------------------------------------------------    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) // receive data
        {
         msg+=CharArrayToString(data); // if something was received, add it to the total string
         printf("received msg from server: %s",msg);
        }
      CheckMessage();
     }
  }
//------------------------------------------------------------------    CloseClean
void StartClient(string addr,ushort port)
  {
// initialize the library
   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; }

// create a socket
   client=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   if(client==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

// connect to server
   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; }
     }

// set to nonblocking mode
   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; // if the socket is not open

   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); };  // get the most recent change from the server
   if(StringLen(pos)<=0) return;
// receive data from the message
   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);

// synchronize volume
   if(!SyncSymbolLot(smb,lot)) msg="<<"+pos+">>"+msg; // if there is an error, return the message to the beginning of the "thread"
  }
//------------------------------------------------------------------    SyncSymbolLot
bool SyncSymbolLot(string smb,double nlot)
  {
// synchronize the server and client volumes
   CTrade trade;
   double clot=GetSymbolLot(smb); // get the current lot for the symbol
   if(clot==nlot) { Print("nothing change"); return true; } // if the volumes are equal, do nothing

                                                            // first, check the special case of no positions present on the server
   if(nlot==0 && clot!=0) { Print("full close position"); return trade.PositionClose(smb); }

// if the server has a position, change it on the client
   double dif=NormalizeDouble(nlot-clot,2);
// buy or sell depending on the difference
   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; // no beginning of the message
   int e=StringFind(msg, ">>"); if(e<0) return false; // no end of the message

   pos=StringSubstr(msg,b+2,e-b-2); // get the information block
   msg=StringSubstr(msg,e+2); // remove it from the message
   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; // filter the position of the current symbol, where the server is running
      double lot=PositionGetDouble(POSITION_VOLUME); // get the volume
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) lot=-lot; // consider the direction
      slot+=lot; // add to the sum
     }
   return NormalizeDouble(slot,2);
  }


The final demonstration of paired operation of the server and client:


Example 3. Tick collector.

This example demonstrates UPD sockets. In it, the server will wait for data on symbol from the client.

The server code is simple, as there is no need to store information on clients and listen for their connections. Checks of socket data can be accelerated using a millisecond timer:

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) // receive and display data
         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 // otherwise start the server
     {
      // initialize the library
      char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
      int res=WSAStartup(MAKEWORD(2,2), wsaData);
      if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

      // create a socket
      server=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
      if(server==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

      // bind to address and port
      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; }
        }

      // set to nonblocking mode
      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() // close and clear operation
  {
   printf("Shutdown server");
   if(server!=INVALID_SOCKET) { closesocket(server); server=INVALID_SOCKET; } // close the server
   WSACleanup();
  }


The client code is also simple. All processing takes place within the tick arrival event:

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

SOCKET client=INVALID_SOCKET; // client socket
ref_sockaddr srvaddr={0}; // structure for connecting to the server
//------------------------------------------------------------------    OnInit
int OnInit()
  {
// fill the structure for the server
   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(); // create socket immediately

   return INIT_SUCCEEDED;
  }
//------------------------------------------------------------------    OnDeinit
void OnDeinit(const int reason) { CloseClean(); }
//------------------------------------------------------------------    OnTick
void OnTick()
  {
   if(client!=INVALID_SOCKET) // if the socket is created, send
     {
      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 // create a client socket
     {
      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; }

      // create a socket
      client=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
      if(client==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

      // set to nonblocking mode
      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");
  }

And here is the demonstration of its final work:



3. Further methods to enhance the server

Obviously, these examples of servers sending out information to any client are not optimal. For instance, you may want to restrict access to your information. So, the mandatory requirements have to include at least:

Also, all the work of the server is performed only within one thread (in timer of one expert). This is critical for a large number of connections or large amounts of information. Therefore, in order to optimize the server, it is reasonable to add at least a pool of experts (each with its own timer) that will handle the interaction with client connections. This will make the server multi-threaded to some extent.

Whether or not to do this in MQL is up to you. There are other means to do that, perhaps they could be more convenient. However, the fact that MQL gives the advantage of direct access to account trading and quotes cannot be denied, as well as the openness of MQL code that does not use third-party DLL.


Conclusion

How else can sockets be used in MetaTrader? Before the article had been written, there were several ideas to be considered as examples:

There can be many options. If you have ideas on the application — share them in the article comments. Perhaps, if they are interesting, we will be able to implement them together.

I wish you good luck and big profits!