English Русский 中文 Español Deutsch 日本語
Trabalhando Com Soquetes em MQL, ou como se tornar um provedor de sinal

Trabalhando Com Soquetes em MQL, ou como se tornar um provedor de sinal

MetaTrader 5Exemplos | 6 outubro 2016, 16:39
5 780 2
---
---

Um pouco de emoção

Soquetes… O que seria do nosso mundo de TI sem eles? Datado por volta de 1982 e até o presente momento, pouco mudou, eles continuam trabalhando para nós a cada momento. Esta é a base da rede, as terminações nervosas da Matriz que todos nós vivemos.

Na parte da manhã, você ativa o terminal MetaTrader e cria os soquetes imediatamente, se conectando aos servidores. Você abre um navegador, dezenas de conexões de soquete são criadas e fechadas, a fim de entregar a informação vinda da Web para você, ou para enviar e-mails, sinais de tempo precisos ou gigabytes de computação distribuída.

Primeiramente, é necessário um pouco de teoria. Dê uma olhada no Wiki ou no MSDN. Os artigos correspondentes descrevem todo o arsenal necessário de estruturas e funções, bem como fornece exemplos de configuração de um cliente e de um servidor.

Neste artigo, será considerado a tradução deste conhecimento em MQL.

1. Portando as estruturas e funções de WinAPI

Não é segredo que o WinAPI foi projetado para a linguagem C e e MQL se tornou praticamente sua irmão de sangue (tanto em espírito como em estilo de trabalho). Vamos criar um arquivo MQH para estas funções WinAPI, que serão utilizados no programa MQL principal. A finalidade de nossas ações é a porta quando elas forem necessárias.

Para o cliente TCP são necessárias apenas algumas funções:

  • inicializar a biblioteca com WSAStartup();
  • criar uma soquete com socket();
  • configurar no modo de não bloqueio com ioctlsocket(), a fim de não congelar enquanto aguarda os dados;
  • conectar ao servidor com connect();
  • ouvir com recv() ou enviar os dados usando send()até ao final do programa ou uma ligação perdida;
  • fechar o soquete com closesocket() depois do trabalho e inicializar a biblioteca usando WSACleanup().

Um servidor TCP requer funções semelhantes, com a ressalva de que será ligado a uma porta específica e vai colocar o soquete em modo de escuta. Os passos necessários são:

  • inicializar a biblioteca - WSAStartup();
  • criar um soquete - socket();
  • configurar o modo de não bloqueio - ioctlsocket();
  • conectar a uma porta - bind();
  • colocar em modo de escuta - listen();
  • após a criação bem-sucedida do modo de escuta, accept();
  • criar conexões de clientes e continuar a trabalhar com elas no modo recv()/send() até o final do programa ou da perda de conexão;
  • depois do trabalho, feche o soquete de escuta do servidor e os clientes conectados usando closesocket() e inicialize a biblioteca com o WSACleanup().

No caso de um soquete UDP, haverá menos etapas (na verdade não existe um "aperto de mão" entre cliente e servidor). Cliente UDP:

  • inicializar a biblioteca - WSAStartup();
  • criar um soquete - socket();
  • configurar o modo de não bloqueio com o ioctlsocket(),a fim de não congelar durante a espera dos dados;
  • enviar - sendto() /receber os dados - recvfrom();
  • fechar o socket com closesocket() depois do trabalho e inicializar a biblioteca, usando o WSACleanup().

Apenas uma única função de ligação é adicionada num servidor UDP:

  • inicializar a biblioteca - WSAStartup();
  • criar um soquete - socket();
  • configurar o modo de não bloqueio - ioctlsocket();
  • ligar a uma porta - bind();
  • recebido - recvfrom() / enviado - sendto();
  • depois do trabalho, feche o soquete de escuta do servidor e os clientes conectados usando closesocket() e inicialize a biblioteca com o WSACleanup().

Como você pode ver, o caminho não é muito complicado, mas as estruturas terão de ser preenchidas para chamar cada função.


a) WSAStartup()

Veja a descrição completa em MSDN:
WINAPI:
int WSAAPI WSAStartup(_In_ WORD wVersionRequested,  _Out_ LPWSADATA lpWSAData);

_In_, _Out_ são definições vazias, que apontam para o escopo do parâmetro. O WSAAPI descreve a regra para a transmissão dos parâmetros, mas para os nossos interesses, ele também pode ser deixado em branco.

Como se pode ver através da documentação, um macro MAKEWORD também será necessário na especificação da versão exigida no primeiro parâmetro, bem como um "ponteiro" para a estrutura LPWSADATA. O macro não é difícil de se criar, copie-o a partir do arquivo de cabeçalho:

#define MAKEWORD(a, b)      ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))
Além disso, todos os tipos de dados também podem ser definidos facilmente nos termos do MQL:
#define BYTE         uchar
#define WORD         ushort
#define DWORD        int
#define DWORD_PTR    ulong

Copie a estrutura WSADATA a partir do MSDN. Os nomes da maioria dos tipos de dados não devem ser alterados para facilitar a leitura, especialmente porque eles já foram acima definidos.
struct WSAData
{
  WORD wVersion;
  WORD  wHighVersion;
  char szDescription[WSADESCRIPTION_LEN+1];
  char szSystemStatus[WSASYS_STATUS_LEN+1];
  ushort iMaxSockets;
  ushort iMaxUdpDg;
  char  lpVendorInfo[];
}

Note que o último parâmetro lpVendorInfo é definido como um array no MQL (em C era um "ponteiro" para char*). Mover o tamanho do array constantes bem como aos definidos. Finalmente, defina o ponteiro para uma estrutura como:
#define LPWSADATA        char&

Por quê? É simples. Qualquer estrutura não é nada mais do que um pedaço de memória limitado. Ele pode ser representado de qualquer maneira - por exemplo, como uma outra estrutura ou como um array do mesmo tamanho. Aqui, será utilizado a representação como um array, por conseguinte, em todas as funções do tipo char&, o endereço do array com o tamanho correspondente ao tamanho da estrutura será requerido. A declaração resultante da função no MQL é a seguinte:

MQL:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData[]);

A chamada da função e transmissão do resultado obtido para a estrutura WSAData se parece com:
char wsaData[]; // array de bytes da estrutura futura
ArrayResize(wsaData, sizeof(WSAData)); // redimensiona ao tamanho da estrutura
WSAStartup(MAKEWORD(2,2), wsaData); // chama a função

Os dados serão transmitidos para o array de bytes wsaData, a partir do qual é fácil recolher informações usando moldes.

Felizmente, esta parte não foi muito difícil — afinal, é apenas a primeira função e já temos muito trabalho a ser feito. Mas agora o princípio básico é claro, assim ficará mais fácil e interessante.

b) socket()

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

Fazer o mesmo - Copiar os dados de MSDN.

Como estamos usando soquetes TCP para IPv4, defina as constantes para os parâmetros desta função imediatamente:

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

Ele tem como seu último argumento a mudança a partir de um ponteiro para um endereço:

d) connect()

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

Há uma pequena dificuldade com a transmissão da estrutura sockaddr, mas o princípio fundamental já é conhecido - substituir as estruturas com arrays de bytes e usá-los para transmitir dados para as funções WinAPI.

Pegue a estrutura do MSDN sem alterações:

struct sockaddr
{
    ushort sa_family; // Endereço da família.
    char sa_data[14]; // Acima de 14 bytes do endereço direto.
};

Conforme combinado, o ponteiro será implementado usando o endereço do array:
#define LPSOCKADDR    char&
Exemplos de uso do MSDN na estrutura sockaddr_in. É semelhante em tamanho, mas os parâmetros são declarados de forma diferente:
struct sockaddr_in
{
    short   sin_family;
    ushort sin_port;
    struct  in_addr sin_addr;
    char    sin_zero[8];
};

Os dados para o sin_addr é uma "união", uma representação do que é um oito bytes inteiro:
struct in_addr
{
   ulong s_addr;
};

Isto é como observar a declaração resultante da função em MQL:
MQL:
int connect(SOCKET s, LPSOCKADDR name[], int namelen);

Nesta fase, estamos totalmente preparados para criar um soquete para cliente. Agora resta pouco a fazer - a função de receber e enviar dados.

e) recv() e send() para TCP

Os protótipos se parecem com:

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

como pode ser visto, o segundo parâmetro foi alterado a partir de um ponteiro char* para um arraychar& []

f) recvfrom() e sendto() para UPD

Os protótipos no MQL se parecem com: <

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


