Comunicare con MetaTrader 5 utilizzando pipe denominate senza utilizzare DLL
Introduzione
Molti sviluppatori affrontano lo stesso problema: come accedere alla sandbox del terminale di trading senza utilizzare DLL non sicure.
Uno dei metodi più semplici e sicuri consiste nell'utilizzare pipe denominate standard che funzionano come normali operazioni sui file. Consentono di organizzare la comunicazione client-server interprocessore tra i programmi. Sebbene sia già stato pubblicato un articolo Una soluzione senza DLL per comunicare tra terminali MetaTrader 5 utilizzando pipe denominate su questo argomento, il che dimostra l'abilitazione dell'accesso alle DLL, utilizzeremo standard e caratteristiche del client terminal.
È possibile trovare ulteriori informazioni sulle pipe denominate nella libreria MSDN, ma passeremo ad esempi pratici in C++ e MQL5. Implementeremo server, client, scambio di dati tra loro e quindi prestazioni di benchmark.
Implementazione del server
Codifichiamo un semplice server in C++. Uno script dal terminale si collegherà a questo server e scambierà dati con esso. Il core del server ha il seguente set di funzioni WinAPI:
- CreateNamedPipe: crea una pipe denominata.
- ConnectNamedPipe: consente al server di attendere le connessioni client.
- WriteFile: scrive i dati su pipe.
- ReadFile: legge i dati dalla pipe.
- FlushFileBuffers: scarica i buffer accumulati.
- DisconnectNamedPipe: disconnette il server.
- CloseHandle: chiude l'handle.
Una volta aperta una pipe denominata, restituisce un handle di file che può essere utilizzato per le normali operazioni di lettura/scrittura sui file. Di conseguenza si ottiene un meccanismo molto semplice che non richiede alcuna conoscenza speciale nelle operazioni di rete.
Le pipe con nome hanno una caratteristica distintiva: possono essere sia locali che di rete. Ovvero, è facile implementare un server remoto che accetterà connessioni di rete dai client terminal.
Ecco un semplice esempio di creazione di un server locale come canale full-duplex che funziona in modalità di scambio di byte:
//--- 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); }
Per ottenere una connessione client devi usare la funzione 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); }
Lo scambio di dati è organizzato utilizzando 4 semplici funzioni:
- 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)
Consentono di inviare/ricevere dati come dati binari o stringhe di testo ANSI in modalità compatibile MQL5. Inoltre, poiché CFilePipe in MQL5 apre un file in modalità ANSI per impostazione predefinita, le stringhe vengono convertite automaticamente in Unicode alla ricezione e all'invio. Se il tuo programma MQL5 apre un file in modalità Unicode (FILE_UNICODE), allora può scambiare stringhe Unicode (con firma iniziale BOM).
Implementazione del client
Scriveremo il nostro client in MQL5. Sarà in grado di eseguire normali operazioni sui file utilizzando la classe CFilePipe dalla libreria standard. Questa classe è quasi identica a CFileBin, ma contiene un'importante verifica della disponibilità dei dati in un file virtuale prima di leggere questi dati.
//+------------------------------------------------------------------+ //| 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); }
Le pipe denominate presentano differenze significative nell'implementazione delle modalità locale e di rete. Senza tale verifica, le operazioni in modalità rete restituiranno sempre un errore di lettura durante l'invio di grandi quantità di dati (oltre 64 KB).
Connettiamoci al server con due controlli: o al computer remoto denominato 'RemoteServerName' o al computer locale.
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");
Data Exchange
Dopo che la connessione è andata a buon fine, inviamo una stringa di testo con le informazioni di identificazione al server. La stringa Unicode verrà automaticamente convertita in ANSI, poiché il file viene aperto in modalità ANSI.
//--- send welcome message if(!ExtPipe.WriteString(__FILE__+" on MQL5 build "+IntegerToString(__MQ5BUILD__))) { Print("Client: sending welcome message failed"); return; }
In risposta, il server invierà la sua stringa "Hello from pipe server" e l'intero 1234567890. Il client invierà ancora una volta la stringa "Test string" e l'intero 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, abbiamo finito con il semplice scambio di dati. Ora è il momento del benchmark delle prestazioni.
Benchmark delle prestazioni
Come test, invieremo 1 gigabyte di dati come array dei numeri di tipo double in blocchi di 8 megabyte dal server al client, quindi verificheremo la correttezza dei blocchi e misureremo la velocità di trasferimento.
Ecco questo codice nel server 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 nel client 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); }
Da notare che il primo e l'ultimo elemento dei blocchi trasferiti vengono controllati per assicurarsi che non ci siano stati errori durante il trasferimento. Inoltre, quando il trasferimento è completo, il client invia un segnale di conferma al server sull'avvenuta ricezione dei dati. Se non utilizzerai le conferme finali, incontrerai facilmente una perdita di dati se una delle parti chiude la connessione troppo presto.
Esegui localmente il server PipeServer.exe e allega lo script PipeClient.mq5 a qualsiasi grafico:
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 |
Per lo scambio locale, la velocità di trasferimento è davvero sorprendente: quasi 3 gigabyte al secondo. Ciò significa che le pipe denominate possono essere utilizzate per trasferire quasi qualsiasi quantità di dati nei programmi MQL5.
Ora esaminiamo le prestazioni di trasferimento dei dati in una normale LAN da 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 |
Nella rete locale è stato trasferito 1 gigabyte di dati alla velocità di 63 megabyte al secondo, il che è molto buono. In effetti è il 63% della larghezza di banda massima della rete gigabit.
Conclusione
Il sistema di protezione della piattaforma di trading MetaTrader 5 non consente ai programmi MQL5 di essere eseguiti al di fuori della loro sandbox, proteggendo i trader dalle minacce quando utilizzano Expert Advisor non attendibili. Utilizzando le pipe denominate puoi creare facilmente integrazioni con software di terze parti e gestire gli EA dall'esterno. In sicurezza.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/503
- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso