
Arbeiten mit Sockets in MQL, oder Wie man ein Signalprovider wird
Ein bisschen Pathos
Sockets… Was in unserer IT-Welt könnte ohne sie auskommen? Seit 1982 und bis heute kaum verändert arbeiten sie ununterbrochen jede Sekunde für uns. Dies ist die Grundlage des Netzwerks, die Nervenenden der Matrix, in der wir alle Leben.
Am Morgen schalten Sie das MetaTrader Terminal ein und sofort erstellt es Sockets und eine Verbindung zu den Servern. Sie öffnen einen Browser, und Dutzende von Socket-Verbindungen werden erstellt und geschlossen, um die Informationen aus dem Web zu liefern oder um E-mails zu senden, genaue Zeitsignale zu erhalten, Gigabyte des verteilten Rechnens zu verteilen.
Zuerst brauchen wir ein wenig Theorie. Schauen Sie in die Wiki oder MSDN. Die entsprechenden Artikel beschreiben das notwendigen Arsenal von Strukturen und Funktionen, sowie geben Sie Beispiele zur Einrichtung eines Clients und eines Server.
In diesem Artikel wird die Umsetzung dieses Wissens in MQL berücksichtigt.
1. Portieren von Strukturen und Funktionen vom WinAPI
Es ist kein Geheimnis, das die WinAPI für die Programmiersprache C entwickelt wurde. Und die MQL-Sprache ist praktisch sein Blutsbruder (sowohl im Geist als auch im Arbeitsstil) geworden. Wir erstellen Sie eine Mqh-Datei für diese WinAPI-Funktionen, die im MQL-Hauptprogramm verwendet werden. Unser Auftrag ist es, diese bei Bedarf zu portieren
Für den TCP-Client werden nur ein paar Funktionen benötigt:
- Initialisierung der Bibliothek mit WSAStartup();
- Erstellung eines Sockets mit socket();
- den nonblocking-Modus mit ioctlsocket() einstellen, damit es zu keinem Hänger kommt, während auf Daten gewartet wird;
- Mit connect() zu Server verbinden;
- Mit recv() hören oder mit send() Daten senden bis zum Ende des Programms oder einer Verbindungsunterbrechung;
- Schließen des Socket mit closesocket() nach getaner Arbeit, und Deinitialisierung der Bibliothek mit WSACleanup().
Ein TCP-Server benötigt ähnliche Funktionen, mit einer Ausnahme - er wird an einen bestimmten Port gebunden und versetzt den Socket in den Abhören-Modus. Die notwendigen Schritte sind:
- Initialisierung der Bibliothek mit WSAStartup();
- Erstellung eines Sockets mit socket();
- Einstellen des nonblocking-Modus - ioctlsocket();
- An einen Port binden - bind();
- In den Abhören-Modus versetzen - listen();
- Nach erfolgreicher Erstellung auf ein accept() warten;
- Client-Verbindungen herstellen und die Arbeit im recv()/send() Modus fortsetzen bis das Ende des Programms oder eine Verbindungsunterbrechung eintritt;
- Nach getaner Arbeit den abhörenden Socket des Servers und verbundene Clients mittels closesocket() schließen und die Bibliothek mit WSACleanup() deinitialisieren.
Im Falle eines UDP-Sockets, gibt es weniger Schritte (es gibt keinen "Handshake" zwischen Client und Server). UDP Client:
- Initialisierung der Bibliothek mit WSAStartup();
- Erstellung eines Sockets mit socket();
- den nonblocking-Modus mit ioctlsocket() einstellen, damit es zu keinem Hänger kommt, während auf Daten gewartet wird;
- send - sendto() /receive - recvfrom() Daten;
- Schließen des Socket mit closesocket() nach getaner Arbeit, und Deinitialisierung der Bibliothek mit WSACleanup().
Nur eine einzelne Bind-Funktion wird zum UDP-Server hinzugefügt:
- Initialisierung der Bibliothek mit WSAStartup();
- Erstellung eines Sockets mit socket();
- Einstellen des nonblocking-Modus - ioctlsocket();
- An einen Port binden - bind();
- receive - recvfrom() / send - sendto();
- Nach getaner Arbeit den abhörenden Socket des Servers und verbundene Clients mittels closesocket() schließen und die Bibliothek mit WSACleanup() deinitialisieren.
Wie Sie sehen können, ist der Weg nicht zu allzu kompliziert, aber die Strukturen müssen gefüllt werden, um jede Funktion aufzurufen.
a) WSAStartup()
Siehe vollständige Beschreibung im MSDN:
WINAPI:
int WSAAPI WSAStartup(_In_ WORD wVersionRequested, _Out_ LPWSADATA lpWSAData);
_In_, _Out_ sind leere Definitionen die auf den Scope der Parameter zeigen. WSAAPI beschreibt die Regel für die Übergabe von Parametern, aber für unsere Zwecke, kann es auch leer gelassen werden. Wie aus den Unterlagen ersichtlich ist, ist auch ein MAKEWORD Makro notwendig, um die erforderliche Version als ersten Parameter zu spezifizieren, sowie ein Zeiger auf die LPWSADATA-Struktur. Das Makro ist nicht schwer zu erstellen, kopieren Sie es aus der Header-Datei:
#define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))Darüber hinaus können alle Datentypen auch einfach in MQL definiert werden:
#define BYTE uchar #define WORD ushort #define DWORD int #define DWORD_PTR ulongKopiere die WSADATA-Struktur vom MSDN. Die Namen der meisten Datentypen sollten zur besseren Lesbarkeit unverändert bleiben, inbesondere da sie zuvor schon definiert wurden.
struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; ushort iMaxSockets; ushort iMaxUdpDg; char lpVendorInfo[]; }Beachten Sie, dass der letzte Parameter lpVendorInfo als Array in MQL definiert wurde (in C war esein Zeiger auf char*). Verschieben Sie die Arraygröße-Konstanten ebenfalls zu den Defines. Zu guter Letzt definieren Sie den Zeiger auf eine Struktur als:
#define LPWSADATA char&
Warum so? Es ist einfach. Jede Struktur ist nichts anderes als ein begrenzter Teil des Speichers. Sie kann auf jede beliebige Art repräsentiert werden - zum Beispiel als eine andere Struktur der gleichen Größe oder als ein Array der selben Größe. Hier wirddie Darstellung als ein Array verwendet, daher ist in allen Funktionen der char& Typ die Adresse des Arrays mit jener Größe, die der Größe der erforderlichen Struktur entspricht. Die daraus resultierende Deklaration der Funktion in MQL sieht wie folgt aus:
MQL:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData[]);
So sieht der Funktionsaufruf und die Übergabe des Ergebnisses an die WSAData Struktur aus:char wsaData[]; // Byte Array der zukünftigen Struktur ArrayResize(wsaData, sizeof(WSAData)); // Auf die Größe der Struktur anpassen WSAStartup(MAKEWORD(2,2), wsaData); // Aufruf der Funktion
Die Daten werden an das Bytearray WsaData weitergegeben, aus dem leicht mittels Casts Informationen gewonnen werden können.
Ich hoffe, dieser Teil war nicht allzu schwer – denn es ist nur die erste Funktion, und es muss bereits so viel Arbeit getan werden. Aber jetzt ist das Grundprinzip klar, damit wird es einfacher und interessanter.
b) Socket()
WINAPI: SOCKET WSAAPI socket(_In_ int af, _In_ int type, _In_ int protocol);
Mach das gleiche - kopiere Daten vom MSDN.
Da wir TCP-Sockets für IPv4 verwenden, legen Sie die Konstanten für die Parameter dieser Funktion sofort fest:
#define SOCKET uint #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR (-1) #define NO_ERROR 0 #define AF_INET 2 // internetwork: UDP, TCP, etc. #define SOCK_STREAM 1 #define IPPROTO_TCP 6
c) ioctlsocket()
MQL: int ioctlsocket(SOCKET s, int cmd, int &argp);
Hat sein letztes Argument von einem Zeiger zu einer Adresse geändert:
d) connect()
WINAPI: int connect(_In_ SOCKET s, _In_ const struct sockaddr *name, _In_ int namelen);
Hier gibt es eine kleine Schwierigkeit mit Übergabe der Sockaddr -Struktur, aber der Grundprinzip ist bereits bekannt – die Strukturen werden mit Bytearrays ersetzt und zur Übergabe der Daten an die WinAPI-Funktionen verwendet.
Übernehmen Sie die Struktur vom MSDN ohne Änderungen:
struct sockaddr { ushort sa_family; // Addressfamilie. char sa_data[14]; // Bis zu 14 Bytes für direkte Adresse. };Wie vereinbart, wird der Zeiger darauf mit der Array-Adresse umgesetzt:
#define LPSOCKADDR char&Beispiele im MSDN verwenden die Sockaddr_in Struktur. Sie ist in der Größe ähnlich, aber die Parameter sind anders deklariert:
struct sockaddr_in { short sin_family; ushort sin_port; struct in_addr sin_addr; char sin_zero[8]; };Die Daten für die sin_addr sind eine "Union", davon ist eine eine Darstellung einer 8-Byte-Ganzzahl:
struct in_addr { ulong s_addr; };So sieht die resultierende Deklaration der Funktion in MQL aus:
MQL: int connect(SOCKET s, LPSOCKADDR name[], int namelen);
In diesem Stadium sind wir bereit, einen Client-Socket zu erstellen. Es bleibt nur wenig zu tun - die Funktion zum Empfangen und Senden von Daten.
Die Prototypen sehen so aus:
WINAPI: int send(_In_ SOCKET s, _In_ const char* buf, _In_ int len, _In_ int flags); int recv(_In_ SOCKET s, _Out_ char* buf, _In_ int len, _In_ int flags); MQL: int send(SOCKET s, char& buf[], int len, int flags); int recv(SOCKET s, char& buf[], int len, int flags);Wie ersichtlich, änderte sich der zweite Parameter von einem Char * Zeiger in ein char& [] Array
f) recvfrom() und sendto() für UPD
Die Prototypen in MQL sehen so aus:
WINAPI: int recvfrom(_In_ SOCKET s, _Out_ char* buf, _In_ int len, _In_ int flags, _Out_ struct sockaddr *from, _Inout_opt_ int *fromlen); int sendto(_In_ SOCKET s, _In_ const char* buf, _In_ int len, _In_ int flags, _In_ const struct sockaddr *to, _In_ int tolen); MQL: int recvfrom(SOCKET s,char &buf[],int len,int flags,LPSOCKADDR from[],int &fromlen); int sendto(SOCKET s,const char &buf[],int len,int flags,LPSOCKADDR to[],int tolen);
Und schließlich die zwei wichtigen Funktionen für das Löschen und Schließen der Handles nach der Arbeit:
g) closesocket() und WSACleanup()
MQL: int closesocket(SOCKET s); int WSACleanup();
Die resultierende Datei der portierten WinAPI-Funktionen:
#define BYTE uchar #define WORD ushort #define DWORD int #define DWORD_PTR ulong #define SOCKET uint #define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8)) #define WSADESCRIPTION_LEN 256 #define WSASYS_STATUS_LEN 128 #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR (-1) #define NO_ERROR 0 #define SOMAXCONN 128 #define AF_INET 2 // internetwork: UDP, TCP, etc. #define SOCK_STREAM 1 #define IPPROTO_TCP 6 #define SD_RECEIVE 0x00 #define SD_SEND 0x01 #define SD_BOTH 0x02 #define IOCPARM_MASK 0x7f /* Parameter müssen < 128 Bytes sein */ #define IOC_IN 0x80000000 /* in Parameter kopieren */ #define _IOW(x,y,t) (IOC_IN|(((int)sizeof(t)&IOCPARM_MASK)<<16)|((x)<<8)|(y)) #define FIONBIO _IOW('f', 126, int) /* setzen/löschen non-blocking i/o */ //------------------------------------------------------------------ struct WSAData struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; ushort iMaxSockets; ushort iMaxUdpDg; char lpVendorInfo[]; }; #define LPWSADATA char& //------------------------------------------------------------------ struct sockaddr_in struct sockaddr_in { ushort sin_family; ushort sin_port; ulong sin_addr; //struct in_addr { ulong s_addr; }; char sin_zero[8]; }; //------------------------------------------------------------------ struct sockaddr struct sockaddr { ushort sa_family; // Addressfamilie char sa_data[14]; // Bis zu 14 bytes für direkte Adresse. }; #define LPSOCKADDR char& struct ref_sockaddr { char ref[2+14]; }; //------------------------------------------------------------------ import Ws2_32.dll #import "Ws2_32.dll" int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData[]); int WSACleanup(); int WSAGetLastError(); ushort htons(ushort hostshort); ulong inet_addr(char& cp[]); string inet_ntop(int Family,ulong &pAddr,char &pStringBuf[],uint StringBufSize); ushort ntohs(ushort netshort); SOCKET socket(int af,int type,int protocol); int ioctlsocket(SOCKET s,int cmd,int &argp); int shutdown(SOCKET s,int how); int closesocket(SOCKET s); // Server Funktion int bind(SOCKET s,LPSOCKADDR name[],int namelen); int listen(SOCKET s,int backlog); SOCKET accept(SOCKET s,LPSOCKADDR addr[],int &addrlen); // Client Funktion int connect(SOCKET s,LPSOCKADDR name[],int namelen); int send(SOCKET s,char &buf[],int len,int flags); int recv(SOCKET s,char &buf[],int len,int flags); #import
2. Erstellung eines Clients und eines Servers
Nach Betrachtung der Art der Implementierung der Arbeit mit Sockets für einige Zeit, fiel die Entscheidung für die Demonstration der Arbeit mit den ihren Funktionen auf den Verzicht von Klassen. Erstens wird dies für ein besseres Verständnis der Tatsache sorgen, dass hier nur lineare Programmierung ohne Verzweigungen beteiligt ist. Zweitens wird es ermöglichen, die Funktionen nach Bedarf jeder beliebigen OOP-Ideologie umzugestalten. Die Erfahrung zeigt, dass Programmierer in der Regel gründlich durch einfache Klassen gehen, um zu verstehen, wie alles funktioniert.
Wichtig! Vergessen Sie bei allen Ihren Experimenten nicht, dass ein gebundener Port nicht automatisch freigegeben wird, wenn der Server-Code abbricht. Dadurch würde die wiederholte Erzeugung eines Sockets und der Versuch des 'Bind' Aufrufs zu einem Fehler führen - "Adresse bereits in Gebrauch". Um dieses Problem zu umgehen, verwenden Sie die SO_REUSEADDR -Option auf dem Socket oder starten Sie einfach das Terminal neu. Verwenden Sie Überwachung Dienstprogramme, wie z. B. TCPViewer, um die in Ihrem Betriebssystem erstellten Sockets zu verfolgen.
Es ist auch notwendig, zu verstehen, dass der Client sich nur mit dem Server verbinden kann, wenn der Server nicht hinter einem NAT versteckt ist oder der Port für den Client/Server nicht durch das Betriebssystem oder Router blockiert ist.
Daher ist es möglich, mit Server und Client lokal auf einem einzelnen Computer zu experimentieren. Aber um vollständig mit mehreren Clients zu arbeiten, muss der Server mindestens auf einem VPS mit "weißer" externer IP-Adresse und einem offenen ausgehenden Port ausgeführt werden.
Beispiel 1. Chartlayout an die Clients senden
Starten Sie mit einer einfachen Interaktion – einmalige Übertragung einer Tpl-Datei vom Server an den Client.
In diesem Fall gibt es keine Notwendigkeit, die send/recv Schleife auf der Clientseite zu implementieren, da es nur notwendig ist, einen Datenteil zu beziehen, sobald die Verbindung hergestellt ist und dann wieder zu trennen. Die Verbindung wird vom Server sofort getrennt, nachdem die Daten gesendet wurden.
Das heißt, wenn sich ein Client mit dem Server verbindet, führt der Server einen Send-Aufruf durch und schließt dann den Socket. Zur gleichen Zeit ruft der Client ein Recv auf und schließt dann ebenso den Socket. Natürlich, ist es in den interessanteren Fällen möglich, eine konstante Übertragung der Chart-Änderungen zu senden, wie z. B. sofortige Synchronisierung der Client- und Server-Diagramme. Dies wäre nützlich für den Handel eines Gurus, der seine Charts jungen Jedis online zeigen kann. Aber heute wird das mit der Ausstrahlung eines Video-Streams aus dem Bildschirm durch verschiedene Webinar Softwares oder Skype erledigt. Daher wird dieses Thema am besten im Forum diskutiert.
Wer und wann wird dieses Codebeispiel nützlich finden? Zum Beispiel, platzieren Sie Ihre Indikatoren oder grafischen Objekte auf dem Chart täglich, stündlich oder auf Minutenbasis. Zur gleichen Zeit, haben Sie einen Server EA auf einem separaten Chart, der auf Client-Verbindungen hört und diesen das aktuellen Tpl des gewünschten Symbols und Zeitraums gibt.
Zufriedene Kunden werden nun über Ziele und Trading-Signale von Ihnen informiert. Es reicht für sie, wenn sie in regelmäßigen Abständen ein Skript ausführen, das die Tpl vom Server herunterlädt und auf den Chart anwendet.
Also, fangen wir mit dem Server an. Alles funktioniert im OnTimer-Event, der als der Thread des EA dient. Jede Sekunde werden die wichtigsten Bausteine des Servers geprüft: Warten auf Clients -> Senden der Daten -> Schließen der Verbindung. Es prüft auch die Aktivität des Server-Sockets selbst, und im Falle eines Verbindungsabbruchs - erstellt es wieder einen Serversocket.
Leider ist die gespeicherte Tpl-Vorlage nicht aus der Datei-Sandbox verfügbar. Um sie aus dem Ordner "Profiles\Templates" abzurufen, muss daher noch einmal die WinAPI verwendet werden. Dieses Mal wird es nicht ausführlich beschrieben, der vollständige Code steht unten.
//+------------------------------------------------------------------+ //| TplServer | //| Programmierung & Entwicklung - Alexey Sergeev | //+------------------------------------------------------------------+ #property copyright "© 2006-2016 Alexey Sergeev" #property link "profy.mql@gmail.com" #property version "1.00" #include "SocketLib.mqh" input string Host="0.0.0.0"; input ushort Port=8080; uchar tpl[]; int iCnt=0; string exname=""; SOCKET server=INVALID_SOCKET; //------------------------------------------------------------------ OnInit int OnInit() { EventSetTimer(1); exname=MQLInfoString(MQL_PROGRAM_NAME)+".ex5"; return 0; } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { EventKillTimer(); CloseClean(); } //------------------------------------------------------------------ OnChartEvent void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(iCnt==0) // Limit für die Erstellung der Vorlagendatei - nicht häufiger als einmal pro Sekunde { Print("Erstelle TPL"); uchar buf[]; CreateTpl(buf); uchar smb[]; StringToCharArray(Symbol(),smb); ArrayResize(smb,10); uchar tf[]; StringToCharArray(IntegerToString(Period()),tf); ArrayResize(tf,10); // Erstelle Daten für das Senden ArrayCopy(tpl,smb, ArraySize(tpl)); // für Symbolname hinzu ArrayCopy(tpl, tf, ArraySize(tpl)); // füge Periodenwert hinzu ArrayCopy(tpl,buf, ArraySize(tpl)); // füge das Template selbst hinzu } iCnt++; } //------------------------------------------------------------------ OnTimer void OnTimer() { iCnt=0; // Zurücksetzen des Vorlagen Erstellungszählers if(server==INVALID_SOCKET) StartServer(Host,Port); else { // Hole alle Clients in einer Schleife und sende die aktuelle Chartvorlage an jeden Client SOCKET client=INVALID_SOCKET; do { client=AcceptClient(); // Client-Socket akzeptieren if(client==INVALID_SOCKET) return; int slen=ArraySize(tpl); int res=send(client,tpl,slen,0); if(res==SOCKET_ERROR) Print("-Senden fehlgeschlagen Fehler: "+WSAErrorDescript(WSAGetLastError())); else printf("%d Bytes von %d gesendet",res,slen); if(shutdown(client,SD_BOTH)==SOCKET_ERROR) Print("-Schließen fehlgeschlagen Fehler: "+WSAErrorDescript(WSAGetLastError())); closesocket(client); } while(client!=INVALID_SOCKET); } } //------------------------------------------------------------------ StartServer void StartServer(string addr,ushort port) { // Initialisiere Bibliothek char wsaData[]; ArrayResize(wsaData,sizeof(WSAData)); int res=WSAStartup(MAKEWORD(2,2), wsaData); if(res!=0) { Print("-WSAStartup fehlgeschlagen Fehler: "+string(res)); return; } // Socket erstellen server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(server==INVALID_SOCKET) { Print("-Create fehlgeschlagen Fehler: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; } // An Adresse und Port binden Print("Versuche Bindung..."+addr+":"+string(port)); char ch[]; StringToCharArray(addr,ch); sockaddr_in addrin; addrin.sin_family=AF_INET; addrin.sin_addr=inet_addr(ch); addrin.sin_port=htons(port); ref_sockaddr ref=(ref_sockaddr)addrin; if(bind(server,ref.ref,sizeof(addrin))==SOCKET_ERROR) { int err=WSAGetLastError(); if(err!=WSAEISCONN) { Print("-Connect fehlgeschlagen Fehler: "+WSAErrorDescript(err)+". Socket aufräumen"); CloseClean(); return; } } // in nonblocking-Modus versetzen int non_block=1; res=ioctlsocket(server,(int)FIONBIO,non_block); if(res!=NO_ERROR) { Print("ioctlsocket fehgeschlagen Fehler: "+string(res)); CloseClean(); return; } // warten auf Port und Clientverbindungen akzeptieren if(listen(server,SOMAXCONN)==SOCKET_ERROR) { Print("Listen fehlgeschlagen mit Fehler: ",WSAErrorDescript(WSAGetLastError())); CloseClean(); return; } Print("Start Server ok"); } //------------------------------------------------------------------ Accept SOCKET AcceptClient() // Client-Socket akzeptieren { if(server==INVALID_SOCKET) return INVALID_SOCKET; ref_sockaddr ch; int len=sizeof(ref_sockaddr); SOCKET new_sock=accept(server,ch.ref,len); //sockaddr_in aclient=(sockaddr_in)ch; in Sturktur umwandlen falls es nötig ist, weitere Informationen über die Verbindung zu erhalten if(new_sock==INVALID_SOCKET) { int err=WSAGetLastError(); if(err==WSAEWOULDBLOCK) Comment("\nWAITING CLIENT ("+string(TimeCurrent())+")"); else { Print("Akzeptieren fehlgeschlagen mit Fehler: ",WSAErrorDescript(err)); CloseClean(); return INVALID_SOCKET; } } return new_sock; } CloseClean(); void CloseClean() // Socket schließen { if(server!=INVALID_SOCKET) { closesocket(server); server=INVALID_SOCKET; } WSACleanup(); Print("Server stoppen"); } //------------------------------------------------------------------ #import "kernel32.dll" int CreateFileW(string lpFileName,uint dwDesiredAccess,uint dwShareMode,uint lpSecurityAttributes,uint dwCreationDisposition,uint dwFlagsAndAttributes,int hTemplateFile); bool ReadFile(int h,ushort &lpBuffer[],uint nNumberOfBytesToRead,uint &lpNumberOfBytesRead,int lpOverlapped=0); uint SetFilePointer(int h,int lDistanceToMove,int,uint dwMoveMethod); bool CloseHandle(int h); uint GetFileSize(int h,int); #import #define FILE_BEGIN 0 #define OPEN_EXISTING 3 #define GENERIC_READ 0x80000000 #define FILE_ATTRIBUTE_NORMAL 0x00000080 #define FILE_SHARE_READ_ 0x00000001 //------------------------------------------------------------------ LoadTpl bool CreateTpl(uchar &abuf[]) { string path=TerminalInfoString(TERMINAL_PATH); string name="tcpsend.tpl"; // Vorlage erstellen ChartSaveTemplate(0,name); // Vorlage ins Array einlesen path+="\\Profiles\\Templates\\"+name; int h=CreateFileW(path, GENERIC_READ, FILE_SHARE_READ_, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if(h==INVALID_HANDLE) return false; uint sz=GetFileSize(h,NULL); ushort rbuf[]; ArrayResize(rbuf,sz); ArrayInitialize(rbuf,0); SetFilePointer(h,0,NULL,FILE_BEGIN); // nach oben bewegen int r; ReadFile(h,rbuf,sz,r,NULL); CloseHandle(h); // EA-Name von der Vorlage entfernen string a=ShortArrayToString(rbuf); ArrayResize(rbuf,0); StringReplace(a,exname," "); StringToShortArray(a,rbuf); // Datei in ein byte Array kopieren (Unicode beibehalten) sz=ArraySize(rbuf); ArrayResize(abuf,sz*2); for(uint i=0; i<sz;++i) { abuf[2*i]=(uchar)rbuf[i]; abuf[2*i+1]=(uchar)(rbuf[i]>>8); } return true; }
Der Clientcode ist etwas einfacher. Da dies als ein einmaliger Erhalt einer Datei geplant wurde, ist es nicht notwendig den EA mit permanent offenem Socket zu betreiben.
Der Client wird als Skript implementiert. Alles passiert innerhalb des OnStart Events.
//+------------------------------------------------------------------+ //| TplClient | //| Programmierung & Entwicklung - Alexey Sergeev | //+------------------------------------------------------------------+ #property copyright "© 2006-2016 Alexey Sergeev" #property link "profy.mql@gmail.com" #property version "1.00" #include "..\Experts\SocketLib.mqh" input string Host="127.0.0.1"; input ushort Port=8080; SOCKET client=INVALID_SOCKET; //------------------------------------------------------------------ OnStart void OnStart() { // Initialisiere Bibliothek char wsaData[]; ArrayResize(wsaData,sizeof(WSAData)); int res=WSAStartup(MAKEWORD(2,2), wsaData); if(res!=0) { Print("-WSAStartup fehlgeschlagen Fehler: "+string(res)); return; } // Socket erstellen client=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(client==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; } // Verbinde zum Server char ch[]; StringToCharArray(Host,ch); sockaddr_in addrin; addrin.sin_family=AF_INET; addrin.sin_addr=inet_addr(ch); addrin.sin_port=htons(Port); ref_sockaddr ref=(ref_sockaddr)addrin; res=connect(client,ref.ref,sizeof(addrin)); if(res==SOCKET_ERROR) { int err=WSAGetLastError(); if(err!=WSAEISCONN) { Print("-Verbindung fehlgeschlagen Fehler: "+WSAErrorDescript(err)); CloseClean(); return; } } // in nonblocking-Modus versetzen int non_block=1; res=ioctlsocket(client,(int)FIONBIO,non_block); if(res!=NO_ERROR) { Print("ioctlsocket fehgeschlagen Fehler: "+string(res)); CloseClean(); return; } Print("Verbindung OK"); // receive data uchar rdata[]; char rbuf[512]; int rlen=512; int rall=0; bool bNext=false; while(true) { res=recv(client,rbuf,rlen,0); if(res<0) { int err=WSAGetLastError(); if(err!=WSAEWOULDBLOCK) { Print("-Receive fehlgeschlagen Fehler: "+string(err)+" "+WSAErrorDescript(err)); CloseClean(); return; } } else if(res==0 && rall==0) { Print("-Receive. Verbindung geschlossen"); break; } else if(res>0) { rall+=res; ArrayCopy(rdata,rbuf,ArraySize(rdata),0,res); } if(res>=0 && res<rlen) break; } // Socket schließen CloseClean(); printf("%d Bytes erhalten",ArraySize(rdata)); // Symbol und die Zeit aus der Datei verwenden string smb=CharArrayToString(rdata,0,10); string tf=CharArrayToString(rdata,10,10); // Vorlagen Datei speichern int h=FileOpen("tcprecv.tpl", FILE_WRITE|FILE_SHARE_WRITE|FILE_BIN); if(h<=0) return; FileWriteArray(h,rdata,20); FileClose(h); // auf den Chart anwenden ChartSetSymbolPeriod(0,smb,(ENUM_TIMEFRAMES)StringToInteger(tf)); ChartApplyTemplate(0,"\\Files\\tcprecv.tpl"); } CloseClean(); void CloseClean() // Socket schließen { if(client!=INVALID_SOCKET) { if(shutdown(client,SD_BOTH)==SOCKET_ERROR) Print("-Schließen fehlgeschlagen Fehler: "+WSAErrorDescript(WSAGetLastError())); closesocket(client); client=INVALID_SOCKET; } WSACleanup(); Print("Verbindung geschlossen"); }
Demonstration der Interaktion zwischen diesen Codes:
Der aufmerksame Leser wird bemerken, dass der Client-Socket mit einem Aufruf der WebRequest MQL-Funktion ersetzt werden kann. Um dies zu tun, fügen Sie nur ein paar der http-Header-Zeilen zum Server hinzu und erlauben Sie die URL in den Client Terminal-Einstellungen. Es steht Ihnen frei, damit zu experimentieren.
Wichtig! In einigen Fällen wurde ein bestimmtes Verhalten des Terminals beobachtet: bei Aufruf der WSACleanup Funktion, schließt MetaTrader seine eigenen Verbindungen.
Wenn Sie bei Ihren Experimenten auf solche Probleme stoßen, kommentieren Sie WSAStartup und WSACleanup im Code aus.
Beispiel 2. Synchronization of trading by symbol
In diesem Beispiel wird der Server die Verbindung nicht schließen, wenn die Informationen gesendet wurden. Die Clientverbindung wird stabil gehalten werden. Somit werden alle Daten über Veränderungen im Handel auf dem Server sofort über die Client-Sockets gesendet. Seinerseits synchronisiert der Client, der ein Datenpaket sofort akzeptiert seine Position mit der Position auf dem Server.
Der Code des Servers und der Client aus dem vorherigen Beispiel werden als Grundlage dienen. Und wird mit Funktionen für die Arbeit mit den Positionen ergänzt.
Also, fangen wir mit dem Server an.
//+------------------------------------------------------------------+ //| SignalServer | //| Programmierung & Entwicklung - Alexey Sergeev | //+------------------------------------------------------------------+ #property copyright "© 2006-2016 Alexey Sergeev" #property link "profy.mql@gmail.com" #property version "1.00" #include "SocketLib.mqh" input string Host="0.0.0.0"; input ushort Port=8081;s bool bChangeTrades; uchar data[]; SOCKET server=INVALID_SOCKET; SOCKET conns[]; //------------------------------------------------------------------ OnInit int OnInit() { OnTrade(); EventSetTimer(1); return 0; } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { EventKillTimer(); CloseClean(); } //------------------------------------------------------------------ OnTrade void OnTrade() { double lot=GetSymbolLot(Symbol()); StringToCharArray("<<"+Symbol()+"|"+DoubleToString(lot,2)+">>",data); // Konvertiere String zu Byte-Array bChangeTrades=true; } //------------------------------------------------------------------ OnTimer void OnTimer() { if(server==INVALID_SOCKET) StartServer(Host,Port); else { AcceptClients(); // füge wartende Clients hinzu if(bChangeTrades) { Print("Sende neue PosInfo an Clients"); Send(); bChangeTrades=false; } } } //------------------------------------------------------------------ StartServer void StartServer(string addr,ushort port) { // Initialisiere Bibliothek char wsaData[]; ArrayResize(wsaData,sizeof(WSAData)); int res=WSAStartup(MAKEWORD(2,2), wsaData); if(res!=0) { Print("-WSAStartup fehlgeschlagen Fehler: "+string(res)); return; } // Socket erstellen server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(server==INVALID_SOCKET) { Print("-Create fehlgeschlagen Fehler: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; } // An Adresse und Port binden Print("Versuche Bindung..."+addr+":"+string(port)); char ch[]; StringToCharArray(addr,ch); sockaddr_in addrin; addrin.sin_family=AF_INET; addrin.sin_addr=inet_addr(ch); addrin.sin_port=htons(port); ref_sockaddr ref=(ref_sockaddr)addrin; if(bind(server,ref.ref,sizeof(addrin))==SOCKET_ERROR) { int err=WSAGetLastError(); if(err!=WSAEISCONN) { Print("-Connect fehlgeschlagen Fehler: "+WSAErrorDescript(err)+". Socket aufräumen"); CloseClean(); return; } } // in nonblocking-Modus versetzen int non_block=1; res=ioctlsocket(server,(int)FIONBIO,non_block); if(res!=NO_ERROR) { Print("ioctlsocket fehgeschlagen Fehler: "+string(res)); CloseClean(); return; } // warten auf Port und Clientverbindungen akzeptieren if(listen(server,SOMAXCONN)==SOCKET_ERROR) { Print("Listen fehlgeschlagen mit Fehler: ",WSAErrorDescript(WSAGetLastError())); CloseClean(); return; } Print("Start Server ok"); } //------------------------------------------------------------------ Accept void AcceptClients() // Client-Socket akzeptieren { if(server==INVALID_SOCKET) return; // alle wartenden Clients hinzufügen SOCKET client=INVALID_SOCKET; do { ref_sockaddr ch; int len=sizeof(ref_sockaddr); client=accept(server,ch.ref,len); if(client==INVALID_SOCKET) { int err=WSAGetLastError(); if(err==WSAEWOULDBLOCK) Comment("\nWAITING CLIENT ("+string(TimeCurrent())+")"); else { Print("Akzeptieren fehlgeschlagen mit Fehler: ",WSAErrorDescript(err)); CloseClean(); } return; } // in nonblocking-Modus versetzen int non_block=1; int res=ioctlsocket(client, (int)FIONBIO, non_block); if(res!=NO_ERROR) { Print("ioctlsocket fehlgeschlagen Fehler: "+string(res)); continue; } // Client-Socket zum Array hinzufügen int n=ArraySize(conns); ArrayResize(conns,n+1); conns[n]=client; bChangeTrades=true; // Flag um anzuzeigen, dass Informationen über die Position gesendet werden muss // Client Information zeigen char ipstr[23]={0}; sockaddr_in aclient=(sockaddr_in)ch; // zu Struktur konvertieren um weitere Informationen über die Verbindung zu erhalten inet_ntop(aclient.sin_family,aclient.sin_addr,ipstr,sizeof(ipstr)); // Hole Adresse printf("Neuen Client akzeptieren %s : %d",CharArrayToString(ipstr),ntohs(aclient.sin_port)); } while(client!=INVALID_SOCKET); } //------------------------------------------------------------------ SendClient void Send() { int len=ArraySize(data); for(int i=ArraySize(conns)-1; i>=0; --i) // Informationen an Clients senden { if(conns[i]==INVALID_SOCKET) continue; // geschlossene übergehen int res=send(conns[i],data,len,0); // senden if(res==SOCKET_ERROR) { Print("-Senden fehlgeschlagen Fehler: "+WSAErrorDescript(WSAGetLastError())+". Socket schließen"); Close(conns[i]); } } } CloseClean(); void CloseClean() // schließen und Operation löschen { printf("Shutdown Server und %d Verbindungen",ArraySize(conns)); if(server!=INVALID_SOCKET) { closesocket(server); server=INVALID_SOCKET; } // Server schließen for(int i=ArraySize(conns)-1; i>=0; --i) Close(conns[i]); // close the clients ArrayResize(conns,0); WSACleanup(); } //------------------------------------------------------------------ Close void Close(SOCKET &asock) // einen Socket schließen { if(asock==INVALID_SOCKET) return; if(shutdown(asock,SD_BOTH)==SOCKET_ERROR) Print("-Shutdown fehlgeschlagen Fehler: "+WSAErrorDescript(WSAGetLastError())); closesocket(asock); asock=INVALID_SOCKET; } //------------------------------------------------------------------ GetSymbolLot double GetSymbolLot(string smb) { double slot=0; int n=PositionsTotal(); for(int i=0; i<n;++i) { PositionSelectByTicket(PositionGetTicket(i)); if(PositionGetString(POSITION_SYMBOL)!=smb) continue; // Filter Position des aktuellen Symbols für das der Server läuft double lot=PositionGetDouble(POSITION_VOLUME); // Hole Volumen if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) lot=-lot; // Richtung beachten slot+=lot; // zur Summe addieren } return slot; }
Jede Sekunde werden die wichtigsten Bausteine des Servers geprüft: Client verbinden und ihn zum Array hinzufügen -> senden der Daten. Es prüft auch die Aktivität des Server-Sockets selbst, und im Falle eines Verbindungsabbruchs - erstellt es einen neuen Serversocket.
Der Name des Symbols des EA auf dem er ausgeführt wird und das Volumen seiner Position werden an die Clients gesendet.
Jede Handelsoperation sendet das Symbol und das Volumen als Nachrichten:
<<GBPUSD|0.25>>
<<GBPUSD|0.00>>
Nachrichten werden bei jedem Trade Event gesendet, und auch wenn sich ein neuer Client verbindet.
Diesmal ist der Code des Clients als Experte umgesetzt, da es notwendig ist, die Verbindung aufrecht zu halten. Der Client akzeptiert neue Teile von Daten vom Server und fügt sie zu den vorhandenen Daten hinzu. Dann sucht er nach dem Anfang << und Ende >> der Nachricht, analysiert sie und stellt das Volumen entsprechend dem auf dem Server für das angegebene Symbol ein.
//+------------------------------------------------------------------+ //| SignalClient | //| Programmierung & Entwicklung - Alexey Sergeev | //+------------------------------------------------------------------+ #property copyright "© 2006-2016 Alexey Sergeev" #property link "profy.mql@gmail.com" #property version "1.00" #include "SocketLib.mqh" #include <Trade\Trade.mqh> input string Host="127.0.0.1"; input ushort Port=8081;s SOCKET client=INVALID_SOCKET; // client socket string msg=""; // Warteschlagen der erhaltenen Nachrichten //------------------------------------------------------------------ OnInit int OnInit() { if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) { Alert("Client arbeitet nur mit Netting Konten"); return INIT_FAILED; } EventSetTimer(1); return INIT_SUCCEEDED; } //------------------------------------------------------------------ OnInit void OnDeinit(const int reason) { EventKillTimer(); CloseClean(); } //------------------------------------------------------------------ OnInit void OnTimer() { if(client==INVALID_SOCKET) StartClient(Host,Port); else { uchar data[]; if(Receive(data)>0) // receive data { msg+=CharArrayToString(data); // wenn etwas empfangen wurde, füge es an den gesamten String an printf("Nachricht vom Server erhalten: %s",msg); } CheckMessage(); } } CloseClean(); void StartClient(string addr,ushort port) { // Initialisiere Bibliothek int res=0; char wsaData[]; ArrayResize(wsaData, sizeof(WSAData)); res=WSAStartup(MAKEWORD(2,2), wsaData); if (res!=0) { Print("-WSAStartup fehlgeschlagen Fehler: "+string(res)); return; } // Socket erstellen client=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(client==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; } // Verbinde zum Server char ch[]; StringToCharArray(addr,ch); sockaddr_in addrin; addrin.sin_family=AF_INET; addrin.sin_addr=inet_addr(ch); addrin.sin_port=htons(port); ref_sockaddr ref=(ref_sockaddr)addrin; res=connect(client,ref.ref,sizeof(addrin)); if(res==SOCKET_ERROR) { int err=WSAGetLastError(); if(err!=WSAEISCONN) { Print("-Verbindung fehlgeschlagen Fehler: "+WSAErrorDescript(err)); CloseClean(); return; } } // in nonblocking-Modus versetzen int non_block=1; res=ioctlsocket(client,(int)FIONBIO,non_block); if(res!=NO_ERROR) { Print("ioctlsocket fehgeschlagen Fehler: "+string(res)); CloseClean(); return; } Print("Verbindung OK"); } //------------------------------------------------------------------ Receive int Receive(uchar &rdata[]) // Empfangen bis die Gegenstellen die Verbindung schließt { if(client==INVALID_SOCKET) return 0; // wenn der Socket nicht offen ist char rbuf[512]; int rlen=512; int r=0,res=0; do { res=recv(client,rbuf,rlen,0); if(res<0) { int err=WSAGetLastError(); if(err!=WSAEWOULDBLOCK) { Print("-Receive fehlgeschlagen Fehler: "+string(err)+" "+WSAErrorDescript(err)); CloseClean(); return -1; } break; } if(res==0 && r==0) { Print("-Receive. Verbindung geschlossen"); CloseClean(); return -1; } r+=res; ArrayCopy(rdata,rbuf,ArraySize(rdata),0,res); } while(res>0 && res>=rlen); return r; } CloseClean(); void CloseClean() // Socket schließen { if(client!=INVALID_SOCKET) { if(shutdown(client,SD_BOTH)==SOCKET_ERROR) Print("-Schließen fehlgeschlagen Fehler: "+WSAErrorDescript(WSAGetLastError())); closesocket(client); client=INVALID_SOCKET; } WSACleanup(); Print("Socket schließen"); } //------------------------------------------------------------------ CheckMessage void CheckMessage() { string pos; while(FindNextPos(pos)) { printf("server position: %s",pos); }; // jüngste Änderung vom Server holen if(StringLen(pos)<=0) return; // Daten von der Nachricht beziehen string res[]; if(StringSplit(pos,'|',res)!=2) { printf("-wrong pos info: %s",pos); return; } string smb=res[0]; double lot=NormalizeDouble(StringToDouble(res[1]),2); // Volumen synchronisieren if(!SyncSymbolLot(smb,lot)) msg="<<"+pos+">>"+msg; // wenn ein Fehler auftritt, Nachricht an den Anfang des "Threads" stellen } //------------------------------------------------------------------ SyncSymbolLot bool SyncSymbolLot(string smb,double nlot) { // Volumen des Servers und Clients synchronisieren CTrade trade; double clot=GetSymbolLot(smb); // hole aktuelle Lots für das Symbol if(clot==nlot) { Print("keine Änderung"); return true; } // wenn die Volumina gleich sind, nichts tun // Zuerst den Spezialfall prüfen, dass auf dem Server keine Positionen existieren if(nlot==0 && clot!=0) { Print("vollständiges Schließen der Position"); return trade.PositionClose(smb); } // Wenn der Server eine Position hat, diese auf dem Client anpassen double dif=NormalizeDouble(nlot-clot,2); // kaufen oder verkaufen - abhängig vom Unterschied if(dif>0) { Print("füge Kaufposition hinzu"); return trade.Buy(dif,smb); } else { Print("füge Verkaufpositon hinzu"); return trade.Sell(-dif,smb); } } //------------------------------------------------------------------ FindNextPos bool FindNextPos(string &pos) { int b=StringFind(msg, "<<"); if(b<0) return false; // keine Anfang der Nachricht int e=StringFind(msg, ">>"); if(e<0) return false; // keine Ende der Nachricht pos=StringSubstr(msg,b+2,e-b-2); // Hole den Informationsblock msg=StringSubstr(msg,e+2); // entferne ihn von der Nachricht return true; } //------------------------------------------------------------------ GetSymbolLot double GetSymbolLot(string smb) { double slot=0; int n=PositionsTotal(); for(int i=0; i<n;++i) { PositionSelectByTicket(PositionGetTicket(i)); if(PositionGetString(POSITION_SYMBOL)!=smb) continue; // Filter Position des aktuellen Symbols für das der Server läuft double lot=PositionGetDouble(POSITION_VOLUME); // Hole Volumen if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) lot=-lot; // Richtung beachten slot+=lot; // zur Summe addieren } return NormalizeDouble(slot,2); }
Die letzte Demonstration eines gekoppelten Betriebs von Server und Client:
Beispiel 3. Tick Sammler.
Dieses Beispiel demonstriert UDP Sockets. Darin wartet der Server auf Daten für ein Symbol vom Client.
Der Server-Code ist einfach, da gibt es keine Notwendigkeit Informationen über Clients zu speichern und auf ihre Verbindungen zu achten. Prüfungen der Socket-Daten können mit Hilfe eines Millisekunden-Timers beschleunigt werden:
input string Host="0.0.0.0"; input ushort Port=8082; SOCKET server=INVALID_SOCKET; //------------------------------------------------------------------ OnInit int OnInit() { EventSetMillisecondTimer(300); return 0; } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { EventKillTimer(); CloseClean(); } //------------------------------------------------------------------ OnTimer void OnTimer() { if(server!=INVALID_SOCKET) { char buf[1024]={0}; ref_sockaddr ref={0}; int len=ArraySize(ref.ref); int res=recvfrom(server,buf,1024,0,ref.ref,len); if (res>=0) // receive and display data Print("Tick vom Client erhalten: ", CharArrayToString(buf)); else { int err=WSAGetLastError(); if(err!=WSAEWOULDBLOCK) { Print("-receive fehlgeschlagen Fehler: "+WSAErrorDescript(err)+". Socket aufräumen"); CloseClean(); return; } } } else // ansonsten den Server starten { // Initialisiere Bibliothek char wsaData[]; ArrayResize(wsaData,sizeof(WSAData)); int res=WSAStartup(MAKEWORD(2,2), wsaData); if(res!=0) { Print("-WSAStartup fehlgeschlagen Fehler: "+string(res)); return; } // Socket erstellen server=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); if(server==INVALID_SOCKET) { Print("-Create fehlgeschlagen Fehler: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; } // An Adresse und Port binden Print("versuche Bindung..."+Host+":"+string(Port)); char ch[]; StringToCharArray(Host,ch); sockaddr_in addrin; addrin.sin_family=AF_INET; addrin.sin_addr=inet_addr(ch); addrin.sin_port=htons(Port); ref_sockaddr ref=(ref_sockaddr)addrin; if(bind(server,ref.ref,sizeof(addrin))==SOCKET_ERROR) { int err=WSAGetLastError(); if(err!=WSAEISCONN) { Print("-Connect fehlgeschlagen Fehler: "+WSAErrorDescript(err)+". Socket aufräumen"); CloseClean(); return; } } // in nonblocking-Modus versetzen int non_block=1; res=ioctlsocket(server,(int)FIONBIO,non_block); if(res!=NO_ERROR) { Print("ioctlsocket fehgeschlagen Fehler: "+string(res)); CloseClean(); return; } Print("Start Server ok"); } } CloseClean(); void CloseClean() // schließen und Operation löschen { printf("Shutdown Server"); if(server!=INVALID_SOCKET) { closesocket(server); server=INVALID_SOCKET; } // Server schließen WSACleanup(); }
Der Clientcode ist auch einfach. Die gesamte Verarbeitung erfolgt innerhalb des Tick-Arrival-Ereignis:
input string Host="127.0.0.1"; input ushort Port=8082; SOCKET client=INVALID_SOCKET; // client socket ref_sockaddr srvaddr={0}; // Struktur für Verbindung zum Server //------------------------------------------------------------------ OnInit int OnInit() { // Struktur für den Server füllen char ch[]; StringToCharArray(Host,ch); sockaddr_in addrin; addrin.sin_family=AF_INET; addrin.sin_addr=inet_addr(ch); addrin.sin_port=htons(Port); srvaddr=(ref_sockaddr)addrin; OnTick(); // Socket sofort erstellen return INIT_SUCCEEDED; } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { CloseClean(); } //------------------------------------------------------------------ OnTick void OnTick() { if(client!=INVALID_SOCKET) // wenn der Socket erstellt wurde senden { uchar data[]; StringToCharArray(Symbol()+" "+DoubleToString(SymbolInfoDouble(Symbol(),SYMBOL_BID),Digits()),data); if(sendto(client,data,ArraySize(data),0,srvaddr.ref,ArraySize(srvaddr.ref))==SOCKET_ERROR) { int err=WSAGetLastError(); if(err!=WSAEWOULDBLOCK) { Print("-Send fehlgeschlagen Fehler: "+WSAErrorDescript(err)); CloseClean(); } } else Print("sende "+Symbol()+" Tick an Server"); } else // Client Socket erstellen { int res=0; char wsaData[]; ArrayResize(wsaData,sizeof(WSAData)); res=WSAStartup(MAKEWORD(2,2),wsaData); if(res!=0) { Print("-WSAStartup fehlgeschlagen Fehler: "+string(res)); return; } // Socket erstellen client=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); if(client==INVALID_SOCKET) { Print("-Create failed error: "+WSAErrorDescript(WSAGetLastError())); CloseClean(); return; } // in nonblocking-Modus versetzen int non_block=1; res=ioctlsocket(client,(int)FIONBIO,non_block); if(res!=NO_ERROR) { Print("ioctlsocket fehgeschlagen Fehler: "+string(res)); CloseClean(); return; } Print("Socket erstellen OK"); } } CloseClean(); void CloseClean() // Socket schließen { if(client!=INVALID_SOCKET) { closesocket(client); client=INVALID_SOCKET; } WSACleanup(); Print("Socket schließen"); }
Und hier ist die Demonstration der letzten Arbeit:
3. Weitere Methoden, um den Server zu verbessern
Natürlich sind diese Beispiele für Server die Informationen zu einem beliebigen Client senden nicht optimal. Beispielsweise sollten Sie den Zugriff auf Ihre Daten einschränken. Die verbindlichen Anforderungen müssen also mindestens Folgendes enthalten:
- Client-Autorisierung (Login/Passwort);
- Schutz gegen das Erraten von Passwörtern (Verbannung/Login oder IP-Sperre).
Darüber hinaus wird die Arbeit des Servers nur innerhalb eines Threads (im Timer eines Experten) durchgeführt. Dies ist kritisch bei einer großen Anzahl von Verbindungen oder große Mengen an Informationen. Daher ist es sinnvoll, mindestens einen Pool von Experten (jeweils mit eigenem Timer) hinzuzufügen, die die Interaktion mit Client-Verbindungen behandeln, um den Server zu optimieren. Das macht den Server zu einem gewissen Grad Multi-Threaded.
Ob Sie das in MQL erledigen oder nicht liegt bei Ihnen. Es gibt andere Möglichkeiten das zu tun, die vielleicht bequemer sein könnten. Jedoch kann die Tatsache, dass MQL den Vorteil eines direkten Zugangs zum Trading-Konto und Quotierungen hat, nicht bestritten werden, als auch die Offenheit des MQL-Codes der keine Fremdanbieter-DLLs verwendet.
Fazit
Wie sonst können die Sockets in MetaTrader werden verwendet? Bevor der Artikel geschrieben wurde, gab es mehrere Ideen die man als Beispiel hätte verwenden können:
- Markt-Sentiment-Indikator (wenn die angeschlossenen Clients die Volumina ihrer Positionen senden und als Antwort das Gesamtvolumen von allen Kunden erhalten);
- oder zum Beispiel das Versenden von Indikator Berechnungen vom Server an Clients (für Abonnenten);
- oder umgekehrt - Kunden helfen bei aufwendigen Berechnungen (Tester Agent-Netzwerk);
- Es ist möglich, den Server nur zu einem "Proxy" zu machen, für den Austausch von Daten zwischen Clients.
Es gibt viele Optionen. Wenn Sie Ideen zur Anwendung haben, teilen Sie sie in den Kommentaren zum Artikel. Wenn sie interessant sind, können wir sie vielleicht gemeinsam umsetzen.
Ich wünsche Ihnen viel Glück und große Gewinne!
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2599





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Danke Für die ausführliche Dokumentation, das hat mir definitv ein ganzes Stück weiter geholfen ;)
Trotzdem, in neueren Versionen scheint das folgende Casting nicht zu funktionieren.
Der Workaround, der für mich geholfen hat war folgender:
Cheers, Jan