E, finalmente, as duas funções mais importantes para o limpar e finalizar os manipuladores depois do trabalho são:

g) closesocket() e WSACleanup()

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


O arquivo resultante das funções WinAPI portadas:
#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            /* os parâmetros devem ser < 128 bytes */
#define IOC_IN          0x80000000      /* cópia em parâmetros */
#define _IOW(x,y,t)     (IOC_IN|(((int)sizeof(t)&IOCPARM_MASK)<<16)|((x)<<8)|(y))
#define FIONBIO         _IOW('f', 126, int) /* configurar/limpar sem bloqueio i/o */
//------------------------------------------------------------------    estrutura 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&
//------------------------------------------------------------------    estrutura sockaddr_in
struct sockaddr_in
  {
   ushort            sin_family;
   ushort            sin_port;
   ulong             sin_addr; //struct in_addr { ulong s_addr; };
   char              sin_zero[8];
  };
//------------------------------------------------------------------    estrutura sockaddr
struct sockaddr
  {
   ushort            sa_family; // Endereço da família.
   char              sa_data[14]; // Acima de 14 bytes do endereço direto.
  };
#define LPSOCKADDR      char&

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

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

// função do servidor
int bind(SOCKET s,LPSOCKADDR name[],int namelen);
int listen(SOCKET s,int backlog);
SOCKET accept(SOCKET s,LPSOCKADDR addr[],int &addrlen);

// função do cliente
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. Criando um cliente e um servidor

Após refletir por algum tempo no caminho para implementar o trabalho com soquetes para novas experiências, a escolha recaiu sobre a demonstração de como trabalhar com as suas funções sem classes. Em primeiro lugar, isso irá permitir uma melhor compreensão do fato de que apenas a programação não-linear de ramificação é envolvida aqui. Em segundo lugar, permitirá refatorar as funções de acordo com todas as necessidades e nenhuma ideologia OOP. A experiência mostra que os programadores devem buscar aulas simples para entender como tudo funciona.

Importante! Em todas os seus "experimentos", não se esqueça que a ligação de uma porta não é liberada automaticamente quando o código do servidor é abortado. Isso resultaria na criação repetida de um soquete e uma tentativa de 'ligação' que chama o resultado com erro - "Endereço já está em uso". Para resolver este problema, use a opção SO_REUSEADDR no soquete, ou simplesmente reinicie o terminal. Use utilitários de monitoramento, tais como TCPViewer, para controlar os sockets criados no seu sistema operacional.

Também é necessário entender que o cliente pode se conectar ao servidor, desde que este não esteja escondido atrás de um NAT ou a porta para o cliente/servidor não esteja bloqueada pelo sistema operacional ou pelo roteador.

Portanto, é possível experimentar localmente com o servidor e o cliente em um único computador. Mas para operar plenamente com vários clientes, o servidor deve ser executado pelo menos num VPS com o endereço IP externo " em branco" e usando uma porta de saída aberta.

Exemplo 1. Enviando o layout gráfico para clientes

Comece com uma simples interação - única transferência de um arquivo TPL a partir do servidor ao cliente.

Neste caso, não há necessidade de manter o loop send/recv no lado do cliente, uma vez que é necessário receber apenas uma parte dos dados uma vez conectado e então desconecte. A conexão será fechada pelo servidor imediatamente assim que os dados forem enviados.

Isto é, quando um cliente se conecta ao servidor, o servidor faz uma chamada de Envio e desliga o soquete. Ao mesmo tempo, o cliente faz uma chamada Recv e semelhante à anterior, desliga-se o soquete. É claro que, nos casos mais interessantes, é possível criar uma transmissão constante das mudanças do gráfico, tais como a sincronização instantânea do cliente e os gráficos do servidor. Isso será útil para um guru de negociação que pode mostrar seus gráficos para jovens "padawans" online. Atualmente pode ser feito pela transmissão de um fluxo de vídeo da tela através de diferentes software webinar , ou mesmo pelo Skype. Portanto, este assunto é melhor discutido no fórum.

Quem e quando encontraria este exemplo útil de código? Por instantes, você coloca seus indicadores ou objetos gráficos no gráfico na base diária, horária a cada minuto. Ao mesmo tempo, você tem um servidor do EA rodando em gráfico separado, que escuta as conexões dos clientes e lhes dá a template atual do símbolo e período necessário.

Clientes satisfeitos serão informados sobre as metas e os sinais de negociação vindos de você. Será o suficiente para eles executarem periodicamente um script que baixa o template a partir do servidor e aplica ao gráfico.

Então, vamos começar com o servidor. Tudo funciona no evento OnTimer, que serve como o filamento do AE. Cada segundo ele verifica os blocos chaves do servidor: Escutando o cliente -> Enviando dados -> Encerrando conexão. Também verifica a atuação efetiva do soquete do próprio servidor, e em caso de perda da conexão - cria-se novamente um soquete de servidor.

Infelizmente, o template salvo não está disponível no arquivo sandbox. Por conseguinte, afim de recuperar na pasta Profiles\Templates, o WinAPI deve ser utilizado mais uma vez. Desta vez, não será descrito em detalhes, o perfil total pode ser visto abaixo.

//+------------------------------------------------------------------+
//|                                                        TplServer |
//|                   Programação e desenvolvimento - 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) // limite para a criação do modelo do arquivo - não mais do que uma vez por segundo
     {
      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)); // adicionar o nome do símbolo
      ArrayCopy(tpl, tf, ArraySize(tpl)); // adicionar valor do período
      ArrayCopy(tpl,buf, ArraySize(tpl)); // adicionar o próprio template 
     }
   iCnt++;
  }
//------------------------------------------------------------------    OnTimer
void OnTimer()
  {
   iCnt=0; // redefinir o contador do modelo de criação

   if(server==INVALID_SOCKET)
      StartServer(Host,Port);
   else
     {
      // obter todos os clientes num loop e enviar o template do gráfico atual para cada cliente
      SOCKET client=INVALID_SOCKET;
      do
        {
         cliente=AcceptClient(); // Aceitar o soquete de um cliente
         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);
     }
  }
//------------------------------------------------------------------   Iniciar Servidor
void StartServer(string addr,ushort port)
  {
// inicializar a biblioteca
   char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
   int res=WSAStartup(MAKEWORD(2,2), wsaData);
   if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

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

// conectar-se ao endereço e porta
   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; }
     }

// definido como modo sem bloqueio
   int non_block=1;
   res=ioctlsocket(server,(int)FIONBIO,non_block);
   if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); CloseClean(); return; }

// porta de escuta e aceita conexões dos cliente
   if(listen(server,SOMAXCONN)==SOCKET_ERROR) { Print("Listen failed with error: ",WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

   Print("start server ok");
  }
//------------------------------------------------------------------    Aceitar
SOCKET AceitarCliente() // Aceitar o soquete de um cliente
  {
   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; converter em estrutura, se for necessário obter informações adicionais sobre a conexão
   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;
  }
//------------------------------------------------------------------    Fechar/Limpar
void CloseClean() // fechar soquete
  {
   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  
//------------------------------------------------------------------    Carregar Template
bool CreateTpl(uchar &abuf[])
  {
   string path=TerminalInfoString(TERMINAL_PATH);
   string name="tcpsend.tpl";

// criar um template
   ChartSaveTemplate(0,name);

// ler o template para o 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); // mover para o topo
   int r; ReadFile(h,rbuf,sz,r,NULL);
   CloseHandle(h);

// remover o nome do EA a partir do template
   string a=ShortArrayToString(rbuf);
   ArrayResize(rbuf,0);
   StringReplace(a,exname," ");
   StringToShortArray(a,rbuf);

// copiar o arquivo para um array de bytes (mantendo o 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;
  }


O código do cliente é um pouco mais simples. Visto que já havia sido planejado como um recibo de um arquivo, não há necessidade de um EA rodar constantemente com o soquete ativo.

O cliente é implementado como um script. Tudo acontece dentro do evento OnStart.

//+------------------------------------------------------------------+
//|                                                        TplClient |
//|                   Programação e desenvolvimento - 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()
  {
// inicializar a biblioteca
   char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
   int res=WSAStartup(MAKEWORD(2,2), wsaData);
   if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

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

// conectar ao servidor
   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; }
     }

// definido como modo sem bloqueio
   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");

//receber dados
   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;
     }

// Fechamento do soquete
   CloseClean();

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

// pegue o símbolo e o período a partir do arquivo
   string smb=CharArrayToString(rdata,0,10);
   string tf=CharArrayToString(rdata,10,10);

