Communiquer avec MetaTrader 5 en utilisant Named Pipes sans utiliser de DLL
Introduction
De nombreux développeurs sont confrontés au même problème : comment accéder au sandbox du terminal de trading sans utiliser de DLL non sécurisées.
L'une des méthodes les plus simples et les plus sûres consiste à utiliser des Named Pipes standard qui fonctionnent comme des opérations de fichier normales. Ils vous permettent d'organiser la communication inter-processeur client-serveur entre les programmes. Bien qu'il existe un article déjà publié Une solution sans DLL pour communiquer entre les terminaux MetaTrader 5 à l'aide de Named Pipes sur ce sujet qui démontre la possibilité d'accéder aux DLL, nous allons utiliser les fonctionnalités standard et sûres du terminal client.
Vous pouvez trouver plus d'informations sur les named pipes dans la bibliothèque MSDN, mais nous allons passer aux exemples pratiques en C++ et MQL5. Nous allons mettre en œuvre le serveur, le client, l'échange de données entre eux et ensuite évaluer les performances.
Mise en œuvre du serveur
Codons un serveur simple en C++. Un script du terminal se connectera à ce serveur et échangera des données avec lui. Le noyau du serveur possède l'ensemble de fonctions WinAPI suivant :
- CreateNamedPipe- crée un named pipe.
- ConnectNamedPipe - permet au serveur d'attendre les connexions client.
- WriteFile - écrit les données dans le pipe.
- ReadFile - lit les données du pipe.
- FlushFileBuffers - vide les tampons accumulés.
- DisconnectNamedPipe - déconnecte le serveur.
- CloseHandle - ferme le handle.
Une fois qu'un pipe nommé est ouvert, il renvoie un handle de fichier qui peut être utilisé pour des opérations de lecture/écriture de fichiers. Vous obtenez ainsi un mécanisme très simple qui ne nécessite aucune connaissance particulière en matière d'exploitation de réseau.
Les Named pipes ont une particularité : ils peuvent être à la fois locaux et en réseau. En d'autres termes, il est facile de mettre en œuvre un serveur distant qui acceptera les connexions réseau des terminaux clients.
Voici un exemple simple de création d'un serveur local comme canal full-duplex qui fonctionne en mode d'échange d'octets :
//--- 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); }
Pour obtenir une connexion client, vous devez utiliser la fonction 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); }
L'échange de données est organisé à l'aide de 4 fonctions 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)
Ils vous permettent d'envoyer/recevoir des données sous forme de données binaires ou de chaînes de texte ANSI en mode compatible MQL5. De plus, comme CFilePipe dans MQL5 ouvre un fichier en mode ANSI par défaut, les chaînes sont automatiquement converties en Unicode à la réception et à l'envoi. Si votre programme MQL5 ouvre un fichier en mode Unicode (FILE_UNICODE), il peut alors échanger des chaînes Unicode (avec signature de départ BOM).
Mise en œuvre client
Nous allons écrire notre client en MQL5. Il sera capable d'effectuer des opérations régulières sur les fichiers en utilisant la classe CFilePipe de la bibliothèque standard. Cette classe est presque identique au CFileBin, mais elle contient une vérification importante de la disponibilité des données dans un fichier virtuel avant de lire ces données.
//+------------------------------------------------------------------+ //| 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); }
Les Named pipes présentent des différences importantes dans la mise en œuvre de leurs modes local et réseau. Sans cette vérification, les opérations en mode réseau renverront toujours une erreur de lecture lors de l'envoi de grandes quantités de données (plus de 64K).
Connectons nous au serveur avec deux contrôles : soit à l'ordinateur distant nommé « RemoteServerNam », soit à la machine 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");
Échange de données
Après une connexion réussie, envoyons une chaîne de texte contenant des informations d'identification au serveur. La chaîne Unicode sera automatiquement convertie en ANSI, puisque le fichier est ouvert en mode ANSI.
//--- send welcome message if(!ExtPipe.WriteString(__FILE__+" on MQL5 build "+IntegerToString(__MQ5BUILD__))) { Print("Client: sending welcome message failed"); return; }
En réponse, le serveur enverra sa chaîne de caractères « Hello from pipe server » et le nombre entier 1234567890. Le client enverra à nouveau la chaîne « Test string » et le nombre entier 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, nous en avons terminé avec l'échange simple de données. Il est maintenant temps de procéder à l'évaluation des performances.
Critères de performance
À titre de test, nous allons envoyer 1 gigaoctet de données sous forme de tableau de nombres de type double par blocs de 8 mégaoctets du serveur au client, puis vérifier l'exactitude des blocs et mesurer le taux de transfert.
Voici ce code en serveur 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; }
et dans le 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); }
Notez que le premier et le dernier élément des blocs transférés sont vérifiés afin de s'assurer qu'il n'y a eu aucune erreur lors du transfert. De plus, lorsque le transfert est terminé, le client envoie un signal de confirmation au serveur concernant la réussite de la réception des données. Si vous n'utilisez pas les confirmations finales, vous rencontrerez facilement une perte de données si l'une des parties ferme la connexion trop tôt.
Exécutez localement le serveur PipeServer.exe et attachez le script PipeClient.mq5 à n'importe quel graphique :
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 |
Pour les échanges locaux, le taux de transfert est vraiment étonnant - près de 3 gigaoctets par seconde. Cela signifie que les Named pipes peuvent être utilisés pour transférer presque n'importe quelle quantité de données dans des programmes MQL5.
Comparons maintenant les performances de transfert de données dans un réseau local ordinaire 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 |
Dans le réseau local, 1 gigaoctet de données a été transféré à un taux de 63 mégaoctets par seconde, ce qui est très bon. En fait, c'est 63 % de la bande passante maximale du réseau gigabit.
Conclusion
Le système de protection de la plateforme de trading MetaTrader 5 ne permet pas aux programmes MQL5 de s'exécuter en dehors de leur sandbox, protégeant les traders contre les menaces lors de l'utilisation d'Expert Advisors non fiables. En utilisant des Named pipes , vous pouvez facilement créer des intégrations avec des logiciels tiers et gérer les AE de l'extérieur. En toute sécurité.
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/503
- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation