MetaTrader 5 herunterladen

Arbeiten mit Sockets in MQL, oder Wie man ein Signalprovider wird

21 Juli 2016, 10:42
o_o
0
569

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    ulong

Kopiere 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.

e) recv() und send() für TCP

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 Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/2599

Beigefügte Dateien |
MQL5.zip (24.71 KB)
Grafische Interfaces VI: Die Slider und Dual-Slider Controls (Kapitel 2) Grafische Interfaces VI: Die Slider und Dual-Slider Controls (Kapitel 2)

In dem vorherigen Artikel haben wir unsere Bibliothek um die vier sehr häufig verwendeten Controls: Checkbox, Edit, Edit mit Checkbox und Check-Combobox erweitert. Das zweite Kapitel des sechsten Teils widmet sich den Slider (Schieberegler) und Dual-Slider Controls.

Graphische Interfaces VI: Das Checkbox Control, Das Edit Control und deren gemischte Typen (Kapitel 1) Graphische Interfaces VI: Das Checkbox Control, Das Edit Control und deren gemischte Typen (Kapitel 1)

Dieser Artikel ist der Anfang des 6. Teils dieser Serie, welche der Entwicklung einer Bibliothek für die Erzeugung von grafischen Interfaces im Metatrader-Terminal gewidmet ist. In dem ersten Kapitel werden wir das Checkbox-Control, dass Edit-Control und deren gemischte Typen besprechen.

Selbstoptimierung der Experten: evolutionäre und genetische Algorithmen Selbstoptimierung der Experten: evolutionäre und genetische Algorithmen

Im Artikel werden die Hauptprinzipien betrachtet, die in den Evolutionsalgorithmen versetzt sind, auch ihre Arten und die Besonderheiten. Auf dem Beispiel des einfachen Experten mit Hilfe der Experimente wird es vorgeführt, was unserem Handelnsystem die Anwendung der Optimierung geben kann. Wir betrachten die Programm-Pakete, die genetische, evolutionäre und andere Arten der Optimierung realisieren und führen die Anwendungsbeispiele bei einer Optimierung eines Prädiktor-Satzes und bei einer Optimierung des Handelnsystems hin.

Wie man die Signale mit Hilfe vom Berater nach seinen Regeln kopieren soll? Wie man die Signale mit Hilfe vom Berater nach seinen Regeln kopieren soll?

Beim Abonnieren zu Signalen kann eine solche Situation auftreten: Ihre Hebelwirkung im Trading-Konto ist 1:100, der Anbieter hat einen Hebel von 1: 500 und handelt mit einem minimalen Lot, und Ihre Handelsbilanzen handeln nahezu gleich - mit dem Abbildungsverhältnis zwischen 10% und 15%. In diesem Artikel erfahren Sie, wie in diesem Fall das Abbildungsverhältnis erhöhen kann.