// salvar o arquivo do template
   int h=FileOpen("tcprecv.tpl", FILE_WRITE|FILE_SHARE_WRITE|FILE_BIN); if(h<=0) return;
   FileWriteArray(h,rdata,20);
   FileClose(h);

// aplicar ao gráfico
   ChartSetSymbolPeriod(0,smb,(ENUM_TIMEFRAMES)StringToInteger(tf));
   ChartApplyTemplate(0,"\\Files\\tcprecv.tpl");
  }
//------------------------------------------------------------------    Fechar/Limpar
void CloseClean() // fechar soquete
  {
   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");
  }


A demonstração da interoperação destes códigos:


O leitor atento deve ter notado que o soquete do cliente pode ser substituído por uma chamada da função WebRequest do MQL. Para fazer isso, adicione apenas um par de linhas do cabeçalho HTTP no servidor e autoriza o URL nas configurações do terminal do cliente. Você é livre para experimentar isto.

Importante! Em alguns casos, um comportamento específico do terminal foi observado: ao chamar a função WSACleanup, o MetaTrader fecha as suas próprias conexões.

Se você encontrar esse problema em seus experimentos, comente WSAStartup e WSACleanup no código.


O exemplo 2. Sincronização de negociação pelo símbolo

Neste exemplo, o servidor não fechará a conexão ao enviar as informações. A conexão do cliente será mantida estável. Assim, quaisquer dados sobre as mudanças nas negociação no servidor serão enviadas imediatamente através dos soquetes do cliente. Por sua vez, o cliente que aceita um pacote de dados, imediatamente sincroniza a sua posição com a posição de entrada do servidor.

O código do servidor e do cliente, servirá como base a partir do exemplo anterior. Incluindo as funções para trabalhar com as posições adicionados a ele.

Começaremos com o servidor:

//+------------------------------------------------------------------+
//|                                                     SignalServer |
//|                   Programação e desenvolvimento - 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); // converter a string para array em bytes
   bChangeTrades=true;
  }
//------------------------------------------------------------------    OnTimer
void OnTimer()
  {
   if(server==INVALID_SOCKET) StartServer(Host,Port);
   else
     {
      AcceptClients(); // adicionar clientes pendentes
      if(bChangeTrades)
        {
         Print("send new posinfo to clients");
         Send(); bChangeTrades=false;
        }
     }
  }
//------------------------------------------------------------------   Iniciar Servidor
void StartServer(string addr,ushort port)
  {
// inicializar a biblioteca
   char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
   int res=WSAStartup(MAKEWORD(2,2), wsaData);
   if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

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

// conectar-se ao endereço e porta
   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; }
     }

// definido como modo sem bloqueio
   int non_block=1;
   res=ioctlsocket(server,(int)FIONBIO,non_block);
   if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); CloseClean(); return; }

// porta de escuta e aceita conexões dos cliente
   if(listen(server,SOMAXCONN)==SOCKET_ERROR) { Print("Listen failed with error: ",WSAErrorDescript(WSAGetLastError())); CloseClean(); return; }

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

// adicionar todos os clientes pendentes
   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;
        }

      // configurar o modo sem bloqueio
      int non_block=1;
      int res=ioctlsocket(client, (int)FIONBIO, non_block);
      if(res!=NO_ERROR) { Print("ioctlsocket failed error: "+string(res)); continue; }

      // adicionar soquete de cliente ao array
      int n=ArraySize(conns); ArrayResize(conns,n+1);
      conns[n]=client;
      bChangeTrades=true; // flag para indicar que as informações sobre a posição deve ser vistas

                          // show client information
      char ipstr[23]={0};
      sockaddr_in aclient=(sockaddr_in)ch; // converter em estrutura para obter informações adicionais sobre a conexão
      inet_ntop(aclient.sin_family,aclient.sin_addr,ipstr,sizeof(ipstr)); // obter o endereço
      printf("Accept new client %s : %d",CharArrayToString(ipstr),ntohs(aclient.sin_port));
     }
   while(client!=INVALID_SOCKET);
  }
