Expert Advisor MetaTrader 4 para Intercâmbio de Informações com o Mundo Exterior
more | 28 setembro, 2015
Introdução
Aqui é uma ferramenta de software que fornece Expert Advisors MetaTrader 4 com a possibilidade da criação de servidor e clientes ao mesmo tempo. Os clientes podem estabelecer conexões tanto para seus próprios servidores como para quaisquer outros tipos de servidores que fornecem conexões de protocolo peer-to-peer. A ferramenta de software oferecido consiste de dois componentes:
NetEventsProc.exe - É um processo do Windows que funciona em modo de fundo (sem console) e execuçõess (paradas), quando necessários pelo segundo componente, NetEventsProcDLL.dll. Pela solicitação de aplicativos, ele pode criar para servidor e clientes ao mesmo tempo: tanto para seus próprios servidores como para quaisquer outros tipos de servidores que fornecem conexões de protocolo peer-to-peer. Por exemplo, você pode criar clientes que trocam informações com websites. Claro, se você pode suportar o protocolo HTTP.
NetEventsProcDLL.dll - É a interface entre o aplicativo que necessita dos serviços do processo NetEventsProc.exe e o processo NetEventsProc.exe em si. Graças à interface de DLL, quaisquer programas escritos em qualquer linguagem de programação pode usar essa ferramenta de software para a troca de informações nos dois sentidos. O Expert Advisor MetaTrader 4 é apenas um exemplo do possível uso desta ferramenta de software.
Este artigo está estruturado da seguinte forma:
Iniciação Rápida: o uso prático da ferramenta de software oferecida é demonstrada primeiramente de forma simples, em seguida com exemplos mais complexos.
Especificação da Interface DLL: descrição detalhada das funções DLL e serviços fornecidos pelo processo NetEventsProc.exe ao abordar estas funções a partir de aplicativos.
Implementaçao do Projeto: Os detalhes desta implementação do projeto são esclarecidas quando possível.
O arquivo anexado NetServerClient.zip contém dois projetos Microsoft Visual Studio 2010 Ultimate: NetEventsProc - para construir NetEventsProc.exe; e NetEventsProcDLL - para construir NetEventsProcDLL.dll. Os códigos-fonte são comentados em detalhes. Você pode olhar para os detalhes da implementação e personalizar os projetos com suas necessidades específicas, se quiser.
NetEventsProc.exe implementa o servidor e os clientes usando soquetes assíncronos. Para comutar soquetes ao modo assíncrono, um dos possíveis métodos de operar no modo assíncrono é usado: soquetes de ligação aos eventos de rede WSAEventSelect (h_Socket, h_Event, FD_ALL_EVENTS).
Este projeto baseia-se no trabalho fundamental de um grande mestre, Elmue.
1. Iniciação Rápida
1.1. O arquivo exe.zip
Extrair este arquivo. Você vai encontrar os seguintes componentes:
NetEventsProcDLL.dll - Coloque na pasta "C:\Windows\System32\".
NetEventsProc.exe - crie a pasta "C:\NetEventsProc\" e coloque este componente nesta pasta. NetEventsProcDLL.dll irá procurar o módulo NetEventsProc.exe exatamente nesta pasta!
Os seguintes componentes deste arquivo proporcionam mecanismos para importar funções DLL a partir do arquivo NetEventsProcDLL.dll para aplicativos:
ImportNetEventsProcDLL.mqh - Protótipos das funções importadas para o Expert Advisor MetaTrader 4 a partir do NetEventsProcDLL.dll. Coloque esse arquivo na pasta de dados do terminal - "MetaTrader 4\experts\include\".
cNetEventsProcDLL.h - definição da classe C++ que contém todos os protótipos das funções DLL a partir do NetEventsProcDLL.dll. Incluindo essa classe no programa C++ permite importar todas as funções DLL a partir do NetEventsProcDLL.dll. Para os programas escritos em outras linguagens de programação, você deve ou importar cada função separadamente ou reescrever a definição desta classe, respectivamente.
NetEventsProcDLL.lib - módulo incluídos em programas que importam funções DLL a partir do NetEventsProcDLL.dll no modo de ligação dinâmica em tempo de carga (compilar com: /EHsc/link NetEventsProcDLL.lib).
Isto, na verdade, conclui o processo de configuração. Agora você pode escrever o Expert Advisors MetaTrader 4 e aplicativos em qualquer linguagem de programação usando funções DLL para a criação de servidor e clientes.
A fim de não divagar, damos os códigos-fonte dos arquivos ImportNetEventsProcDLL.mqh e cNetEventsProcDLL.h aqui mesmo. O arquivo de cabeçalho ImportNetEventsProcDLL.mqh contém protótipos das funções DLL importadas do programa NetEventsProcDLL.dll e duas funções de serviços adicionais:
string GetErrMsg(int s32_Error); string FormatIP(int IP);
A função GetErrMsg converte para texto os códigos de retorno da funções DLL. A função FormatIP converte a representação binária do endereço IP para o formato de texto padrão como "93.127.110.161". Você deve colocar o arquivo ImportNetEventsProcDLL.mqh na pasta de dados terminal - "MetaTrader 4\experts\include\".
Aqui está o código fonte do ImportNetEventsProcDLL.mqh (somente uma parte do arquivo é fornecida, correspondendo diretamente à definição dos protótipos de funções DLL importadas):
// ImportNetEventsProcDLL.mqh #import "NetEventsProcDLL.dll" // Apenas para Clientes: int ConnectTo(string ps8_ServerIP, // entrada- string ps8_ServerIP = "0123456789123456" int s32_Port, // entrada int& h_Client[]); // saída- int h_Client[1] int ConnectClose(int h_Client); // entrada // // Apenas para o servidor: int ServerOpen(int s32_Port); // entrada int GetAllConnections(int& ph_Client[], // saída - int ph_Client[62] int& ps32_ClientIP[], // saída - int ps32_ClientIP[62] int& ps32_ClientCount[]); // saída - int ps32_ClientCount[1] int DisconnectClient(int h_Client); // entrada int ServerClose(); // // Para ambos: clientes e servidor int SendToInt (int h_Client, // entrada int& ps32_SendBuf[], // entrada int s32_SendBufLen); // entrada - tamanho do array SendBuf[] no elemento int int SendToDouble(int h_Client, // entrada double& pd_SendBuf[], // entrada int s32_SendBufLen); // entrada - o tamanho do array SendBuf[] no elemento double int SendToString(int h_Client, // entrada string ps8_SendBuf, // entrada int s32_SendBufLen); // entrada - tamanho da string SendBuf no elemento char int ReadFromInt (int h_Client, // entrada int& ps32_ReadBuf[], // entrada int s32_ReadBufLen, // entrada - o tamanho do array ReadBuf[] no elemento int int& ps32_ReadLen[]); // saida- int ps32_ReadLen[1] - conta que realmente lê os dados no elemento int int ReadFromDouble(int h_Client, // entrada double& pd_ReadBuf[], // entrada int s32_ReadBufLen, // entrada - o tamanho do array ReadBuf[] no elemento double int& ps32_ReadLen[]); // saída - int ps32_ReadLen[1] - conta que realmente lê os dados no elemento double int ReadFromString(int h_Client, // entrada string ps8_ReadBuf, // entrada int s32_ReadBufLen, // entrada - tamanho da string ReadBuf no elemento char int& ps32_ReadLen[]); // saída - int ps32_ReadLen[1] - conta que realmente lê os dados no elemento char // #import //*************************************************************************************** ... ... ... // Obter uma leitura humana para mensagem de erro a um código de erro API string GetErrMsg(int s32_Error) { ... .. } // Converte DWORD IP para string IP string FormatIP(int IP) { ... ... ... }
O arquivo cNetEventsProcDLL.h tem definição de classe C++ com todas as funções DLL importadas do arquivo NetEventsProcDLL.dll. Aqui está o código fonte desse arquivo:
//+---------------------------------------------------------------------------+ //| cNetEventsProcDLL.h | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ // cNetEventsProcDLL.h #pragma once #define EXPFUNC __declspec(dllexport) class cNetEventsProcDLL { public: static BOOL MessageDLL_PROCESS_ATTACH(void); static BOOL MessageDLL_PROCESS_DETACH(void); //******************************************************************************************************************* static EXPFUNC int __stdcall ConnectTo(char* ps8_ServerIP, // entrada - ps8_ServerIP = "0123456789123456" int s32_Port, //entrada int* ph_Client); //saída - int ph_Client[1] static EXPFUNC int __stdcall ConnectClose(int h_Client); //entrada static EXPFUNC int __stdcall ServerOpen(int s32_Port); //entrada static EXPFUNC int __stdcall GetAllConnections(int* ph_Client, // saída - int ph_Client[62] int* ps32_ClientIP, // saída - int ps32_ClientIP[62] int* ps32_ClientCount); // saída - int ps32_ClientCount[1] static EXPFUNC int __stdcall DisconnectClient(SOCKET h_Client); // entrada static EXPFUNC int __stdcall ServerClose(); static EXPFUNC int __stdcall SendToInt(SOCKET h_Client, // entrada int* ps32_SendBuf, // entrada int s32_SendBufLen); // entrada - o tamanho de matriz SendBuf[] no elemento int static EXPFUNC int __stdcall SendToDouble(SOCKET h_Client, // entrada double* pd_SendBuf, // entrada int s32_SendBufLen); // entrada - o tamanho de array SendBuf[] no elemento double static EXPFUNC int __stdcall SendToString(SOCKET h_Client, // entrada char* ps8_SendBuf, // entrada int s32_SendBufLen); // o tamanho da string SendBuf no elemento char static EXPFUNC int __stdcall ReadFromInt(SOCKET h_Client, // entrada int* ps32_ReadBuf, // entrada int s32_ReadBufLen, // o tamanho do array ReadBuf[] no elemento int int* ps32_ReadLen); // saída- int ps32_ReadLen [1] - contagem real de dados lidos no elemento int static EXPFUNC int __stdcall ReadFromDouble(SOCKET h_Client, // entrada double* pd_ReadBuf, // entrada int s32_ReadBufLen, // o tamanho do array ReadBuf[] no elemento double int* ps32_ReadLen); // saída - int ps32_ReadLen[1] - conta que realmente lê os dados no elemento double static EXPFUNC int __stdcall ReadFromString(SOCKET h_Client, // entrada char* ps8_ReadBuf, // entrada int s32_ReadBufLen, // o tamanho do array ReadBuf[] no elemento char int* ps32_ReadLen); // saída - int ps32_ReadLen[1] - conta que realmente lê os dados no elemento char //******************************************************************************************************************* protegido: static DWORD SendTo(SOCKET h_Client, char* ps8_SendBuf, INT s32_SendBufLen); static DWORD ReadFrom(SOCKET h_Client, char* ps8_ReadBuf, INT s32_ReadBufLen, INT& s32_ReadLen); };
1.2. O arquivo FastStart.zip
Este arquivo contém os códigos-fonte de todos os programas usados em exemplos de demonstração. Programas em C++ são representados como projetos do Microsoft Visual Studio 2010: Client e EchoServer. Os códigos-fonte de programas MQL4 também estão presentes neste arquivo juntamente com o arquivo ImportNetEventsProcDLL.mqh, usado para importar funções DLL aos programas MQL4. Coloque esse arquivo na pasta "MetaTrader 4\experts\include\".
Em uma discussão mais aprofundada, os códigos-fonte de todos esses programas estão listados no texto. Vamos considerar 3 exemplos que demonstram o trabalho com todas as funções DLL nas linguagens de programação MQL4 e C++ :
Seção 1.2.1. demonstra a troca de informações entre o Expert Advisor МetaТrader 4 -servidor e cliente C++.
Seção 1.2.2. demonstra a troca de informações entre Servidor C++ e Expert Advisor МetaТrader 4 -cliente.
Seção 1.2.3. demonstra a troca de informações entre Expert Advisors MetaTrader 4. Um desses EAs atua como um servidor que fornece a outros Expert Advisors МetaТrader 4 - indicadores (os Expert Advisors-clientes) com os valores do indicador. Em outras palavras, temos implementado a distribuição dos valores de um indicador "seguro" aos clientes.
1.2.1. Expert Advisor МetaТrader 4-servidor & C++ programa-cliente
Considere esta tarefa tradicional de troca de informações entre Expert Advisor МetaТrader 4 e o programa C++:
EchoServer.mq4 - Expert Advisor que funciona como um servidor echo.
Client.cpp - Programa C++ que atua como um cliente para este Expert Advisor-servidor.
Cliente C++ lê as mensagens inseridas pelo usuário no console e as envia ao Expert Advisor. O Expert Advisor recebe essas mensagens, as exibe na janela do terminal e as envia de volta ao destinatário. As imagens abaixo ilustram esta ideia:
Aqui está o código fonte do Expert Advisor MetaTrader 4. EchoServer.mq4 que atua como um servidor echo:
//+---------------------------------------------------------------------------+ //| EchoServer.mq4 | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ #property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more" #property link "https://www.mql4.com/ ru/users/more" #include <ImportNetEventsProcDLL.mqh> /*int ServerOpen(int s32_Port); // entrada */ /*int ServerClose(); */ /*int GetAllConnections(int& ph_Client[], // saída - int ph_Client[62] int& ps32_ClientIP[], // saída - int ps32_ClientIP[62] int& ps32_ClientCount[]); // saída - int ps32_ClientCount[1] */ /*int SendToString(int h_Client, // entrada string ps8_SendBuf, // entrada int s32_SendBufLen); // entrada - tamanho da string SendBuf no elemento char */ /*int ReadFromString(int h_Client, // entrada string ps8_ReadBuf, // entrada int s32_ReadBufLen, // entrada - tamanho da string ReadBuf no elemento char int& ps32_ReadLen[]); // saída - int ps32_ReadLen[1] - conta que realmente lê os dados no elemento char */ // Variáveis Globals int s32_Error; int i; int s32_Port = 2000; bool b_ServerOpened = false; // para GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount) int ph_Client [62]; int ps32_ClientIP [62]; int ps32_ClientCount[1 ]; // para int ReadFromString(h_Client, ps8_Buf, s32_BufLen, ps32_ReadLen) // para int SendToString (h_Client, ps8_Buf, s32_BufLen) string ps8_Buf = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; int s32_BufLen; int ps32_ReadLen[1]; //+------------------------------------------------------------------+ //| Função de inicialização do expert | //+------------------------------------------------------------------+ int init() { //---- s32_BufLen = StringLen(ps8_Buf); if (!b_ServerOpened) { s32_Error = ServerOpen(s32_Port); Print("ServerOpen() return is: ",GetErrMsg(s32_Error)); if (s32_Error == OK) { b_ServerOpened = true; Print("Server is Opened and Waiting fo Client connection requests..."); } } //---- return(0); } //+------------------------------------------------------------------+ //| Função de desinicialização do expert | //+------------------------------------------------------------------+ int deinit() { //---- if (b_ServerOpened) { s32_Error = ServerClose(); Print("ServerClose() return is: ",GetErrMsg(s32_Error)); if (s32_Error == OK) b_ServerOpened = false; } //---- return(0); } int start() { //---- if (!b_ServerOpened) return(0); s32_Error = GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount); if (s32_Error != 0) { Print("GetAllConnections(...) return is: ",GetErrMsg(s32_Error)); return(1); } Print("ClientCount = ", ps32_ClientCount[0]); for (i = 0; i<ps32_ClientCount[0]; i++) { Print("h_Client = ", ph_Client[i], " Client IP = ", FormatIP(ps32_ClientIP[i])); s32_Error = ReadFromString(ph_Client[i], ps8_Buf, s32_BufLen, ps32_ReadLen); Print("ReadFromString(",ph_Client[i],",...) return is: ", GetErrMsg(s32_Error)); if (s32_Error == 0) { Print("ReadFromString(",ph_Client[i],",...) ps32_ReadLen = ",ps32_ReadLen[0]); if (ps32_ReadLen[0] > 0) { Print("ReadFromString(",ph_Client[i],",...) Read data is: ", StringSubstr(ps8_Buf,0,ps32_ReadLen[0])); s32_Error = SendToString(ph_Client[i], ps8_Buf, StringLen(StringSubstr(ps8_Buf,0,ps32_ReadLen[0]))); Print("SendToString(", ph_Client[i],",...) return is: ",GetErrMsg(s32_Error)); } } } //---- return(0); } //+------------------------------------------------------------------+
E aqui está o código fonte do Client.cpp - programa C++ que age como um cliente ao Expert Advisor-servidor:
//+---------------------------------------------------------------------------+ //| Client.cpp | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ // Client.cpp #include <winsock2.h> #pragma comment(lib, "ws2_32") #include <iostream> #pragma comment(lib, "NetEventsProcDLL") // NetEventsProcDLL.lib placed in ...\FastStart\Client\ #include "cNetEventsProcDLL.h" // LocalIp = 0x6401a8c0 -> 192.168.1.100 // Retorna uma lista de todos os locais do IP deste computador (múltiplo de IP se várias placas de rede) DWORD GetLocalIPs(char s8_IpList[][20], int &s32_IpCount); /* Este é um Cliente simples. Ele se conecta ao Servidor Echo, envie sua entrada para o Servidor Echo e lê Echo a partir do Servidor. ATENÇÃO !!! Se o seu desejo é se conectar ao servidor local, não use "127.0.0.1" !!! ATENÇÃO !!! Você pode obter todos os seus locais de IP por meio da função GetLocalIPs(...), aqui temos um exemplo de chamada: // Lista de IP Local char s8_IpList[10][20]; int s32_IpCount; DWORD u32_Err = GetLocalIPs(s8_IpList, s32_IpCount); if (u32_Err) { printf("Get local IP's error !, no local IP means no network available..."); return 1; } printf("\nLocal IP's list:\n"); for (int i = 0; i<s32_IpCount; i++) printf("\n%s\n",s8_IpList[i]); // */ // Este é o servidor que deseja se conectar... #define SERVER_IP "192.168.1.5" //este é o meu local de IP obtido por meio da função GetLocalIPs(...), // não use chamada de auto-retorno do endereço "127.0.0.1"!!! #define PORT 2000 int main() { // Lista de IP Local char s8_IpList[10][20]; int s32_IpCount; DWORD u32_Err = GetLocalIPs(s8_IpList, s32_IpCount); if (u32_Err) { printf("Get local IP's error !, no local IP means no network available..."); return 1; } printf("\nLocal IP's list:\n"); for (int i = 0; i<s32_IpCount; i++) printf("\n%s\n",s8_IpList[i]); // char s8_ServerIP[] = SERVER_IP; int s32_Port = PORT; int ph_Client[1]; int h_Client; DWORD u32_Error = cNetEventsProcDLL::ConnectTo(SERVER_IP, PORT, ph_Client); if (u32_Error) { printf("\nConnectTo(...) failed with error: %d\n", u32_Error); return 1; } else printf("\nConnectTo(...) OK, ph_Client[0] = : %d\n", ph_Client[0]); h_Client = ph_Client[0]; // A conexão foi estabelecida com sucesso! Vamos ter alguns dados SendTo (...) nesta ligação ... char ps8_SendData[200]; int s32_SendDataLen;; char ps8_ReadBuf[1025]; DWORD s32_ReadBufLen = 1025; int ps32_ReadLen[1]; while(true) { std::cout << "\nDigite algo para enviar ao servidor ou Sair 'q':\n" << std::endl; std::cin.getline(ps8_SendData, 200); s32_SendDataLen = strlen(ps8_SendData); OemToCharBuff(ps8_SendData, ps8_SendData, s32_SendDataLen); if (ps8_SendData[0] == 'q') { u32_Error = cNetEventsProcDLL::ConnectClose(h_Client); if (u32_Error) { printf("\nConnectClose(...) failed with error: %d\n", u32_Error); break; } else { printf("\nConnectClose(...) OK.\n"); break; } } u32_Error = cNetEventsProcDLL::SendToString(h_Client, ps8_SendData, s32_SendDataLen); switch (u32_Error) { case 0: printf("\nSendTo(...) OK"); printf("\nSendTo(%d...) sent %d bytes\n", h_Client, s32_SendDataLen); CharToOemBuff(ps8_SendData, ps8_SendData, s32_SendDataLen); printf("\nSendTo(%d...) Sent Data: %s\n",h_Client, ps8_SendData); printf("Waiting now for Echo...."); break; case ERROR_INVALID_PARAMETER: printf("\nSendTo(%d...) return is: ERROR_INVALID_PARAMETER(%d)\n",h_Client, u32_Error); printf("\nERROR_INVALID_PARAMETER -> One of this parms or more: h_Client, ps8_SendData, u32_SendDataLen is invalid...\n"); break; case WSAEWOULDBLOCK: printf("\nSendTo(%d...) return is: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error); printf("\nWSAEWOULDBLOCK -> The data will be send after the next FD_WRITE event, do nouthing\n"); break; case WSA_IO_PENDING: printf("\nSendTo(%d...) return is: WSA_IO_PENDING(%d)\n",h_Client, u32_Error); printf("\nWSA_IO_PENDING -> Error: A previous Send operation is still pending. This data will not be sent, try latter\n"); break; default: printf("\nSendTo(%d...)failed with severe error: %d\n",h_Client, u32_Error); // Erro grave -> abortar evento loop printf("\nConnection was closed !\n"); break; }; if (u32_Error == 0 || u32_Error == WSAEWOULDBLOCK) { int ReadLen = 0; while(!ReadLen) { u32_Error = cNetEventsProcDLL::ReadFromString(h_Client, ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen); if (u32_Error) { printf("\nReadFromString(%d...) failed with error: %d\n", h_Client, u32_Error); break; } ReadLen = ps32_ReadLen[0]; } if (u32_Error) { printf("\nReadFromString(%d...) failed with error: %d\n", h_Client, u32_Error); } else { printf("\nReadFromString(%d...) OK, read %d bytes\n", h_Client, ReadLen); } if (ReadLen > 0) { CharToOemBuff(ps8_ReadBuf, ps8_ReadBuf, s32_SendDataLen); ps8_ReadBuf[ReadLen] = 0; printf("\nReadFromString(%d...) Read Data: %s\n", h_Client, ps8_ReadBuf); } } } return 0; } // LocalIp = 0x6401a8c0 -> 192.168.1.100 // Retorna uma lista de todos os locais do IP deste computador (múltiplo de IP se várias placas de rede) DWORD GetLocalIPs(char s8_IpList[][20], int &s32_IpCount) { // Winsock versão 2.0 está disponível em TODOS os sistemas operacionais Windows // exceto o Windows 95 que vem como o Winsock 1.1 WSADATA k_Data; DWORD u32_Error = WSAStartup(MAKEWORD(2,0), &k_Data); if (u32_Error) return u32_Error; int ps32_IpList[20]; char s8_Host[500]; if (gethostname(s8_Host, sizeof(s8_Host)) == SOCKET_ERROR) return WSAGetLastError(); struct hostent* pk_Host = gethostbyname(s8_Host); if (!pk_Host) return WSAGetLastError(); s32_IpCount = 0; for (DWORD i=0; TRUE; i++) { if (!pk_Host->h_addr_list[i]) break; // A lista IP está terminada e zerada ps32_IpList[i] = *((DWORD*)pk_Host->h_addr_list[i]); s32_IpCount++; } if (!s32_IpCount) return WSAENETDOWN; // Significa que não há nenhuma rede IP local disponível for (int i = 0; i<s32_IpCount; i++) { BYTE* pu8_Addr = (BYTE*)&ps32_IpList[i]; sprintf(s8_IpList[i],"%d.%d.%d.%d",pu8_Addr[0], pu8_Addr[1], pu8_Addr[2], pu8_Addr[3]); } return 0; }
Para executar este exemplo de demonstração você precisa:
Coloque o arquivo EchoServer.mq4 na pasta de dados terminal e fazer a compilação: "МetaТrader 4\experts\".
Abra o projeto Client no Microsoft Visual Studio 2010 Ultimate e faça a construção do mesmo usando a configuração Release. Se você quiser construir o projeto em outra IDE, não se esqueça de especificar o módulo NetEventsProcDLL.lib como uma entrada adicional ao editor (compilação com: /EHsc/link NetEventsProcDLL.lib).
Execute o módulo Client.exe. Você vai ter um erro com o código 10057 e na lista dos IPs locais de seu computador. Corrija o seguinte seqüência de caracteres no código-fonte client.cpp
#define SERVER_IP "192.168.1.5"
substituindo "192.168.1.5" com a primeira (ou única) IP local e compilar o projeto novamente.
Execute o Expert Advisor EchoServer em qualquer gráfico no terminal MetaTrader 4. Se tudo for feito corretamente, a janela de terminal exibirá as seguintes mensagens:"ServerOpen() return is: OK", "Server is Opened and Waiting for Client connection requests..." ( "Retorno do ServerOpen() está: OK", "Servidor esta Aberto e Aguardando solicitação de conexão pelo Cliente..."). Em seguida, em cada tick o Expert Advisor verifica as solicitações da conexão, conecta, lê as mensagens recebidas a partir de cada conexão e envia uma cópia da mensagem de volta ao destinatário. O Expert Advisor exibe todas as informações atuais na janela do terminal.
Agora você pode executar o programa Client.exe construído na etapa 3.
Você pode executar várias cópias deste programa e ver como o Expert Advisor troca informações com todas as cópias.
Se você definir o IP global do computador onde o Expert Advisor-servidor está sendo executado, em vez de seu IP local no passo 3, você pode executar o Client.exe e comunicar com o Expert Advisor em qualquer outro computador, estando ligado à Internet. Não se esqueça de desativar todas as proteções de firewall atuais.
1.2.2. Programa C++-servidor & Expert Advisor MetaTrader 4-cliente
Considere esta tarefa tradicional de troca de informações entre Expert Advisor МetaТrader 4 e o programa C++:
EchoServer.cpp - Programa C++ atua como um servidor echo.
Client.mq4 - Expert Advisor atua como um cliente para este servidor C++.
O Expert Advisor-cliente lê cotações do símbolo sobre o qual ele está sendo executado e envia estas cotações ao servidor do C++. Servidor C++ retorna de volta a cotação ao destinatário, este recebe e exibe a cotação na janela do terminal. As imagens abaixo ilustram esta ideia:
Aqui está o código fonte do Expert Advisor МetaТrader 4, Client.mq4, que atua como um cliente:
//+---------------------------------------------------------------------------+ //| Client.mq4 | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ #property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more" #property link "https://www.mql4.com/ ru/users/more" #include <ImportNetEventsProcDLL.mqh> /*int ConnectTo(string ps8_ServerIP, // entrada - string ps8_ServerIP = "0123456789123456" int s32_Port, // entrada int& ph_Client[]); // saída - int ph_Client[1] */ /*int SendToDouble(int h_Client, // entrada double& pd_SendBuf[], // entrada int s32_SendBufLen); // entrada - o tamanho da array SendBuf[] no elemento double */ /*int ReadFromDouble(int h_Client, // entrada double& pd_ReadBuf[], // entrada int s32_ReadBufLen, // entrada - o tamanho de array ReadBuf[] no elemento double int& ps32_ReadLen[]); // saída - int ps32_ReadLen[1] - conta que realmente lê os dados no elemento double */ /*int ConnectClose(int h_Client); // entrada */ // Variáveis Globals int s32_Error; int i; // para int ConnectTo(ps8_ServerIP, s32_Port, ph_Client); // saída - int h_Client[1] string ps8_ServerIP = "192.168.1.5"; // meu IP local int s32_Port = 2000; int ph_Client[1]; bool b_ConnectTo = false; // para int SendToDouble(ph_Client[0], pd_Buf, s32_BufLen); // para int ReadFromDouble(ph_Client[0], pd_Buf, s32_BufLen, ps32_ReadLen); double pd_Buf[1]; int s32_BufLen = 1; int ps32_ReadLen[1]; //+------------------------------------------------------------------+ //| Função de inicialização do expert | //+------------------------------------------------------------------+ int init() { //---- if (!b_ConnectTo) { s32_Error = ConnectTo(ps8_ServerIP, s32_Port, ph_Client); Print("ConnectTo(...) return is: ",GetErrMsg(s32_Error)); Print("ConnectTo(...) handle is: ",ph_Client[0]); if (s32_Error == OK) { b_ConnectTo = true; Print("Client now is ConnectTo the Server: ",ps8_ServerIP); } } //---- return(0); } //+------------------------------------------------------------------+ //| Função de desinicialização do expert | //+------------------------------------------------------------------+ int deinit() { //---- if (b_ConnectTo) { s32_Error = ConnectClose(ph_Client[0]); Print("ConnectClose(...) return is: ",GetErrMsg(s32_Error)); if (s32_Error == OK) b_ConnectTo = false; } //---- return(0); } int start() { //---- if (!b_ConnectTo) return(0); RefreshRates(); double pd_Value[1]; pd_Value[0] = NormalizeDouble(Bid,Digits); s32_Error = SendToDouble(ph_Client[0], pd_Value, s32_BufLen); if (s32_Error != 0) { Print("SendToDouble(",ph_Client[0],"...) return is: ",GetErrMsg(s32_Error)); return(1); } else Print("SendToDouble(",ph_Client[0],"...) return is: OK"); s32_Error = ReadFromDouble(ph_Client[0], pd_Buf, s32_BufLen, ps32_ReadLen); if (s32_Error != 0) { Print("ReadFromDouble(",ph_Client[0],"...) return is: ", GetErrMsg(s32_Error)); return(1); } else Print("ReadFromDouble(",ph_Client[0],"...) return is: OK"); pd_Buf[0] = NormalizeDouble(pd_Buf[0],Digits); if (ps32_ReadLen[0] > 0) Print("Read doble value is: ", pd_Buf[0]); //---- return(0); } //+------------------------------------------------------------------+
E aqui está o código fonte do EchoServer.cpp, Programa C++ que atua como um servidor echo para o Expert Advisor-cliente:
//+---------------------------------------------------------------------------+ //| EchoServer.cpp | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ // EchoServer.cpp #include <winsock2.h> #pragma comment(lib, "NetEventsProcDLL") // NetEventsProcDLL.lib placed in ...\FastStart\EchoServer\ #include <iostream> #include <conio.h> #include "cNetEventsProcDLL.h" BOOL FormatIP(DWORD u32_IP, char* s8_IP); int main() { int s32_Port = 2000; // Tente criar uma escuta do servidor na porta 2000 // Voce pode alterar a porta DWORD u32_Error = cNetEventsProcDLL::ServerOpen(s32_Port); if (u32_Error) { printf("\nServerOpen() failed with error: %d\n", u32_Error); return 1; } else printf("\nServerOpen() fine, we now are waiting for connections...\n"); DWORD u32_Count = 0; DWORD u32_CountOld = 0; double pd_Buf[1025]; DWORD u32_BufLen = 1025; int ps32_ReadLen[1]; pd_Buf[0] = 0; int ph_Client[62]; int ps32_ClientIP[62]; int ps32_ClientCount[1]; while(!kbhit()) { u32_Error = cNetEventsProcDLL::GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount); if (u32_Error) { printf("\nGetAllConnections(...) failed with error: %d\n", u32_Error); break; } else u32_Count = ps32_ClientCount[0]; if (u32_Count != u32_CountOld) { u32_CountOld = u32_Count; printf("\nNumber of connections now = %d\n", u32_Count); printf("# h_Connect (peer IP)\n"); for (DWORD i = 0; i<u32_Count; i++) { char s8_IP[20]; sprintf(s8_IP, "%s","123456789012345"); FormatIP(ps32_ClientIP[i], s8_IP); printf("%d %d (%s)\n", i, ph_Client[i], s8_IP); } } for (DWORD i = 0; i<u32_Count; i++) { u32_Error = cNetEventsProcDLL::ReadFromDouble(ph_Client[i], pd_Buf, u32_BufLen, ps32_ReadLen); if (u32_Error) { printf("ReadFromDouble(%d...) failed with error: %d\n", ph_Client[i], u32_Error); } if (ps32_ReadLen[0]) { printf("ReadFromDouble(%d...) read %d double values\n", ph_Client[i], ps32_ReadLen[0]); printf("\nReadFromDouble(%d...) Read Data: %9.5f\n", ph_Client[i], pd_Buf[0]); } if (ps32_ReadLen[0]) { u32_Error = cNetEventsProcDLL::SendToDouble(ph_Client[i], pd_Buf, ps32_ReadLen[0]); if (u32_Error) { printf("SendToDouble(%d...) failed with error: %d\n", ph_Client[i], u32_Error); } else { printf("SendToDouble(%d...) sent %d double values\n", ph_Client[i], ps32_ReadLen[0]); printf("SendToDouble(%d...) sent Data: %9.5f\n",ph_Client[i], pd_Buf[0]); } } } } u32_Error = cNetEventsProcDLL::ServerClose(); if (u32_Error) { printf("\nServerClose() failed with error: %d\n", u32_Error); return 1; } else printf("\nServerClose() fine...\n"); Sleep(10000); return 0; } BOOL FormatIP(DWORD u32_IP, char* s8_IP) { DWORD u32_Len = strlen(s8_IP); if ( u32_Len < 15) return FALSE; BYTE* pu8_Addr = (BYTE*)&u32_IP; sprintf(s8_IP,"%d.%d.%d.%d",pu8_Addr[0], pu8_Addr[1], pu8_Addr[2], pu8_Addr[3]); return TRUE; }
Para executar este exemplo de demonstração você precisa:
Coloque o arquivo Client.mq4 na pasta de dados do terminal: "MetaTrader 4\experts\", atribua IP local (obtido no exemplo anterior 1.2.1.) na string
string ps8_ServerIP = "192.168.1.5";
e execute a compilação. Se o servidor C++ será executado em outro computador, então cole aqui o IP global deste computador. Não esqueça de desativar todas as proteções existentes no seu firewall.
Abra o projeto Client no Microsoft Visual Studio 2010 Ultimate e faça a construção do mesmo usando a configuração Release. Se você quiser construir o projeto em outra IDE, não se esqueça de especificar o módulo NetEventsProcDLL.lib como uma entrada adicional ao editor (compilação com: /EHsc/link NetEventsProcDLL.lib).
Execute o arquivo EchoServer.exe do Servidor C++. Se tudo for feito corretamente, o console irá exibir a seguinte mensagem: "ServerOpen() fine, we now are waiting for connections....." (ServerOpen() pronto, agora esperando por conexões.....). Ao pressionar qualquer tecla, fechará o servidor e encerrar o programa.
Execute o cliente Client.mq4 em qualquer gráfico do terminal МetaТrader 4.
Você pode executar o cliente simultaneamente em vários gráficos, ou vários terminais ou computadores diferentes.
Observe como trabalha o servidor C++ e os clientes МetaТrader 4. Pressione qualquer tecla no console do servidor C++, então o programa C++ fechará o servidor e encerrará.
1.2.3. Expert Advisor МetaТrader 4-indicador (Expert Advisor-servidor) & Cliente МetaТrader 4-indicador
O Expert Advisor-indicator (Expert Advisor-servidor) fornece os Clientes-indicadores com os valores do indicador. Neste caso, estes são os valores do indicador padrão iEnvelops (...). Este exemplo pode ter um valor prático para a distribuição dos valores do indicador "protegido" a todos os assinantes-clientes.
As imagens abaixo ilustram esta ideia:
Aqui está o código fonte do Expert Advisor МetaТrader 4, ServerSendInd.mq4, atuando como o fornecedor dos valores do indicador iEnvelops(...):
//+---------------------------------------------------------------------------+ //| ServerSendInd.mq4 | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ #property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more" #property link "https://www.mql4.com/ ru/users/more" #include <ImportNetEventsProcDLL.mqh> /*int ServerOpen(int s32_Port); // entrada */ /*int ServerClose(); */ /*int GetAllConnections(int& ph_Client[], // saída - int ph_Client[62] int& ps32_ClientIP[], // saída - int ps32_ClientIP[62] int& ps32_ClientCount[]); // saída - int ps32_ClientCount[1] */ /*int ReadFromString(int h_Client, // entrada string ps8_ReadBuf, // entrada int s32_ReadBufLen, // entrada - tamanho da string ReadBuf no elemento char int& ps32_ReadLen[]); // saída - int ps32_ReadLen[1] - conta que realmente lê os dados no elemento char */ /*int SendToDouble(int h_Client, // entrada double& pd_SendBuf[], // entrada int s32_SendBufLen); // entrada - Tamanho do Array SendBuf[] no elemento double */ // Variáveis Globals int s32_Error; int i; int s32_Port = 2000; bool b_ServerOpened = false; // para GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount) int ph_Client [62]; int ps32_ClientIP [62]; int ps32_ClientCount[1 ]; // para int ReadFromString(h_Client, ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen) string ps8_ReadBuf = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; int s32_ReadBufLen; int ps32_ReadLen[1]; // para int SendToDouble(ph_Client[0], pd_SendBuf, s32_SendBufLen); #define BARS_COUNT 200 double pd_SendBuf [BARS_COUNT]; //BARS_COUNT/2 Bars for each of 2 line int s32_SendBufLen = BARS_COUNT; //+------------------------------------------------------------------+ //| Função de inicialização do expert | //+------------------------------------------------------------------+ int init() { //---- s32_ReadBufLen = StringLen(ps8_ReadBuf); if (!b_ServerOpened) { s32_Error = ServerOpen(s32_Port); Print("ServerOpen() return is: ",GetErrMsg(s32_Error)); if (s32_Error == OK) { b_ServerOpened = true; Print("Server is Opened and Waiting for Clients connection requests..."); } } //---- return(0); } //+------------------------------------------------------------------+ //| Função de desinicialização do expert | //+------------------------------------------------------------------+ int deinit() { //---- if (b_ServerOpened) { s32_Error = ServerClose(); Print("ServerClose() return is: ",GetErrMsg(s32_Error)); if (s32_Error == OK) b_ServerOpened = false; } //---- return(0); } int start() { //---- if (!b_ServerOpened) return(0); s32_Error = GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount); if (s32_Error != 0) { Print("GetAllConnections(...) failed with error: ",GetErrMsg(s32_Error)); return(1); } Print("ClientCount = ", ps32_ClientCount[0]); for (i = 0; i<ps32_ClientCount[0]; i++) { Print("h_Client = ", ph_Client[i], " Client IP = ", FormatIP(ps32_ClientIP[i])); s32_Error = ReadFromString(ph_Client[i], ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen); if (s32_Error != 0) { Print("ReadFromString(",ph_Client[i],") failed with error: ", GetErrMsg(s32_Error)); continue; } if (ps32_ReadLen[0] > 0) { // ps8_ReadBuf = "EURUSDMinuts" i.e. "Symbol+Timeframe" string Sym = StringSubstr(ps8_ReadBuf,0,6); int TimeFrame = StrToInteger(StringSubstr(ps8_ReadBuf,6,ps32_ReadLen[0]-6)); int k; for (k = 0; k<BARS_COUNT/2; k++) { while(true) { double UpperLine_k = iEnvelopes(Sym, TimeFrame, 14, MODE_SMA, 0, PRICE_CLOSE, 0.1, MODE_UPPER, k); if (GetLastError() != 0) continue; else break; } while(true) { double LowerLine_k = iEnvelopes(Sym, TimeFrame, 14, MODE_SMA, 0, PRICE_CLOSE, 0.1, MODE_LOWER, k); if (GetLastError() != 0) continue; else break; } pd_SendBuf[k] = UpperLine_k; pd_SendBuf[k+BARS_COUNT/2] = LowerLine_k; } s32_Error = SendToDouble(ph_Client[i], pd_SendBuf, s32_SendBufLen); if (s32_Error != 0) { Print("SendToDouble(",ph_Client[i],") failed with error: ", GetErrMsg(s32_Error)); continue; } } } //---- return(0); } //+------------------------------------------------------------------+
E aqui está o código fonte Cliente-indicador, ClientIndicator.mq4, que recebe os valores do indicador iEnvelops(...) do Expert Advisor ServerSendInd.mq4:
//+---------------------------------------------------------------------------+ //| ClientIndicator.mq4 | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ #property copyright "Copyright © 2012, https://www.mql4.com/ en/users/more" #property link "https://www.mql4.com/ ru/users/more" #include <ImportNetEventsProcDLL.mqh> /*int ConnectTo(string ps8_ServerIP, // entrada - string ps8_ServerIP = "0123456789123456" int s32_Port, // entrada int& ph_Client[]); // saída - int ph_Client[1] */ /* /*int ConnectClose(int h_Client); // entrada */ /*int SendToString(int h_Client, // entrada string ps8_SendBuf, // entrada int s32_SendBufLen); // entrada - tamanho da string SendBuf no elemento char */ /*int ReadFromDouble(int h_Client, // entrada double& pd_ReadBuf[], // entrada int s32_ReadBufLen, // entrada - o tamanho de array ReadBuf[] no elemento double int& ps32_ReadLen[]); // saída - int ps32_ReadLen[1] - conta que realmente lê os dados no elemento double */ // Variáveis Globals int s32_Error; int i; // para int ConnectTo(ps8_ServerIP, s32_Port, ph_Client); // out - int h_Client[1] string ps8_ServerIP = "192.168.1.5"; // mine local IP int s32_Port = 2000; int ph_Client[1]; bool b_ConnectTo = false; // para int SendToString (h_Client, ps8_SendBuf, s32_SendBufLen) string ps8_SendBuf = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; int s32_SendBufLen; // para int ReadFromDouble(ph_Client[0], pd_ReadBuf, s32_ReadBufLen, ps32_ReadLen); #define BARS_COUNT 200 double pd_ReadBuf [BARS_COUNT]; int s32_ReadBufLen = BARS_COUNT; int ps32_ReadLen[1]; string Indicator_Name = "Envelopes: "; //---- #property indicator_chart_window #property indicator_buffers 2 #property indicator_color1 Blue #property indicator_color2 Red double UpperLine[]; double LowerLine[]; //+------------------------------------------------------------------+ //| Função inicializar do indicador personalizado | //+------------------------------------------------------------------+ int init() { //---- indicadores SetIndexStyle (0,DRAW_LINE); SetIndexBuffer(0,UpperLine); SetIndexStyle (1,DRAW_LINE); SetIndexBuffer(1,LowerLine); s32_SendBufLen = StringLen(ps8_SendBuf); if (!b_ConnectTo) { s32_Error = ConnectTo(ps8_ServerIP, s32_Port, ph_Client); Print("ConnectTo(...) return is: ",GetErrMsg(s32_Error)); Print("ConnectTo(...) handle is: ",ph_Client[0]); if (s32_Error == OK) { b_ConnectTo = true; Print("Client now is ConnectTo the Server: ",ps8_ServerIP); } } //---- return(0); } //+------------------------------------------------------------------+ //| Função desinicializar do indicador personalizado | //+------------------------------------------------------------------+ int deinit() { //---- if (b_ConnectTo) { s32_Error = ConnectClose(ph_Client[0]); Print("ConnectClose(...) return is: ",GetErrMsg(s32_Error)); if (s32_Error == OK) b_ConnectTo = false; } //---- return(0); } //+------------------------------------------------------------------+ //| Função de iteração do indicador personalizado | //+------------------------------------------------------------------+ int start() { //---- if (!b_ConnectTo) return(0); string Sym = Symbol(); int TimeFrame = Period(); ps8_SendBuf = Symbol() + DoubleToStr(Period(),0); s32_Error = SendToString(ph_Client[0], ps8_SendBuf, StringLen(ps8_SendBuf)); if (s32_Error != 0) { Print("SendToString(", ph_Client[0],",...) failed with error: ",GetErrMsg(s32_Error)); return (1); } s32_Error = ReadFromDouble(ph_Client[0], pd_ReadBuf, s32_ReadBufLen, ps32_ReadLen); if (s32_Error != 0) { Print("ReadFromDouble(",ph_Client[0],"...) return is: ", GetErrMsg(s32_Error)); return(1); } if (ps32_ReadLen[0] == 0) return (0); //-------------------------------------------------------------------- int Counted_bars = IndicatorCounted(); // Número de barras calculados i = Bars - Counted_bars - 1; // Índice da primeira não calculada if (i > BARS_COUNT/2-1) i = BARS_COUNT/2-1; // Calcula contagem especificada, se houverem muitas barras //----------------------------------------------------------------------- for (i = BARS_COUNT/2-1; i >= 0; i--) { UpperLine [i] = pd_ReadBuf[i]; LowerLine [i] = pd_ReadBuf[i+BARS_COUNT/2]; } return; } // fim do int start() //--------------------------------------------------------------------
Para executar este exemplo de demonstração você precisa:
Coloque o arquivo ServerSendInd.mq4 na pasta de dados terminal e faça a compilação: "МetaТrader 4\experts\".
Coloque o arquivo ClientIndicator.mq4 na pasta de dados terminal "МetaТrader 4\experts\indicators\" e atribua o IP local (obtido no exemplo 1.2.1.) para a string:
string ps8_ServerIP = "192.168.1.5";
Se o ServerSendInd for executado em outro computador, então cole aqui o IP global deste computador. Não esqueça de desativar todas as proteções existentes no seu firewall. Faça a Compilação.
Execute o ServerSendInd. Se tudo for feito corretamente, o console irá exibir a seguinte mensagem: "ServerOpen() fine, we now are waiting for connections....." (ServerOpen() pronto, agora esperando por conexões.....).
Em qualquer gráfico do terminal МetaТrader execute o indicador ClientIndicator.mq4. Duas linhas de indicador aparecerão no gráfico. Se você executar o indicador Envelopes padrão no mesmo gráfico, certifique-se de que ambas as linhas do nosso indicador coincidem com as linhas de indicador padrão.
Você pode executar o indicador ClientIndicator simultaneamente em vários gráficos, ou vários terminais ou computadores diferentes.
- Observe como trabalha o servidor ServerSendInd e ClientIndicator. Tente mudar o timeframe de gráfico com o indicador ClientIndicator. O servidor ServerSendInd será criado imediatamente para enviar valores do indicador para este timeframe.
2. Especificação da Interface DLL
Nesta seção iremos descrever em detalhes todas as funções DLL e parâmetros que são necessários para chamá-las. Todas as funções retornam zero em caso de execução bem-sucedida. Caso contrário, as funções retornam códigos de erro API Winsock2. Todas as funções DLL exportadas são dadas na declaração da classe C++, cNetEventsProcDLL.h, portanto iremos fornecer o código-fonte deste arquivo:
//+---------------------------------------------------------------------------+ //| cNetEventsProcDLL.h | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ //--- cNetEventsProcDLL.h #pragma once #define EXPFUNC __declspec(dllexport) //--- class cNetEventsProcDLL { public: static BOOL MessageDLL_PROCESS_ATTACH(void); static BOOL MessageDLL_PROCESS_DETACH(void); //--- static EXPFUNC int __stdcall ConnectTo(char *ps8_ServerIP, // entrada - ps8_ServerIP = "0123456789123456" int s32_Port, // entrada int* ph_Client); // saída - int ph_Client[1] //--- static EXPFUNC int __stdcall ConnectClose(int h_Client); // entrada //--- static EXPFUNC int __stdcall ServerOpen(int s32_Port); // entrada //--- static EXPFUNC int __stdcall GetAllConnections(int* ph_Client, // saída - int ph_Client[62] int* ps32_ClientIP, // saída - int ps32_ClientIP[62] int* ps32_ClientCount); // saída - int ps32_ClientCount[1] //--- static EXPFUNC int __stdcall DisconnectClient(SOCKET h_Client); // entrada //--- static EXPFUNC int __stdcall ServerClose(); //--- static EXPFUNC int __stdcall SendToInt(SOCKET h_Client, // entrada int *ps32_SendBuf, // entrada int s32_SendBufLen); // entrada - Tamanho do Array SendBuf[] no elemento int //--- static EXPFUNC int __stdcall SendToDouble(SOCKET h_Client, // entrada double* pd_SendBuf, // entrada int s32_SendBufLen); // entrada - Tamanho do Array SendBuf[] no elemento double //--- static EXPFUNC int __stdcall SendToString(SOCKET h_Client, // entrada char* ps8_SendBuf, // entrada INT s32_SendBufLen); // o tamanho da string SendBuf no elemento char //--- static EXPFUNC int __stdcall ReadFromInt(SOCKET h_Client, // entrada int *ps32_ReadBuf, // entrada int s32_ReadBufLen, // o tamanho do array ReadBuf[] no elemento int int *ps32_ReadLen); // saída - int ps32_ReadLen[1] - conta real da leitura de dados no elemento int //--- static EXPFUNC int __stdcall ReadFromDouble(SOCKET h_Client, // entrada double *pd_ReadBuf, // entrada int s32_ReadBufLen, // o tamanho do array ReadBuf[] no elemento double int *ps32_ReadLen); // saída - int ps32_ReadLen[1] - conta real da leitura de dados no elemento double //--- static EXPFUNC int __stdcall ReadFromString(SOCKET h_Client, // entrada char *ps8_ReadBuf, // entrada int s32_ReadBufLen, // o tamanho do array ReadBuf[] no elemento char int* ps32_ReadLen); // saída - int ps32_ReadLen[1] - conta que realmente lê os dados no elemento char //--- protegido: static DWORD SendTo(SOCKET h_Client,char *ps8_SendBuf,INT s32_SendBufLen); static DWORD ReadFrom(SOCKET h_Client,char *ps8_ReadBuf,INT s32_ReadBufLen,INT &s32_ReadLen); };
Agora vamos considerar todas as funções DLL na ordem que aparecem neste arquivo:
ConnectTo - solicitação ao servidor para criar conexão:
static EXPFUNC int __stdcall ConnectTo(char* ps8_ServerIP, // entrada - ps8_ServerIP = "0123456789123456" int s32_Port, // entrada int* ph_Client); // entrada- int ph_Client[1]
Parâmetros da função:
char * ps8_ServerIP - endereço IP do servidor que você deseja se conectar (por exemplo, "93.127.110.162"). Se o servidor é local, então especificar o IP, não "127.0.0.1", mas o IP obtido tal como foi descrito no exemplo 1.2.1.
int s32_Port - número da porta que o servidor "escuta para".
int* ph_Client - manipulador de conexão é colocado nessa variável, se a função for completada com sucesso. Este manipulador deve ser usado em todas as operações subsequentes a esta conexão.
Se bem sucedida, a função retornará verdadeira, caso contrário ele retorna o código de erro Winsock2 API. Exemplo C++:
DWORD u32_Error = cNetEventsProcDLL::ConnectTo(SERVER_IP, PORT, ph_Client); if (u32_Error) { printf("\nConnectTo(...) failed with error: %d\n", u32_Error); return 1; } else printf("\nConnectTo(...) OK, ph_Client[0] = : %d\n", ph_Client[0]); int h_Client = ph_Client[0];
ConnectClose - solicitação ao servidor para fechar a conexão.
static EXPFUNC int __stdcall ConnectClose(int h_Client); // entrada
Parâmetros da função:
int h_Client - manipulador de conexão que deve ser fechado. Se bem sucedida, a função retornará verdadeira, caso contrário ele retorna o código de erro Winsock2 API.
Exemplo C++:
int u32_Error = cNetEventsProcDLL::ConnectClose(h_Client); if (u32_Error) printf("\nConnectClose(...) failed with error: %d\n", u32_Error); else printf("\nConnectClose(...) OK.\n");
ServerOpen - solicitação para criar o servidor.
static EXPFUNC int __stdcall ServerOpen(int s32_Port); //entrada
Parâmetros da função:
int s32_Port - número da porta que o servidor irá "escutar para", esperando as solicitaçoes dos clientes. Se bem sucedida, a função retornará verdadeira, caso contrário ele retorna o código de erro Winsock2 API.
Exemplo C++:
int u32_Error = cNetEventsProcDLL::ServerOpen(s32_Port); if (u32_Error) { printf("\nServerOpen() failed with error: %d\n", u32_Error); return 1; } else printf("\nServerOpen() fine, we now are waiting for connections...\n");
GetAllConnections - solicitação ao servidor para obter informações sobre todas as conexões atuais.
static EXPFUNC int __stdcall GetAllConnections(int* ph_Client, // saída - int ph_Client[62] int* ps32_ClientIP, // saída - int ps32_ClientIP[62] int* ps32_ClientCount); // saída - int ps32_ClientCount[1]
Parâmetros da função:
int ph_Client[62] - saída do array para que o servidor colocado trate de todas as conexões atuais.
int ps32_ClientIP[62] - saída do array para que o servidor coloque endereços IP de todas as conexões atuais. Para converter esses endereços para o formato padrão, como "92.127.110.161", use a função string FormatIP(int IP) para Expert Advisor МetaТrader 4 ou qualquer função semelhante, fornecida em exemplos de programas C++. O número 62 em tamanho do array é especificado não por coincidência, mas porque designa o limite do número de conexões possíveis (clientes) por um servidor.
int * ps32_ClientCount - servidor coloca o número das conexões atuais para esta variável, isto é, o número de elementos nos arrays acima mencionados.
Se bem sucedida, a função retornará verdadeira, caso contrário ele retorna o código de erro Winsock2 API. Exemplo C++:
int ph_Client[62]; int ps32_ClientIP[62]; int ps32_ClientCount[1]; int u32_Error = cNetEventsProcDLL::GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount); if (u32_Error) { printf("\nGetAllConnections(...) failed with error: %d\n", u32_Error); } else int u32_Count = ps32_ClientCount[0];
DisconnectClient - solicitação ao servidor para fechar conexão com um de seus clientes.
static EXPFUNC int __stdcall DisconnectClient(int h_Client); // entrada
Parâmetros da função:
int h_Client - manipulador de conexão que deve ser fechado. Se bem sucedida, a função retornará verdadeira, caso contrário ele retorna o código de erro Winsock2 API.
Exemplo C++:
int u32_Error = cNetEventsProcDLL::DisconnectClient(h_Client); if (u32_Error) printf("\nDisconnectClient(...) failed with error: %d\n", u32_Error); else printf("\nDisconnectClient(...) OK.\n");
ServerClose - solicitação para fechar o servidor.
static EXPFUNC int __stdcall ServerClose();
Ao fechar o servidor, todas as conexões atuais serão fechadas, de modo que cada cliente receberá o código de retorno "no connection" (nenhuma conexão), em resposta para qualquer operação. Se bem sucedida, a função retornará verdadeira, caso contrário ele retorna o código de erro Winsock2 API. Exemplo C++:
int u32_Error = cNetEventsProcDLL::ServerClose(); if (u32_Error) printf("\nServerClose() failed with error: %d\n", u32_Error); else printf("\nServerClose() OK.\n");
WSAEWOULDBLOCK - Operação foi bem sucedida, os dados ainda não foram enviados ao destinatário, mas serão a um tempo adequado. Não são necessárias ações do usuário.
WSA_IO_PENDING - Entrega de dados anterior ainda não está completa, o usuário deve tentar enviar dados posteriormente. Esta é uma situação normal, então considera-se que a função foi executada com sucesso.
SendToInt - solicitação para enviar bloco de dados (array tipo int) através da conexão atual.
static EXPFUNC int __stdcall SendToInt(SOCKET h_Client, // entrada int* ps32_SendBuf, // in int s32_SendBufLen); // entrada - o tamanho do array SendBuf[] no elemento int
Parâmetros da função:
SOCKET h_Client - manipulador da conexão atual.
int ps32_SendBuf[s32_SendBufLen] - bloco único (array tipo int) que precisa ser enviado ao cliente.
int s32_SendBufLen - tamanho do array.
Se bem sucedida, a função retornará verdadeira, caso contrário ele retorna o código de erro Winsock2 API. Exemplo C++:
int ps32_SendBuf[200]; int s32_SendBufLen=200; int u32_Error = cNetEventsProcDLL::SendToInt(h_Client, ps32_SendBuf, s32_SendBufLen); switch (u32_Error) { case 0: printf("\nSendTo(...) OK"); break; case WSAEWOULDBLOCK: printf("\nSendTo(%d...) return is: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error); printf("\nWSAEWOULDBLOCK -> The data will be send after the next FD_WRITE event, do nouthing\n"); break; case WSA_IO_PENDING: printf("\nSendTo(%d...) return is: WSA_IO_PENDING(%d)\n",h_Client, u32_Error); printf("\nWSA_IO_PENDING -> Error: A previous Send operation is still pending. This data will not be sent, try latter\n"); break; default: printf("\nSendTo(%d...)failed with severe error: %d\n",h_Client, u32_Error); break; };
SendToDouble - solicitação de envio de blocos de dados (array tipo double) através da conexão atual.
static EXPFUNC int __stdcall SendToDouble(SOCKET h_Client, // entrada double* pd_SendBuf, // entrada int s32_SendBufLen); // entrada - Tamnho do array SendBuf[] array no elemento int
Parâmetros da função:
SOCKET h_Client - manipulador da conexão atual.
double pd_SendBuf[s32_SendBufLen] - Bloco único (array tipo double) que necessita ser enviado ao cliente.
int s32_SendBufLen - tamanho do array.
Se bem sucedida, a função retornará verdadeira, caso contrário ele retorna o código de erro Winsock2 API. Exemplo C++:
double pd_SendBuf[200]; int s32_SendBufLen=200; int u32_Error = cNetEventsProcDLL::SendToDouble(h_Client, pd_SendBuf, s32_SendBufLen); switch (u32_Error) { case 0: printf("\nSendTo(...) OK"); break; case WSAEWOULDBLOCK: printf("\nSendTo(%d...) return is: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error); printf("\nWSAEWOULDBLOCK -> The data will be send after the next FD_WRITE event, do nouthing\n"); break; case WSA_IO_PENDING: printf("\nSendTo(%d...) return is: WSA_IO_PENDING(%d)\n",h_Client, u32_Error); printf("\nWSA_IO_PENDING -> Error: A previous Send operation is still pending. This data will not be sent, try latter\n"); break; default: printf("\nSendTo(%d...)failed with severe error: %d\n",h_Client, u32_Error); break; };
SendToString - solicitação para enviar bloco de dados (array tipo char) através da conexão atual.
static EXPFUNC int __stdcall SendToString(SOCKET h_Client, // entrada char* ps8_SendBuf, // entrada int s32_SendBufLen); // entrada - o tamanho do array SendBuf[] no elemento int
Parâmetros da função:
SOCKET h_Client - manipulador da conexão atual.
char ps8_SendBuf[s32_SendBufLen] - bloco único (array tipo int) que necessita ser enviado ao cliente.
int s32_SendBufLen - tamanho do array.
Se bem sucedida, a função retornará verdadeira, caso contrário ele retorna o código de erro Winsock2 API. Exemplo C++:
char ps8_SendBuf[200]; int s32_SendBufLen=200; int u32_Error = cNetEventsProcDLL::SendToString(h_Client, ps8_SendBuf, s32_SendBufLen); switch (u32_Error) { case 0: printf("\nSendTo(...) OK"); break; case WSAEWOULDBLOCK: printf("\nSendTo(%d...) return is: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error); printf("\nWSAEWOULDBLOCK -> The data will be send after the next FD_WRITE event, do nouthing\n"); break; case WSA_IO_PENDING: printf("\nSendTo(%d...) return is: WSA_IO_PENDING(%d)\n",h_Client, u32_Error); printf("\nWSA_IO_PENDING -> Error: A previous Send operation is still pending. This data will not be sent, try latter\n"); break; default: printf("\nSendTo(%d...)failed with severe error: %d\n",h_Client, u32_Error); break; };
ReadFromInt - solicitação para enviar bloco de dados (array tipo int) através da conexão atual.
static EXPFUNC int __stdcall ReadFromInt(SOCKET h_Client, // entrada int* ps32_ReadBuf, // entrada int s32_ReadBufLen, // o tamanho do array ReadBuf[] no elemento int int* ps32_ReadLen); // saída- int ps32_ReadLen[1] - conta real da leitura de dados no elemento int
Parâmetros da função:
SOCKET h_Client - manipulador da conexão atual.
int ps32_ReadBuf[s32_ReadBufLen] - array tipo int para receber bloco de dados.
int s32_ReadBufLen - tamanho do array de recebimento.
int* ps32ReadLen - esta variável contém o tamanho real do bloco de dados que foram recebidos e colocados no array ps32_ReadBuf[]. Se o tamanho do array de recebimento não for suficiente para receber o bloco de dados, então, com um sinal menos, esta variável irá manter o tamanho necessária para receber o bloco de dados. O bloco permanece na pilha e o código de retorno será igual a zero. Se não houver dados na pilha de cliente com manipulador especificado, esta variável será igual a zero e o código de retorno será também igual a zero.
Se bem sucedida, a função retornará verdadeira, caso contrário ele retorna o código de erro Winsock2 API. Exemplo C++:
int ps32_ReadBuf[2000]; int s32_ReadBufLen=2000; int ps32_ReadLen[1]; int u32_Error = cNetEventsProcDLL::ReadFromInt(h_Client, ps32_ReadBuf, s32_ReadBufLen, ps32_ReadLen); if(u32_Error) printf("ReadFromInt(%d...) failed with error: %d", h_Client, u32_Error); else if(ps32_ReadLen[0] >= 0) printf("ReadFromInt(%d...) fine, %d int number was read", h_Client, ps32_ReadLen[0]); else printf("ReadFromInt(%d...) fine, but ReadBuf must be at least %d int number size", h_Client, -ps32_ReadLen[0]);
ReadFromDouble - solicitação para receber bloco de dados (array tipo double) através da conexão atual.
static EXPFUNC int __stdcall ReadFromDouble(SOCKET h_Client, // entrada double* pd_ReadBuf, // entrada int s32_ReadBufLen, // o tamanho do array ReadBuf[] no elemento double int* ps32_ReadLen); // saída - int ps32_ReadLen[1] - conta que realmente lê os dados no elemento double
Parâmetros da função:
SOCKET h_Client - manipulador da conexão atual.
double pd_ReadBuf[s32_ReadBufLen] - array tipo double para receber bloco de dados.
int s32_ReadBufLen - tamanho do array de recebimento.
int* ps32ReadLen - Esta variável contém o tamanho real do bloco de dados que foram recebidos e colocados no array ps32_ReadBuf[]. Se o tamanho do array de recebimento não for suficiente para receber o bloco de dados, então, com um sinal menos, esta variável irá manter o tamanho necessária para receber o bloco de dados. O bloco permanece na pilha e o código de retorno será igual a zero. Se não houver dados na pilha de cliente com manipulador especificado, esta variável será igual a zero e o código de retorno será também igual a zero.
Se bem sucedida, a função retornará verdadeira, caso contrário ele retorna o código de erro Winsock2 API. Exemplo C++:
double ps32_ReadBuf[2000]; int s32_ReadBufLen = 2000; int ps32_ReadLen[1]; int u32_Error = cNetEventsProcDLL::ReadFromDouble(h_Client, pd_ReadBuf, s32_ReadBufLen, ps32_ReadLen); if(u32_Error) printf("ReadFromDouble(%d...) failed with error: %d", h_Client, u32_Error); else if(ps32_ReadLen[0] >= 0) printf("ReadFromDouble(%d...) fine, %d double number was read", h_Client, ps32_ReadLen[0]); else printf("ReadFromDouble(%d...) fine, but ReadBuf must be at least %d double number size", h_Client, -ps32_ReadLen[0]);
ReadFromString - Solicitação para receber bloco de dados (array tipo double) através da conexão atual.
static EXPFUNC int __stdcall ReadFromString(SOCKET h_Client, // entrada char* ps8_ReadBuf, // entrada int s32_ReadBufLen, // o tamanho do array ReadBuf[] no elemento char int* ps32_ReadLen); // out - int ps32_ReadLen[1] - conta atual da leitura dos dados no elemento char
Parâmetros da função:
SOCKET h_Client - manipulador da conexão atual.
char ps8_ReadBuf[s32_ReadBufLen] - array tipo char para receber bloco de dados.
int s32_ReadBufLen - tamanho do array de recebimento.
int* ps32ReadLen - Esta variável contém o tamanho real do bloco de dados que foram recebidos e colocados no array ps32_ReadBuf[]. Se o tamanho do array de recebimento não for suficiente para receber o bloco de dados, então, com um sinal menos, esta variável irá manter o tamanho necessária para receber o bloco de dados. O bloco permanece na pilha e o código de retorno será igual a zero. Se não houver dados na pilha de cliente com manipulador especificado, esta variável será igual a zero e o código de retorno será também igual a zero.
Se bem sucedida, a função retornará verdadeira, caso contrário ele retorna o código de erro Winsock2 API. Exemplo C++:
char ps8_ReadBuf[2000]; int s32_ReadBufLen = 2000; int ps32_ReadLen[1]; int u32_Error = cNetEventsProcDLL::ReadFromString(h_Client, ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen); if(u32_Error) printf("ReadFromStrung(%d...) failed with error: %d", h_Client, u32_Error); else if(ps32_ReadLen[0] >= 0) printf("ReadFromString(%d...) fine, %d char was read", h_Client, ps32_ReadLen[0]); else printf("ReadFromString(%d...) fine, but ReadBuf must be at least %d char size", h_Client, -ps32_ReadLen[0]);
O próximo grupo de funções corresponde diretamente a troca de dados via a conexão atual. Recebimento de dados para cada conexão corrente é realizado no modo assíncrono, ou seja, sem resposta do destinatário.
Todos os dados são enviados e recebidos na forma de unidade independente de troca, ou seja, bloco. Todos os blocos para cada destinatário são acumulados na pilha FIFO. O destinatário pode recuperar esses blocos na pilha a qualquer momento. Cada função de troca opera com um único bloco.
Para o envio de dados de operações, é possível que a operação seja bem sucedida, mas o código de retorno seja diferente de zero e pode ter os seguintes valores:
Qualquer outro código de retorno indica erro do usuário.
3. Implementação do Projeto
O arquivo anexado, NetServerClient.zip, contém dois projetos Microsoft Visual Studio 2010 Ultimate:
- NetEventsProc - to build NetEventsProc.exe
- NetEventsProcDLL - to build NetEventsProcDLL.dll
Os códigos-fonte são comentados em detalhes. Você pode olhar para os detalhes da implementação e personalizar os projetos com suas necessidades específicas, se quiser.
NetEventsProc.exe implementa o servidor e os clientes usando soquetes assíncronos. Para comutar soquetes ao modo assíncrono, um dos possíveis métodos de operar no modo assíncrono é usado: soquetes de ligação aos eventos de rede WSAEventSelect (h_Socket, h_Event, FD_ALL_EVENTS).
Se este artigo causar interesse aos leitores, então na próxima versão do artigo iremos discutir todos os detalhes da implementação. Mas isso é tudo por agora. Observe mais uma vez que este projeto baseia-se no trabalho fundamental de um grande mestre, Elmue.
4. Conclusão
Espero que este artigo resolva o problema do intercâbio de informações entre o terminal МetaТrader 4 e outros aplicativos, independentemente da sua localização: se no computador local ou remoto em relação ao terminal instalado. Procurei não colocar neste artigo um monte de "código espaguete", fazendo do processo de usar as funções DLL algo bastante simples e claro. Pelo menos, eu fiz esforços para tal.
Algumas coisas muito importantes devem ser observadas:
Quase todos os computadores são os nós de uma rede local (LAN). Mesmo se você tiver apenas um computador, muito provavelmente o nó de LAN consiste de um computador.
A conexão na ampla área de rede (WAN) é realizada através de um dispositivo adicional de hardware que pode ser chamado de roteador, modem ou algum outro termo técnico. Para simplificar, vamos chamá-lo de roteador. É por esse dispositivo que se atribue o endereço IP global.
Roteadores resolvem certos problemas de segurança quando se trabalha com WAN, permitem economizar endereços IP globais e ajudam organizar a conexão WAN a partir da rede local, mas ao mesmo tempo distorce o sentido inicial da WAN que implica possibilidade de uma conexão direta via Peer-To-Peer de dois computadores.
Isto é devido ao fato de praticamente todo o roteador executar a chamada Network Address Translation (NAT). O endereço de rede é representado pela tupla <protocol, IP, port>. Qualquer elemento desta tupla pode ser alterado pelo roteador, tudo depende de determinado modelo de roteador.
Neste caso, o computador que acessa a WAN da LAN não tem todos os benefícios proporcionados por uma WAN pura. A grande maioria dos roteadores tem o recurso OUTBOUND que permite lembrar de endereços da rede de cliente LAN que está endereçado na rede global WAN com alguma solicitação.
Graças a esta característica, o roteador pode enviar ao cliente todas as informações recebidas como resposta à solicitação do cliente, assim cliente LAN podem se conectar a servidores WAN. No entanto, isto nem sempre é verdade, pois por razões de segurança e disciplina de trabalho, alguns endereços de rede podem ser bloqueados pelo hardware.
Portanto, para organizar servidor num computador LAN é necessário configurar o chamado encaminhamento de porta. Como no exemplo deste artigo, no caso mais simples de um computador da LAN, onde você precisa transmitir a número da porta 2000. Você pode fazê-lo sozinho, conectando-se ao mini-site roteador no navegador ou você pode recorrer a um profissional. Muito provavelmente este mini-site está disponível no link 192.168.1.1.
Isso tudo deve ser considerado, se você quiser ter possibilidade de trocar informações via WAN.
Na próxima versão do artigo, vamos considerar o tipo de conexão Peer-To-Peer (P2P).