
Comunicando-se com o MetaTrader 5 utilizando pipes nomeados sem DLLs
Introdução
Muitos desenvolvedores encontram o mesmo problema - como chegar ao sandbox do terminal sem utilizar DLLs arriscados.
Um dos métodos mais fáceis e seguros é utilizar pipes nomeados padrão que funcionam como operações de arquivo normais. Eles permitem que você organize a comunicação cliente-servidor interprocessadores entre programas. Embora já exista um artigo publicado sobre este assunto - Uma solução livre de DLL para comunicação entre os terminais MetaTrader 5 utilizando pipes nomeados - que demonstra a habilitação de acesso para DLLs, utilizaremos os recursos seguros padrão do terminal do cliente.
Você pode encontrar mais informações sobre pipes nomeados na biblioteca MSDN, mas iremos direto a exemplos práticos em C++ e MQL5. Implementaremos o servidor, cliente, troca de dados entre eles e, em seguida, faremos uma comparação de desempenho.
Implementação do servidor
Vamos codificar um servidor simples em C++. Um script do terminal se conectará a esse servidor e trocará dados com ele. O núcleo do servidor tem o seguinte conjunto de funções WinAPI:
- CreateNamedPipe - cria um pipe nomeado.
- ConnectNamedPipe - habilita o servidor para aguardar conexões de cliente.
- WriteFile - escreve dados no pipe.
- ReadFile - faz a leitura dos dados do pipe.
- FlushFileBuffers - esvazia os buffers acumulados.
- DisconnectNamedPipe - desconecta o servidor.
- CloseHandle - fecha o handle.
Uma vez que um pipe nomeado é aberto, ele retorna um handle de arquivo que pode ser usado para operações normais de leitura/escrita de arquivo. Como resultado, você obtém um mecanismo muito simples que não requer qualquer conhecimento especial sobre operações de rede.
Pipes nomeados têm uma característica distinta - eles podem ser tanto locais como de rede. Isso quer dizer que é fácil implementar um servidor remoto que aceitará conexões de rede dos terminais do cliente.
Aqui está um exemplo simples da criação de um servidor local como canal full-duplex que funciona no modo de troca de bytes:
//--- open CPipeManager manager; if(!manager.Create(L"\\\\.\\pipe\\MQL5.Pipe.Server")) return(-1); //+------------------------------------------------------------------+ //| Create named pipe | //+------------------------------------------------------------------+ bool CPipeManager::Create(LPCWSTR pipename) { //--- check parameters if(!pipename || *pipename==0) return(false); //--- close old Close(); //--- create named pipe m_handle=CreateNamedPipe(pipename,PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES,256*1024,256*1024,1000,NULL); if(m_handle==INVALID_HANDLE_VALUE) { wprintf(L"Creating pipe '%s' failed\n",pipename); return(false); } //--- ok wprintf(L"Pipe '%s' created\n",pipename); return(true); }
Para obter uma conexão de cliente, você precisa utilizar a função ConnectNamedPipe:
//+------------------------------------------------------------------+ //| Connect client | //+------------------------------------------------------------------+ bool CPipeManager::ConnectClient(void) { //--- pipe exists? if(m_handle==INVALID_HANDLE_VALUE) return(false); //--- connected? if(!m_connected) { //--- connect if(ConnectNamedPipe(m_handle,NULL)==0) { //--- client already connected before ConnectNamedPipe? if(GetLastError()!=ERROR_PIPE_CONNECTED) return(false); //--- ok } m_connected=true; } //--- return(true); }
A troca de dados é organizada através do uso de 4 funções simples:
- CPipeManager::Send(void *data,size_t data_size)
- CPipeManager::Read(void *data,size_t data_size)
- CPipeManager::SendString(LPCSTR command)
- CPipeManager::ReadString(LPSTR answer,size_t answer_maxlen)
Elas permitem enviar/receber dados como binários ou strings de texto ANSI em modo compatível com MQL5. Além disso, porque CFilePipe, em MQL5, abre um arquivo em modo ANSI de forma automaticamente definida, as strings são automaticamente convertidas para Unicode durante o recebimento e envio. Se o seu programa MQL5 abre um arquivo em modo Unicode (FILE_UNICODE), então ele pode trocar strings Unicode (com assinatura de início BOM).
Implementação do cliente
Escreveremos o nosso cliente em MQL5. Ele será capaz de realizar operações de arquivo regulares utilizando a classe CFilePipe da Biblioteca padrão. Esta classe é quase idêntica à classe CFileBin, mas contém uma importante verificação de disponibilidade de dados em um arquivo virtual antes de ler esses dados.
//+------------------------------------------------------------------+ //| Wait for incoming data | //+------------------------------------------------------------------+ bool CFilePipe::WaitForRead(const ulong size) { //--- check handle and stop flag while(m_handle!=INVALID_HANDLE && !IsStopped()) { //--- enough data? if(FileSize(m_handle)>=size) return(true); //--- wait a little Sleep(1); } //--- failure return(false); } //+------------------------------------------------------------------+ //| Read an array of variables of double type | //+------------------------------------------------------------------+ uint CFilePipe::ReadDoubleArray(double &array[],const int start_item,const int items_count) { //--- calculate size uint size=ArraySize(array); if(items_count!=WHOLE_ARRAY) size=items_count; //--- check for data if(WaitForRead(size*sizeof(double))) return FileReadArray(m_handle,array,start_item,items_count); //--- failure return(0); }
Pipes nomeados apresentam diferenças significativas na implementação de seus modos local e de rede. Sem a referida verificação, as operações no modo de rede sempre retornarão um erro de leitura em caso de envio de grandes quantidades de dados (acima de 64K).
Vamos conectar ao servidor com duas verificações: ou ao computador remoto chamado "RemoteServerName" ou à máquina local.
void OnStart() { //--- wait for pipe server while(!IsStopped()) { if(ExtPipe.Open("\\\\RemoteServerName\\pipe\\MQL5.Pipe.Server",FILE_READ|FILE_WRITE|FILE_BIN)!=INVALID_HANDLE) break; if(ExtPipe.Open("\\\\.\\pipe\\MQL5.Pipe.Server",FILE_READ|FILE_WRITE|FILE_BIN)!=INVALID_HANDLE) break; Sleep(250); } Print("Client: pipe opened");
Troca de dados
Após a conexão exitosa, vamos enviar uma string de texto com informações de identificação ao servidor. A string em Unicode será automaticamente convertida em ANSI, visto que o arquivo é aberto em modo ANSI.
//--- send welcome message if(!ExtPipe.WriteString(__FILE__+" on MQL5 build "+IntegerToString(__MQ5BUILD__))) { Print("Client: sending welcome message failed"); return; }
Em resposta, o servidor enviará a sua string "Hello from pipe server" e o inteiro 1234567890. O cliente novamente enviará a string "Test string" e o inteiro 1234567890.
//--- read data from server string str; int value=0; if(!ExtPipe.ReadString(str)) { Print("Client: reading string failed"); return; } Print("Server: ",str," received"); if(!ExtPipe.ReadInteger(value)) { Print("Client: reading integer failed"); return; } Print("Server: ",value," received"); //--- send data to server if(!ExtPipe.WriteString("Test string")) { Print("Client: sending string failed"); return; } if(!ExtPipe.WriteInteger(value)) { Print("Client: sending integer failed"); return; }
OK, já terminamos a troca de dados simples. Agora é hora de comparar o desempenho.
Comparação de desempenho
Como teste, enviaremos 1 gigabyte de dados como uma série de números de tipo duplo em blocos de 8 megabytes do servidor ao cliente e então verificaremos a exatidão dos blocos e mediremos a taxa de transferência.
Aqui está este código no servidor C++:
//--- benchmark double volume=0.0; double *buffer=new double[1024*1024]; // 8 Mb wprintf(L"Server: start benchmark\n"); if(buffer) { //--- fill the buffer for(size_t j=0;j<1024*1024;j++) buffer[j]=j; //--- send 8 Mb * 128 = 1024 Mb to client DWORD ticks=GetTickCount(); for(size_t i=0;i<128;i++) { //--- setup guard signatures buffer[0]=i; buffer[1024*1024-1]=i+1024*1024-1; //--- if(!manager.Send(buffer,sizeof(double)*1024*1024)) { wprintf(L"Server: benchmark failed, %d\n",GetLastError()); break; } volume+=sizeof(double)*1024*1024; wprintf(L"."); } wprintf(L"\n"); //--- read confirmation if(!manager.Read(&value,sizeof(value)) || value!=12345) wprintf(L"Server: benchmark confirmation failed\n"); //--- show statistics ticks=GetTickCount()-ticks; if(ticks>0) wprintf(L"Server: %.0lf Mb sent at %.0lf Mb per second\n",volume/1024/1024,volume/1024/ticks); //--- delete[] buffer; }
e no cliente MQL5:
//--- benchmark double buffer[]; double volume=0.0; if(ArrayResize(buffer,1024*1024,0)==1024*1024) { uint ticks=GetTickCount(); //--- read 8 Mb * 128 = 1024 Mb from server for(int i=0;i<128;i++) { uint items=ExtPipe.ReadDoubleArray(buffer); if(items!=1024*1024) { Print("Client: benchmark failed after ",volume/1024," Kb, ",items," items received"); break; } //--- check the data if(buffer[0]!=i || buffer[1024*1024-1]!=i+1024*1024-1) { Print("Client: benchmark invalid content"); break; } //--- volume+=sizeof(double)*1024*1024; } //--- send confirmation value=12345; if(!ExtPipe.WriteInteger(value)) Print("Client: benchmark confirmation failed "); //--- show statistics ticks=GetTickCount()-ticks; if(ticks>0) printf("Client: %.0lf Mb received at %.0lf Mb per second\n",volume/1024/1024,volume/1024/ticks); //--- ArrayFree(buffer); }
Note que o primeiro e o último elemento dos blocos transferidos são verificados para certificar que não houve erros durante a transferência. Além disso, quando a transferência é finalizada, o cliente envia um sinal de confirmação ao servidor relativo ao êxito do recebimento de dados. Se você não utilizar confirmações finais, facilmente encontrará perda de dados caso uma das partes finalizar a conexão muito cedo.
Execute o servidor PipeServer.exe localmente e anexe o script PipeClient.mq5 a qualquer gráfico:
PipeServer.exe | PipeClient.mq5 |
---|---|
MQL5 Pipe Server Copyright 2012, MetaQuotes Software Corp. Pipe '\\.\pipe\MQL5.Pipe.Server' created Client: waiting for connection... Client: connected as 'PipeClient.mq5 on MQL5 build 705' Server: send string Server: send integer Server: read string Server: 'Test string' received Server: read integer Server: 1234567890 received Server: start benchmark ...................................................... ........ Server: 1024 Mb sent at 2921 Mb per second |
PipeClient (EURUSD,H1) Client: pipe opened PipeClient (EURUSD,H1) Server: Hello from pipe server received PipeClient (EURUSD,H1) Server: 1234567890 received PipeClient (EURUSD,H1) Client: 1024 Mb received at 2921 Mb per second |
Para a troca local, a taxa de transferência é realmente impressionante - quase 3 gigabytes por segundo. Isso significa que os pipes nomeados podem ser utilizados para transferir praticamente qualquer quantidade de dados aos programas MQL5.
Agora vamos comparar o desempenho de transferência de dados em uma rede LAN comum de 1 gigabit.
PipeServer.exe | PipeClient.mq5 |
---|---|
MQL5 Pipe Server Copyright 2012, MetaQuotes Software Corp. Pipe '\\.\pipe\MQL5.Pipe.Server' created Client: waiting for connection... Client: connected as 'PipeClient.mq5 on MQL5 build 705' Server: send string Server: send integer Server: read string Server: 'Test string' received Server: read integer Server: 1234567890 received Server: start benchmark ...................................................... ........ Server: 1024 Mb sent at 63 Mb per second |
PipeClient (EURUSD,H1) Client: pipe opened PipeClient (EURUSD,H1) Server: Hello from pipe server received PipeClient (EURUSD,H1) Server: 1234567890 received PipeClient (EURUSD,H1) Client: 1024 Mb received at 63 Mb per second |
Na rede local, 1 gigabyte de dados foi transferido a uma taxa de 63 megabytes por segundo, o que é muito bom. Na realidade, isso corresponde a 63% da largura de banda máxima da rede gigabit.
Conclusão
O sistema de proteção da plataforma de negociação MetaTrader 5 não permite que os programas MQL5 sejam executados fora do seu sandbox, o que protege os negociadores contra ameaças durante o uso de Expert Advisors não confiáveis. Com a utilização de pipes nomeados, você pode criar integrações fáceis com softwares de terceiros e gerenciar EAs externamente. Com segurança.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/503





- 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