English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Comunicando-se com o MetaTrader 5 utilizando pipes nomeados sem DLLs

Comunicando-se com o MetaTrader 5 utilizando pipes nomeados sem DLLs

MetaTrader 5Exemplos | 13 março 2014, 15:36
1 960 0
MetaQuotes
MetaQuotes

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:

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

Arquivos anexados |
pipeclient.mq5 (3.15 KB)
pipeserver.zip (43.59 KB)
Como escrever uma boa descrição para um produto do mercado Como escrever uma boa descrição para um produto do mercado
O MQL5 Market tem muitos produtos à venda, mas algumas de suas descrições deixam muito a desejar. Muitos textos obviamente precisam de melhoria, visto que negociadores comuns não são capazes de compreendê-los. Este artigo o ajuda a colocar seu produto em uma luz favorável. Use nossas recomendações para escrever uma descrição atrativa que facilmente mostrará aos clientes o que exatamente você está vendendo.
Como preparar cotações do MetaTrader 5 para outros aplicativos Como preparar cotações do MetaTrader 5 para outros aplicativos
O artigo descreve os exemplos de criação de diretórios, cópia de dados, arquivamento, trabalho com símbolos no Market Watch ou a lista comum, bem como os exemplos de tratamento de erros, etc. Todos estes elementos podem eventualmente ser reúnidos em um simples script para arquivamento de dados em um formato definido pelo usuário.
Quão seguro é comprar os produtos no Mercado MQL5? Quão seguro é comprar os produtos no Mercado MQL5?
Lançamos o serviço de venda de aplicativos de negociação para o MetaTrader 5 prestando muita atenção às questões de segurança. Minimizamos todos os riscos relacionados para permitir que você se concentre em questões mais importantes - procurar o robô de negociação mais apropriado!
Como comprar um robô negociação, no Mercado MetaTrader, e instalá-lo? Como comprar um robô negociação, no Mercado MetaTrader, e instalá-lo?
Cada produto disponível no Mercado MetaTrader pode ser comprado tanto através das plataformas de negociação MetaTrader 4 e MetaTrader 5, quanto diretamente no site MQL5.com. Selecione o produto que melhor se adapta à sua maneira de trabalhar, pague por ele conveniente e não se esqueça de ativá-lo.