Trabalhando Com Soquetes em MQL, ou como se tornar um provedor de sinal
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 ulongCopie 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.
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
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Hi.
There is a way to do the inverse? Send message from client to server ?
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?