//------------------------------------------------------------------    Enviar ao Cliente
void Send()
  {
   int len=ArraySize(data);
   for(int i=ArraySize(conns)-1; i>=0; --i) // enviar as informações aos clientes
     {
      if(conns[i]==INVALID_SOCKET) continue; // pular fechamento
      int res=send(conns[i],data,len,0); // enviar
      if(res==SOCKET_ERROR) { Print("-Send failed error: "+WSAErrorDescript(WSAGetLastError())+". close socket"); Close(conns[i]); }
     }
  }
//------------------------------------------------------------------    Fechar/Limpar
void CloseClean() // fechar e limpar operação
  {
   printf("Shutdown server and %d connections",ArraySize(conns));
   if(server!=INVALID_SOCKET) { closesocket(server); server=INVALID_SOCKET; } // fechar o servidor
   for(int i=ArraySize(conns)-1; i>=0; --i) Close(conns[i]); // fechar os clientes
   ArrayResize(conns,0);
   WSACleanup();
  }
//------------------------------------------------------------------    Fechar
void Close(SOCKET &asock) // fechar um soquete
  {
   if(asock==INVALID_SOCKET) return;
   if(shutdown(asock,SD_BOTH)==SOCKET_ERROR) Print("-Shutdown failed error: "+WSAErrorDescript(WSAGetLastError()));
   closesocket(asock);
   asock=INVALID_SOCKET;
  }
//------------------------------------------------------------------    Obter lote do símbolo
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; // filtrar a posição do símbolo atual, onde o servidor é executando
      double lot=PositionGetDouble(POSITION_VOLUME); // obter o volume
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) lot=-lot; // Considerar a direção
      slot+=lot; // adicionar à soma
     }
   return slot;
  }


Cada segundo ele verifica os blocos chaves do servidor: conectando e adicionando o cliente ao array total -> enviando os dados. Também verifica por atuações efetivas do soquete do próprio servidor e em caso de uma perda de conexão - cria-se um soquete de servidor.

O nome do símbolo do EA é executado e o volume de sua posição é enviado aos clientes.

Cada operação de negociação irá enviar o símbolo e volume como mensagens:

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

As mensagens são enviadas a cada evento de negociação e também quando um novo cliente se conecta.

Desta vez, o código do cliente é implementado como um expert, pois é necessário manter a conexão ativa. O cliente aceita esta nova porção de dados do servidor e adiciona-o aos dados existentes. Em seguida, ele procura por sinais do início << e fim >> da mensagem, analisa-os e ajusta o seu volume de acordo com o que está no servidor para o símbolo escolhido.

//+------------------------------------------------------------------+
//|                                                     SignalClient |
//|                   Programação e desenvolvimento - 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; // soquete do cliente
string msg=""; // fila de mensagens recebidas
//------------------------------------------------------------------    OnInit
int OnInit()
  {
   if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
     {
      Alert("O cliente trabalha apenas com as contas da rede"); 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) // receber dados
        {
         msg+=CharArrayToString(data); // se algo foi recebido, adicionar à string total
         printf("msg recebida do servidor: %s",msg);
        }
      CheckMessage();
     }
  }
//------------------------------------------------------------------    Fechar/Limpar
void StartClient(string addr,ushort port)
  {
// inicializar a biblioteca
   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; }

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

// conectar ao servidor
   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; }
     }

// definido como modo sem bloqueio
   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");
  }
//------------------------------------------------------------------    Receber
int Receive(uchar &rdata[]) // Receber até os pares fecharem a conexão
  {
   if(client==INVALID_SOCKET) return 0; // se o soquete não está aberto

   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;
  }
//------------------------------------------------------------------    Fechar/Limpar
void CloseClean() // fechar soquete
  {
   if(client!=INVALID_SOCKET)
     {
      if(shutdown(client,SD_BOTH)==SOCKET_ERROR) Print("-Shutdown failed error: "+WSAErrorDescript(WSAGetLastError()));
      closesocket(client); client=INVALID_SOCKET;
     }

   WSACleanup();
   Print("fechar soquete");
  }
//------------------------------------------------------------------    Verificar mensagem
void CheckMessage()
  {
   string pos;
   while(FindNextPos(pos)) { printf("server position: %s",pos); };  // obter a alteração mais recente do servidor
   if(StringLen(pos)<=0) return;
// receber dados a partir da mensagem
   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);

// sincronizar o volume
   if(!SyncSymbolLot(smb,lot)) msg="<<"+pos+">>"+msg; // Se houver um erro, retornar a mensagem para o início da "linha"
  }
//------------------------------------------------------------------    Sincronizar lote do símbolo
bool SyncSymbolLot(string smb,double nlot)
  {
// sincronizar os volumes de servidor e cliente
   Negociação do CTrade;
   double clot=GetSymbolLot(smb); // obter o conjunto atual para o símbolo
   if(clot==nlot) { Print("nothing change"); return true; } // se os volumes são iguais, não há nada a fazer

                                                            // em primeiro lugar, verifica o caso especial de não existir posição presente no servidor
   if(nlot==0 && clot!=0) { Print("full close position"); return trade.PositionClose(smb); }

// se o servidor tiver uma posição, alterá-la no cliente
   double dif=NormalizeDouble(nlot-clot,2);
// compra ou venda de acordo com a diferença
   if(dif>0) { Print("add Buy position"); return trade.Buy(dif,smb); }
   else { Print("add Sell position"); return trade.Sell(-dif,smb); }
  }
//------------------------------------------------------------------    Localizar próxima posição
bool FindNextPos(string &pos)
  {
   int b=StringFind(msg, "<<"); if(b<0) return false; // sem início da mensagem
   int e=StringFind(msg, ">>"); if(e<0) return false; // sem final da mensagem

   pos=StringSubstr(msg,b+2,e-b-2); // obter o bloco de informação
   msg=StringSubstr(msg,e+2); // removê-lo da mensagem
   return true;
  }
//------------------------------------------------------------------    Obter lote do símbolo
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; // filtrar a posição do símbolo atual, onde o servidor é executando
      double lot=PositionGetDouble(POSITION_VOLUME); // obter o volume
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) lot=-lot; // Considerar a direção
      slot+=lot; // adicionar à soma
     }
   return NormalizeDouble(slot,2);
  }


A demonstração final da operação emparelhada do servidor e cliente:


Exemplo 3. Coletor de ticks.

Este exemplo apresenta soquetes UDP. Neste caso o servidor vai esperar por dados do símbolo a partir do cliente.

O código do servidor é simples, já que não há necessidade de armazenar informações sobre clientes e ouvir suas conexões. As verificações de dados do soquete podem ser aceleradas usando um timer de milissegundos:

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) // receber e visualizar os dados
         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 // caso contrário, iniciar o servidor
     {
      // inicializar a biblioteca
      char wsaData[]; ArrayResize(wsaData,sizeof(WSAData));
      int res=WSAStartup(MAKEWORD(2,2), wsaData);
      if(res!=0) { Print("-WSAStartup failed error: "+string(res)); return; }

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

      // conectar endereço e porta
      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; }
        }

      // configurar o modo sem bloqueio
      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");
     }
  }
//------------------------------------------------------------------    Fechar/Limpar
void CloseClean() // fechar e limpar operação
  {
   printf("Shutdown server");
   if(server!=INVALID_SOCKET) { closesocket(server); server=INVALID_SOCKET; } // fechar o servidor
   WSACleanup();
  }


O código do cliente também é simples. Todo o processamento ocorre dentro do evento de chegada do tick :

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

SOCKET client=INVALID_SOCKET; // soquete do cliente
ref_sockaddr srvaddr={0}; // estrutura para se conectar ao servidor
//------------------------------------------------------------------    OnInit
int OnInit()
  {
// preencher a estrutura para o servidor
   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(); // criar um soquete imediatamente

   return INIT_SUCCEEDED;
  }
//------------------------------------------------------------------    OnDeinit
void OnDeinit(const int reason) { CloseClean(); }
//------------------------------------------------------------------    OnTick
void OnTick()
  {
   if(client!=INVALID_SOCKET) // se o socket é criado, enviar
     {
      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 // criar o soquete de um cliente
     {
      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; }

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

      // configurar o modo sem bloqueio
      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");
     }
  }
//------------------------------------------------------------------    Fechar/Limpar
void CloseClean() // fechar soquete
  {
   if(client!=INVALID_SOCKET) { closesocket(client); client=INVALID_SOCKET; }
   WSACleanup();
   Print("fechar soquete");
  }

Enfim, aqui está a demonstração final do trabalho:



3. Outros métodos para aprimorar o servidor

Obviamente, estes exemplos de servidores enviados com informações para qualquer cliente não são as ideais. Por exemplo, você pode querer restringir o acesso às suas informações. Assim, os requisitos obrigatórios devem incluir pelo menos:

  • autorização do cliente (login/senha);
  • proteção contra adivinhação de senha (ban/login ou bloqueio do IP).

Além disso, todo o trabalho do servidor é executado somente dentro de uma linha (no temporizador de um expert). Isto é crítico para um grande número de conecções, ou de grandes quantidades de informações. Por conseguinte, a fim de otimizar o servidor, é sensato adicionar pelo menos um grupo de experts (cada um com o seu próprio temporizador) que irá tratar da interação com as conecções do cliente. Isso fará com que o servidor multi-linhas se estenda um pouco.

Se deve ou não fazer isso no MQL, depende de você. Há outros meios para fazer isso, talvez eles poderiam ser mais convenientes. No entanto, a vantagem MQL é o acesso direto para a conta de negociação e cotações, fato que não pode ser negado, bem como a abertura do código MQL que não usa DLL de terceiros.


Conclusão

De que outra forma os soquetes poderiam ser usados no MetaTrader? Antes que o artigo fosse escrito, houveram diversas ideias a serem consideradas, como exemplos:

  • indicador das expectativas de mercado (quando os clientes conectados enviam os volumes de suas posições e obtém uma resposta com o volume total recebido de todos os clientes);
  • ou enviando os cálculos do indicador do servidor aos clientes (para assinantes);
  • ou vice-versa - os clientes irão ajudar com os cálculos pesados (agente de teste da rede);
  • é possível fazer do servidor apenas um "proxy" para a troca de dados entre os clientes.

Podem haver diversas opções. Se você tem idéias sobre a aplicação — compartilhe nos comentários do artigo. Se as mesmas forem interessantes, poderemos implementá-las juntos.

Desejo-lhe boa sorte e grandes lucros!


Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2599

Arquivos anexados |
MQL5.zip (24.71 KB)
Últimos Comentários | Ir para discussão (2)
Wemerson Guimaraes
Wemerson Guimaraes | 19 set 2017 em 15:40

Hi.

There is a way to do the inverse? Send message from client to server ?

Wagner Moraes
Wagner Moraes | 6 abr 2021 em 06:10

Olá, toda vez que retiro o client ou o server do gráfico a conexão do mt5 com a corretora cai, qual o possível problema?

A contribuição de Thomas Demark para a análise técnica A contribuição de Thomas Demark para a análise técnica
Este artigo descreve os pontos TD e as linhas TD inventadas por Thomas Demark, mostra sua aplicação na prática, bem como demostra o processo de escrita de três indicadores e dois EAs usando as ideias dele.
Estudando a Classe CCanvas. Anti-aliasing e Sombras Estudando a Classe CCanvas. Anti-aliasing e Sombras
Um algoritmo anti-aliasing da classe CCanvas é a base para todas as construções onde o antisserrilhamento está sendo usado. O artigo contém informações sobre como este algoritmo opera e fornece exemplos relevantes de visualização. Ele também abrange as sombras desenhadas dos objetos gráficos e tem um algoritmo detalhadamente desenvolvido para desenhar sombras nas canvas. A análise numérica da biblioteca ALGLIB é usada para os cálculos.
Como, na MetaTrader 5, desenvolver e depurar rapidamente sua estratégia de negociação Como, na MetaTrader 5, desenvolver e depurar rapidamente sua estratégia de negociação
Os sistemas automáticos de scalping são considerados não só o auge do trading algorítmico, mas também os mais difíceis na escrita do código. Neste artigo, nós mostraremos como -usando os recursos embutidos de depuração e teste visual- construir estratégias baseadas na análise de ticks entrantes. O desenvolvimento de regras de entrada e saída muitas vezes exige anos de negociação manual. Mas com a MetaTrader 5 você pode rapidamente verificar qualquer estratégia semelhante no histórico real.
Expert Advisor multiplataforma: Ordens Expert Advisor multiplataforma: Ordens
MetaTrader 4 e MetaTrader 5 usam regras diferentes para o processamento de pedidos de negociação. Este artigo discute a possibilidade de utilizar o objeto de classe que representa a transação para processamento pelo servidor, graças a isso o Expert Advisor poderá trabalhar com elas independentemente da versão da plataforma de negociação e o modo usado.