English Русский Español 日本語 Português
preview
Marktsimulation (Teil 09): Sockets (III)

Marktsimulation (Teil 09): Sockets (III)

MetaTrader 5Beispiele |
28 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel, Marktsimulation (Teil 08): Sockets (II), haben wir mit der Entwicklung einer praktischen Anwendung begonnen, die Sockets verwendet. Ziel war es, die Verwendung dieses Tools in einer auf MetaTrader 5 ausgerichteten Programmierung zu demonstrieren. Es stimmt, dass MQL5 es uns nicht erlaubt, einen Server direkt mit reinem MQL5 zu erstellen. Da die Verwendung von Sockets jedoch unabhängig von einer bestimmten Sprache oder sogar dem Betriebssystem ist, können wir sie auch im MetaTrader 5 verwenden, indem wir die Programmierung in MQL5 implementieren.

Aus internen Gründen der MetaTrader 5-Plattform selbst können wir jedoch keine Indikatoren zusammen mit Sockets verwenden. Oder genauer gesagt: Wir können keine Aufrufe zu Socket-bezogenen Prozeduren innerhalb eines Indikatorcodes platzieren. Der Grund dafür ist, dass wir sonst die Leistung der in den Indikatoren durchgeführten Berechnungen einfrieren oder beeinträchtigen könnten.

Dennoch hindert uns nichts daran, Indikatoren auch für andere Zwecke zu verwenden. Und genau das haben wir im vorherigen Artikel getan, in dem wir unseren gesamten Mini-Chat, einschließlich der Steuerelemente und des Textfelds, in einem Indikator erstellt haben. Die Details, die im Indikator erstellt und platziert werden, beeinträchtigen in keiner Weise den Ausführungsfluss der Indikatoren. Aber ohne die Verwendung eines Indikators wäre es ziemlich kompliziert, das zu erstellen, was im vorigen Artikel gemacht wurde, weil wir am Ende mit einem Bereich des Charts des Vermögenswertes, der geplottet wird, interferieren würden.

Auf diese Weise haben wir jedoch keine solchen Probleme, sodass unser Mini-Chat in einem eigenen Fenster isoliert bleibt.

Die ausführbare Datei, die im vorigen Artikel erstellt wurde, ist jedoch nicht in der Lage, unsere Anforderungen zu erfüllen, damit der Mini-Chat tatsächlich funktioniert. Wir müssen noch ein paar weitere Details umsetzen. Hier werden wir also die notwendige Unterstützung schaffen, damit der Mini-Chat in MetaTrader 5 funktioniert. Dennoch ist MQL5, wie eingangs erwähnt, derzeit nicht in der Lage, alle unsere Anforderungen zu erfüllen. Daher müssen wir auf eine externe Programmierung zurückgreifen. An diesem Punkt kann jedoch etwas getan werden. Ich werde dies später in diesem Artikel erklären. Lassen Sie uns nun den Teil abschließen, der in MQL5 durchgeführt werden soll.


Implementieren der Verbindungsklasse

Bevor wir uns den endgültigen Quellcode des Expert Advisors ansehen, mit dem der Mini-Chat tatsächlich funktioniert, wollen wir uns die Header-Datei ansehen, die die Verbindungsklasse implementiert. Der vollständige Code ist nachstehend aufgeführt:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. class C_Connection
05. {
06.    private   :
07.       int      m_Socket;
08.       char     m_buff[];
09.       string   m_info;
10.       bool     m_NewLine;
11.    public   :
12. //+------------------------------------------------------------------+
13.       C_Connection(string addr, ushort port, ushort timeout = 1000)
14.          :m_Socket(INVALID_HANDLE),
15.           m_info(""),
16.           m_NewLine(true)
17.       {
18.          if ((m_Socket = SocketCreate()) == INVALID_HANDLE) Print("Unable to create socket. Error: ", GetLastError());
19.          else if (SocketConnect(m_Socket, addr, port, timeout)) return;
20.          else Print("Connection with the address [", addr,"] in port: ",port, " failed. Error code: ", GetLastError());
21.          SetUserError(1);
22.       }
23. //+------------------------------------------------------------------+
24.       ~C_Connection()
25.       {
26.          SocketClose(m_Socket);
27.       }
28. //+------------------------------------------------------------------+
29.       bool ConnectionWrite(string szMsg)
30.       {
31.          int len = StringToCharArray(szMsg, m_buff) - 1;
32. 
33.          if (m_Socket != INVALID_HANDLE)
34.             if (len >= 0)
35.                if (SocketSend(m_Socket, m_buff, len) == len)
36.                   return true;
37.          Print("Connection Write: FAILED...");
38. 
39.          return false;
40.       }
41. //+------------------------------------------------------------------+
42.       const string ConnectionRead(ushort timeout = 500)
43.       {
44.          int ret;
45. 
46.          if (m_NewLine)
47.          {
48.             m_info = "";
49.             m_NewLine = false;
50.          }
51.          ret = SocketRead(m_Socket, m_buff, SocketIsReadable(m_Socket), timeout);
52.          if (ret > 0)
53.          {
54.             m_info += CharArrayToString(m_buff, 0, ret);
55.             for (ret--; (ret >= 0) && (!m_NewLine); ret--)
56.                m_NewLine = (m_buff[ret] == '\n') || (m_buff[ret] == '\r');
57.          }
58.          
59.          return (m_NewLine ? m_info : "");
60.       }
61. //+------------------------------------------------------------------+
62. };
63. //+------------------------------------------------------------------+

Die Header-Datei C_Study.mqh

Ich möchte, dass Sie auf bestimmte Details in diesem Code achten, denn wenn Sie sie verstehen, werden Sie wirklich verstehen, wie der Mini-Chat funktioniert. Unsere Klasse enthält einige globale Variablen, die privat sind, wie Sie zwischen den Zeilen 07 und 10 sehen können. Beachten Sie, dass alle diese Variablen nach der privaten Klausel erscheinen.

Daher kann auf sie außerhalb der Klasse nicht zugegriffen werden. Das erste, was wir tun, wenn wir diese Klasse verwenden, ist, ihren Konstruktor aufzurufen. Die erste auszuführende Zeile ist also Zeile 13. Beachten Sie nun, dass wir in den Zeilen 14, 15 und 16 ein paar Dinge tun. Die globalen Variablen der Klasse werden dort initialisiert. Ein Detail: Ich tue dies hier und auf diese Weise aus Gewohnheit, aber hauptsächlich, um zu vermeiden, dass ich vergesse, es innerhalb des Hauptteils des Konstruktors zu tun. Ob Sie es glauben oder nicht, diese Art von Versehen ist sehr häufig. Auf diese Weise kann ich sicherstellen, dass ich nicht vergesse, die Hauptvariablen richtig zu initialisieren.

Sehr gut. Sobald der Konstruktor mit seiner Ausführung beginnt, versuchen wir in Zeile 18 mit einem Aufruf der Standardbibliothek, einen Socket zu erstellen. Wenn dies fehlschlägt, wird im MetaTrader 5 eine Meldung ausgegeben. Wenn dies gelingt, wird in Zeile 19 versucht, eine Verbindung zu dem in den Parametern angegebenen Socket herzustellen. Aber im Gegensatz zu dem, was in der vorherigen Zeile passiert ist, kehren wir hier, wenn wir Erfolg haben, sofort zurück. Der Grund dafür ist, dass die Verbindung hergestellt wurde. Im Falle einer Störung werden wir in Zeile 20 über den Vorfall und den Grund für die Störung informiert.

Wenn es nicht möglich ist, einen Socket zu erstellen oder eine Verbindung herzustellen, können wir mit Zeile 21 den Hauptcode, in diesem Fall den Expert Advisor, darüber informieren, dass der Verbindungsprozess fehlgeschlagen ist. Wenn wir uns den Code des Expert Advisors ansehen, werde ich Ihnen erklären, wie Sie dies erkennen können. Zeile 21 wird nur im Falle eines Fehlers beim Verbindungsversuch ausgeführt.

Der Destruktor, der sich in Zeile 24 befindet, hat nur einen einzigen Zweck: das Schließen des Sockets. Aus diesem Grund hat er nur eine Zeile im Hauptteil. Dies ist die Zeile 26, in der der verwendete Socket freigegeben wird, sofern er erstellt wurde.

Schauen wir uns nun die beiden Hauptfunktionen unserer Klasse an. Der erste ist für das Senden von Daten über die Verbindung zuständig. Richtig, es geht um die Schreibfunktion, die sich in Zeile 29 befindet. Diese Funktion muss genau wie die nächste mit etwas Sorgfalt implementiert werden, denn der Rest des Codes ändert sich überhaupt nicht, egal, was Sie senden wollen: ob Bilder, Töne, Videos oder Text, wie hier geschehen. Was sich wirklich ändert, sind nur diese beiden Funktionen: Schreiben und Lesen. In diesem Fall tun wir die Dinge auf die einfachste Art und Weise. Daher werden wir bei der Übermittlung von Informationen keine Verschlüsselung oder andere Sicherheitsvorkehrungen treffen. Der Grund dafür ist einfach: Wir testen und lernen gerade, wie Sockets funktionieren.

Da wir tatsächlich nur Text übertragen wollen, müssen wir zunächst die Anzahl der zu übertragenden Zeichen bestimmen. Dies geschieht in Zeile 31. Die Tatsache, dass wir von der Anzahl der Zeichen eins abziehen, ist darauf zurückzuführen, dass die empfangene Zeichenkette dem C/C++-Standard entspricht, d. h. sie wird durch ein Nullzeichen abgeschlossen. Dieses Zeichen wird von uns nicht übermittelt. Da der Konstruktor bei der Erstellung oder sogar bei der Verbindung fehlgeschlagen sein kann, müssen wir prüfen, ob der Socket gültig ist, bevor wir versuchen, etwas über ihn zu übertragen. Dieser Test wird in Zeile 33 durchgeführt. Wenn der Socket aus irgendeinem Grund ungültig ist, wird die Übertragung nicht stattfinden.

Nachdem dies getestet wurde, müssen wir noch eine weitere Sache testen. Wir müssen die Anzahl der zu übertragenden Zeichen überprüfen. Es macht nämlich keinen Sinn, etwas zu senden, wenn es nichts zu senden gibt. Dieser Test wird in Zeile 34 durchgeführt. Wenn alle diese Tests erfolgreich verlaufen, wird in Zeile 35 versucht, die gewünschten Daten über den geöffneten Socket zu senden. In diesem Fall senden wir über eine normale Verbindung, d. h. wir verwenden keinerlei Verschlüsselung oder Authentifizierungsverfahren. MQL5 ermöglicht jedoch sichere Übertragungen. Studieren Sie die Dokumentation, um dieses Thema besser zu verstehen.

Gelingt es uns, die Daten zu senden, geben wir in Zeile 36 einen true-Wert an den Aufrufer zurück. Wenn einer der Tests fehlschlägt, wird in Zeile 37 eine Meldung in MetaTrader 5 ausgegeben, und in Zeile 39 wird ein falscher Wert an den Aufrufer zurückgegeben.

Schauen wir uns nun die Lesefunktion an, die sich in Zeile 42 befindet. Hier lohnt es sich, kurz innezuhalten und etwas zu erklären. Jede Schreibfunktion sollte eine entsprechende Lesefunktion haben. Dies ist nicht zwingend erforderlich, aber denken Sie an folgendes Szenario: Sie können mehrere Funktionen erstellen, um verschiedene Arten von Daten zu senden. In diesem Fall ist es angebracht, dass die Daten von entsprechenden Funktionen empfangen werden.

Mit anderen Worten: Warum sollten Sie versuchen, Daten, die ein Video darstellen, in einer Funktion zu lesen, die für das Lesen von Text konzipiert ist? Das ergibt doch keinen Sinn, oder? Versuchen Sie daher immer, die Dinge richtig abzustimmen, sowohl in Bezug auf die Art der übertragenen Daten als auch auf die Art des verwendeten Protokolls.

Dieser Teil des Protokolls hat nichts mit einer TCP- oder UDP-Verbindung selbst zu tun, sondern mit der Art und Weise, wie die Informationen vor der Übertragung strukturiert wurden. Stellen Sie sich die Daten im Socket immer wie eine Datei vor, die Sie gerade lesen. Um den Inhalt richtig zu verstehen, müssen Sie das richtige Leseprotokoll verwenden.

Es macht keinen Sinn zu versuchen, eine Bitmap mit einem Protokoll zu lesen, das für das Lesen von JPEG-Bildern entwickelt wurde. Obwohl es sich bei beiden um Bilder handelt, sind sie innerhalb der Datei völlig unterschiedlich aufgebaut. Das Gleiche gilt auch für die Sockets. Nach dieser Warnung wollen wir uns nun der Socket-Leseroutine widmen.

Anders als in dem Artikel Marktsimulation (Teil 07): Sockets (I) beschrieben, wo wir ein Echo auf dem Server erzeugt haben, bleiben wir hier nicht unbedingt blockiert und warten auf die Antwort des Servers. Wir warten sogar eine kurze Zeit, bis sie reagiert. Der Punkt ist, dass wir unabhängig davon, ob er schneller oder langsamer antwortet, zum Aufrufer zurückkehren, noch bevor die gesamte Nachricht vom Socket gelesen wurde.

Aber wie? Können wir zu unserem Expert Advisor Code zurückkehren, noch bevor wir den Socket vollständig ausgelesen haben? Ja, das können wir, aber wir müssen dabei einige Vorsichtsmaßnahmen treffen. Deshalb habe ich bereits erwähnt, dass Sie darauf achten sollten, die Art von Informationen, die vom Socket erwartet werden, richtig zu behandeln. Hier, in unserer Lesefunktion, warten wir standardmäßig 500 Millisekunden, bevor wir zurückkehren. Wenn diese Zeit verstrichen ist, kehren wir trotzdem zum Hauptcode zurück, auch wenn keine Informationen im Socket vorhanden sind.

Wenn in dieser Zeit jedoch eine Information erscheint, lesen wir sie in Zeile 51. Achten Sie nun auf die folgende Tatsache, die wichtig ist, wenn Sie einen eigenen Server einrichten wollen. In Zeile 54 fügen wir den im Socket vorhandenen Inhalt in die Rückgabezeichenfolge ein. Diese Rückgabezeichenfolge gibt jedoch nur dann etwas an den Aufrufer zurück, wenn ein Zeilenumbruch oder ein Wagenrücklaufzeichen in der Zeichenfolge gefunden wird. Um dies zu überprüfen, verwenden wir die Schleife in Zeile 55, die den empfangenen Inhalt auf der Suche nach den oben genannten Zeichen durchsucht.

Wenn eines dieser Zeichen gefunden wird, erhält die Variable m_NewLine den Wert true. So kann in Zeile 59 der aus dem Socket gelesene Inhalt an den Aufrufer zurückgegeben werden. Wenn ein neuer Aufruf erfolgt, erlaubt der Test in Zeile 46, den Inhalt der Zeichenkette zu löschen, sodass ein neuer Zyklus beginnt.

Achtung: Im Code der Socket-Schreibfunktion, der sich in Zeile 29 befindet, fügen wir nicht die von der Lesefunktion erwarteten Zeichen hinzu. Auch das Objekt OBJ_EDIT fügt keine solchen Zeichen hinzu. Auch der Code, der die Funktion ConnectionWrite aufruft, tut dies nicht. Wer fügt also diese Figuren hinzu? Der Server tut es. Wenn Sie also einen eigenen Server einrichten wollen, müssen Sie diese Zeichen hinzufügen. Andernfalls kann der Mini-Chat nicht zwischen einer geposteten Nachricht und einer anderen unterscheiden.

Diese Vorgehensweise mag ein wenig töricht oder sinnlos erscheinen. Aber auf diese Weise können wir etwas erreichen, was Sie in den Videos in diesem Artikel sehen können. Sehr gut. Nachdem wir nun den Code für die Verbindungsklasse gesehen haben, wollen wir uns den Code für den Expert Advisor ansehen.


Umsetzung des Advisor Advisor

Der vollständige Code des Expert Advisors, der zur Implementierung des Mini-Chats verwendet wurde, ist unten vollständig zu sehen:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Mini Chat for demonstration of using sockets in MQL5."
04. #property link      "https://www.mql5.com/pt/articles/12673"
05. #property version   "1.00"
06. //+------------------------------------------------------------------+
07. #define def_IndicatorMiniChat   "Indicators\\Mini Chat\\Mini Chat.ex5"
08. #resource "\\" + def_IndicatorMiniChat
09. //+------------------------------------------------------------------+
10. #include <Market Replay\Mini Chat\C_Connection.mqh>
11. #include <Market Replay\Defines.mqh>
12. //+------------------------------------------------------------------+
13. input string   user00 = "127.0.0.1";      //Address
14. input ushort   user01 = 27015;            //Port
15. //+------------------------------------------------------------------+
16. long    gl_id;
17. int    gl_sub;
18. C_Connection *Conn;
19. //+------------------------------------------------------------------+
20. int OnInit()
21. {
22.    Conn = new C_Connection(user00, user01);
23.    if (_LastError > ERR_USER_ERROR_FIRST)
24.       return INIT_FAILED;   
25.    ChartIndicatorAdd(gl_id = ChartID(), gl_sub = (int) ChartGetInteger(gl_id, CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_IndicatorMiniChat));
26. 
27.    EventSetTimer(1);
28. 
29.    return INIT_SUCCEEDED;
30. }
31. //+------------------------------------------------------------------+
32. void OnDeinit(const int reason)
33. {
34.    EventKillTimer();
35.    delete Conn;
36.    ChartIndicatorDelete(gl_id, gl_sub, ChartIndicatorName(gl_id, gl_sub, 0));
37. }
38. //+------------------------------------------------------------------+
39. void OnTick()
40. {
41. }
42. //+------------------------------------------------------------------+
43. void OnTimer()
44. {
45.    string sz0 = (*Conn).ConnectionRead();
46.    if (sz0 != "")
47.       EventChartCustom(gl_id, evChatReadSocket, 0, 0, sz0);
48. }
49. //+------------------------------------------------------------------+
50. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
51. {
52.    if (id == CHARTEVENT_CUSTOM + evChatWriteSocket)
53.       (*Conn).ConnectionWrite(sparam);
54. }
55. //+------------------------------------------------------------------+

Quellcode des Expert Advisors

Beachten Sie, dass der Code recht einfach ist. Wir brauchen hier praktisch nicht viel zu programmieren. Dennoch gibt es einige Vorsichtsmaßnahmen zu beachten. Beachten Sie, dass wir in den Zeilen 07 und 08 den im vorherigen Artikel erstellten Indikator zum Code des Expert Advisors hinzufügen. Sobald der Code des Expert Advisors kompiliert ist, kann die ausführbare Datei des Indikators gelöscht werden, da sie in den Expert Advisor eingebettet wird. Im letzten Artikel habe ich die Gründe für diese Maßnahme erläutert. Der Code verläuft ohne große Änderungen im Vergleich zu dem, was in dem früheren Artikel zu sehen war.

Das Detail ist nun, dass ein Anruf enthalten war, um Timer-Ereignisse zu empfangen. Dies geschieht in Zeile 27. So wird etwa einmal pro Sekunde der für die Behandlung von Timer-Ereignissen zuständige Code ausgeführt, d. h. es erfolgt ein Aufruf von OnTime. Was in diesem Expert Advisor Code wirklich zählt, ist genau dieser OnTime Aufruf und der OnChartEvent Aufruf. Diese beiden ermöglichen es, dass der Mini-Chat so funktioniert, wie er jetzt implementiert ist. Zunächst wollen wir das Verfahren OnTime verstehen.

Wann immer MetaTrader 5 ein OnTime-Ereignis auslöst, wird unser Expert Advisor diesen Aufruf abfangen. Dann, in Zeile 45, versuchen wir, von dem in den Zeilen 13 und 14 angegebenen Socket zu lesen. Das ist richtig – wir werden eine Verbindung zu einem Netzwerk herstellen. Dies wurde vor zwei Artikeln erklärt, in denen ich über Sockets sprach. Wenn Sie daran zweifeln, lesen Sie den Artikel Sockets (I).

Sehr gut. Der Leseversuch kann eine Zeichenkette zurückgeben oder nicht. Wenn wir von der Lesefunktion einen Rückgabewert erhalten, ist der Test in Zeile 46 erfolgreich. In diesem Fall lösen wir ein nutzerdefiniertes Ereignis aus, das in Zeile 47 angezeigt wird. Der Zweck dieses Ereignisses besteht darin, dem Mini-Chat-Indikator den Zugriff auf die im Socket gespeicherten Informationen zu ermöglichen. Wenn der Mini-Chat direkt im Expert Advisor implementiert wäre, wäre ein solches nutzerdefiniertes Ereignis völlig unnötig. Aber da wir die Dinge getrennt haben, müssen wir die Daten an den Indikator senden, damit er die Informationen im Textfeld anzeigen kann. Sehen Sie, wie interessant das wird: Je nachdem, was wir anzeigen wollen, müssen wir uns an die beste Art und Weise der Umsetzung der Lösung anpassen.

Es gibt keine weiteren Einzelheiten zum OnTime-Verfahren. So einfach ist das. Auch die Prozedur OnChartEvent ist sehr einfach. Dieses Mal fangen wir eine Anfrage des Mini-Chat-Indikators ab, um in den Socket zu schreiben. Um zu prüfen, ob ein solcher Aufruf vorliegt, verwenden wir den Test in Zeile 52. Wenn bestätigt wird, dass es sich um ein nutzerdefiniertes Ereignis handelt, leitet Zeile 53 die vom Mini-Chat-Indikator bereitgestellten Daten an die Schreibfunktion weiter.

In diesem Kommunikationssystem gibt es ein Detail, das für diejenigen, die mit ereignisgesteuerter Programmierung nicht vertraut sind, vielleicht nicht so offensichtlich ist. Wenn ein anderes Programm (ein anderer Indikator, ein Skript oder sogar ein Dienst) ein nutzerdefiniertes Ereignis mit demselben Wert wie evChatReadSocket auslöst, interpretiert der Mini-Chat-Indikator dieses Ereignis so, als käme es vom Expert Advisor. Dasselbe geschieht, wenn ein nutzerdefiniertes Ereignis mit demselben Wert wie evChatWriteSocket ausgelöst wird. Der Expert Advisor interpretiert sie so, als käme sie vom Mini-Chat-Indikator.

Ich weiß nicht, ob Sie bemerkt haben, welche Möglichkeiten dies eröffnet. Vor allem, wenn Sie die Adresse und den Port kennen, die die Daten, die Sie über den Socket senden, empfangen werden, und ebenfalls von einer bestimmten Adresse oder einem bestimmten Port lesen können. Diese Art von Mechanismus eröffnet die Möglichkeit, einen kleinen „Spion“ zu schaffen, der das Geschehen an einem bestimmten Ort beobachtet. Deshalb müssen wir immer alles richtig verschlüsseln und kodieren. Da wir dies hier jedoch nur zu Demonstrationszwecken verwenden, ist das Risiko eines Datenverlusts minimal, praktisch gleich Null. Wie Sie in den Videos sehen werden, können Sie eine Netzwerkverbindung testen, ohne dass ein tatsächliches Netzwerk installiert ist.

Da alles bis zu diesem Punkt Teil von MQL5 ist, werden Sie wahrscheinlich mit den Ergebnissen zufrieden sein und den Server selbst implementieren können. Aber für diejenigen, die nicht wissen, wie man einen Server implementiert, wollen wir uns das in einem neuen Abschnitt ansehen, einfach aus Neugier.


Implementierung des Servers für den Mini-Chat

Die Implementierung eines Servers ist alles andere als eine komplexe Aufgabe. Im Grunde ist es ganz einfach. Die Implementierung eines Servers ist jedoch etwas anderes als seine Inbetriebnahme. Die Implementierung bezieht sich auf die Erstellung des Codes. Bei der Inbetriebnahme muss sichergestellt werden, dass nichts durchsickert, was nicht von vornherein hätte durchsickern sollen.

Wir werden hier nicht auf die betrieblichen Aspekte des Serverbetriebs eingehen. Wir werden uns ausschließlich mit der Umsetzung befassen. Ich möchte jedoch klarstellen, dass der Code, den Sie sehen werden, nicht für Produktionszwecke verwendet werden sollte. Der gezeigte Code ist als didaktische Maßnahme gedacht. Mit anderen Worten: Verwenden Sie es nicht, ohne genau zu wissen, was Sie tun. Verwenden Sie es ausschließlich, um zu lernen und zu verstehen, wie ein Server tatsächlich implementiert ist. Der vollständige Code ist unten abgebildet:

001. #define WIN32_LEAN_AND_MEAN
002. 
003. #include <winsock2.h>
004. #include <iostream>
005. #include <sstream>
006. #include <stdlib.h>
007. 
008. #pragma comment (lib, "Ws2_32.lib")
009. 
010. #define DEFAULT_BUFLEN   256
011. #define DEFAULT_PORT     27015
012. 
013. using namespace std;
014. 
015. int __cdecl main(void)
016. {
017.    WSADATA wsData;
018.    SOCKET listening, slave;
019.    sockaddr_in hint;
020.    fd_set master;
021.    bool Looping = true;
022.    int ConnectCount;
023.    string szMsg;
024. 
025.    if (WSAStartup(MAKEWORD(2, 2), &wsData) != EXIT_SUCCESS)
026.    {
027.       cout << "Can't Initialize WinSock! Quitting..." << endl;
028.       return EXIT_FAILURE;
029.    }
030.    if ((listening = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
031.    {
032.       cerr << "Can't create a socket! Quitting" << endl;
033.       WSACleanup();
034.       return EXIT_FAILURE;
035.    }
036.    else
037.       cout << "Creating a Socket..." << endl;
038. 
039.    hint.sin_family = AF_INET;
040.    hint.sin_port = htons(DEFAULT_PORT);
041.    hint.sin_addr.S_un.S_addr = INADDR_ANY;
042. 
043.    if (bind(listening, (sockaddr*)&hint, sizeof(hint)) == SOCKET_ERROR)
044.    {
045.       cout << "Bind failed with error:" << WSAGetLastError() << endl;
046.       WSACleanup();
047.       return EXIT_FAILURE;
048.    }
049.    else
050.       cout << "Bind success..." << endl;
051. 
052.    if (listen(listening, SOMAXCONN) == SOCKET_ERROR)
053.    {
054.       cout << "Listen failed with error:" << WSAGetLastError() << endl;
055.       WSACleanup();
056.       return EXIT_FAILURE;
057.    }
058.    else
059.       cout << "Listen success..." << endl;
060.    
061.    FD_ZERO(&master);
062.    FD_SET(listening, &master);
063.    cout << "Waiting for client connection" << endl;
064. 
065.    while (Looping)
066.    {
067.       fd_set tmp = master;
068. 
069.       ConnectCount = select(0, &tmp, nullptr, nullptr, nullptr);
070.       for (int c0 = 0; c0 < ConnectCount; c0++)
071.       {
072.          if ((slave = tmp.fd_array[c0]) == listening)
073.          {
074.             SOCKET NewClient = accept(listening, nullptr, nullptr);
075. 
076.             szMsg = "Sent by SERVER: Welcome to Mini Chart\r\n";
077.             FD_SET(NewClient, &master);
078.             send(NewClient, szMsg.c_str(), (int)szMsg.size() + 1, 0);
079.             cout << "Client #" << NewClient << " connecting..." << endl;
080.          }
081.          else
082.          {
083.             char buff[DEFAULT_BUFLEN];
084.             int bytesIn;
085. 
086.             ZeroMemory(buff, DEFAULT_BUFLEN);
087.             if ((bytesIn = recv(slave, buff, DEFAULT_BUFLEN, 0)) <= 0)
088.             {
089.                closesocket(slave);
090.                cout << "Client #" << slave << " disconnected..." << endl;
091.                FD_CLR(slave, &master);
092.             }
093.             else
094.             {
095.                if (buff[0] == '\\') //Check command ...
096.                {
097.                   szMsg = string(buff, bytesIn);
098. 
099.                   if (szMsg == "\\shutdown")
100.                   {
101.                      Looping = false;
102.                      break;
103.                   }
104.                   continue;
105.                }
106.                for (u_int c1 = 0; c1 < master.fd_count; c1++)
107.                {
108.                   SOCKET out = master.fd_array[c1];
109. 
110.                   if ((out != listening) && (out != slave))
111.                   {
112.                      ostringstream s1;
113. 
114.                      s1 << "Client #" << slave << ": " << buff << "\r\n";
115.                      send(out, s1.str().c_str(), (int)s1.str().size() + 1, 0);
116.                   }
117.                }
118.             }
119. 
120.          }
121.       }
122.    }
123. 
124.    FD_CLR(listening, &master);
125.    closesocket(listening);
126.    szMsg = "Server is shutting down. Goodbye\r\n";
127.    while (master.fd_count > 0)
128.    {
129.       slave = master.fd_array[0];
130.       send(slave, szMsg.c_str(), (int)szMsg.size() + 1, 0);
131.       FD_CLR(slave, &master);
132.       closesocket(slave);
133.    }
134. 
135.    WSACleanup();
136. 
137.    return EXIT_SUCCESS;
138. }

Der Quellcode des Dienstes

Ein Detail zu diesem Servercode: Obwohl er in C++ geschrieben ist, müssen Sie ihn mit den Visual Studio-Tools kompilieren. Wenn Sie jedoch verstehen, was getan wird, können Sie es so ändern, dass es mit GCC kompiliert werden kann. Aber halten Sie sich nicht zu sehr an diesen Code. Versuchen Sie zu verstehen, wie es funktioniert, denn darauf kommt es wirklich an.

Für diejenigen, die mit C++ und der Socket-Programmierung mit C/C++ nicht sehr vertraut sind, werde ich einige wichtige Punkte erklären. Die Zeilen 01 und 08 beziehen sich auf den Visual Studio Compiler. In Zeile 10 wird die Größe des Puffers festgelegt, der zum Lesen vom Socket verwendet wird. Sie können einen größeren oder kleineren Puffer verwenden, aber bedenken Sie, dass Sie, wenn der Puffer zu klein ist, mehrere Lesevorgänge durchführen müssen, um alle Informationen aus dem Socket zu erhalten. In jedem Fall ist für das, was wir hier tun (rein zu didaktischen Zwecken), ein Puffer von 256 Zeichen ausreichend.

Zeile 11 ist in der Tat wichtig für uns, da sie festlegt, welcher Port verwendet wird. Wir könnten dies auch anders über ein Befehlszeilenargument machen, aber hier geht es darum, die Dinge so einfach wie möglich zu halten, damit die Erklärung ohne Schwierigkeiten fortgesetzt werden kann. Zwischen den Zeilen 17 und 23 deklariere ich einige Variablen, die häufiger verwendet werden sollen. Obwohl ich den gesamten Code in den Körper der Hauptfunktion platziert habe, wäre es ideal, ihn in kleine Routinen aufzuteilen. Da dieser Server aber sehr einfach ist, können wir es uns leisten, alles im Rahmen der Prozedur main zu erledigen.

Beachten Sie nun etwas: Bei jedem Schritt, der unternommen wird, um den Socket zu konfigurieren und dem Server zu erlauben, an einem Port zu lauschen, sodass eine Verbindung von einem Client hergestellt werden kann, geben wir eine Meldung auf der Konsole aus. Wenn das Socket erstellt wird, wird dies in Zeile 37 bestätigt. Beachten Sie, dass hierfür zwei Schritte erforderlich sind. Zuerst wird WSAStartup aufgerufen, dann der Socket selbst. Systeme wie Linux benötigen den Aufruf von WSAStartup nicht, und auch einige Socket-Implementierungen unter Windows verwenden ihn nicht. In den meisten Fällen ist es jedoch eine gute Praxis, WSAStartup zu verwenden, wenn Sie unter Windows arbeiten.

In jedem Fall wird der Socket erstellt. Dann, zwischen den Zeilen 39 und 41, konfigurieren wir sie. Diese Konfiguration gibt an, welcher Port verwendet wird, welche Adresse überwacht wird und welcher Socket-Typ verwendet wird. Da wir experimentieren, erlauben wir jeder Adresse eine Verbindung. Es ist möglich, die Adresse einzuschränken. Aber bei Servern lassen wir normalerweise alle Verbindungen zu. Auch hier ist es wichtig zu wissen, wie dieser Teil zu konfigurieren ist, denn wenn Sie es falsch machen, können Sie am Ende das Tor zur Hölle öffnen. Danach müssen wir dem System mitteilen, wie der Socket konfiguriert werden soll. Dies geschieht in Zeile 43.

In Zeile 52 legen wir die maximale Anzahl der gleichzeitig geöffneten Verbindungen fest, die der Server akzeptieren soll. Dieser Schritt ist nicht kompliziert. Von diesem Zeitpunkt an ist der Server konfiguriert und bereit, Verbindungen anzunehmen. Dann bereiten wir einige Strukturen vor, um die entstehenden Verbindungen zu verfolgen. Dies geschieht in den Zeilen 61 und 62. Die Idee ist, eine Art dynamisches Array zu erstellen, das alle offenen Verbindungen speichert und es dem Server ermöglicht, wie in den Demovideos gezeigt zu arbeiten.

Sobald dies geschehen ist, beginnen wir mit der Schleife in Zeile 65. Diese Schleife kann vom Kunden abgebrochen werden, wie im Demovideo gezeigt. In dieser Schleife gibt es mehrere Dinge, die erklärt werden müssen. Das meiste davon besteht darin, zu lesen, was ein Client an den Socket sendet, und es an alle anderen Clients weiterzuleiten, was nicht besonders erwähnenswert ist.

Bei der Erläuterung des MQL5-Codes habe ich jedoch erwähnt, dass es wichtig ist, ein Wagenrücklauf- oder Zeilenumbruchzeichen einzufügen, und dass der Client diese Zeichen nicht hinzufügt – dies wird vom Server erledigt. Da wir nun den Servercode untersuchen, schauen wir uns Zeile 114 an, wo wir die notwendigen Zeichen anhängen, damit der MQL5-Client weiß, dass die Nachricht vollständig empfangen wurde. Wenn dies nicht hier auf dem Server geschieht, wird der in MQL5 implementierte Mini-Chat nicht richtig funktionieren.

Dies ist der Teil, den Sie sicherstellen müssen, wenn Sie sich für die Einrichtung eines eigenen Servers entscheiden. Sie müssen sicherstellen, dass der Server dieselben Zeichen hinzufügt. Die Reihenfolge spielt keine Rolle, aber sie müssen am Ende der Nachricht stehen.

Jetzt beginnt der lustige Teil. Ich habe bereits erwähnt, dass der Client den Server herunterfahren kann (dies ist in dem Demonstrationsvideo zu sehen). Wie ist das möglich? Einfach. Sehen Sie sich Zeile 95 an. Dort wird festgelegt, dass ein Zeichen am Anfang der Nachricht einen Befehl angibt, den der Server ausführen soll. Wenn dieser Test erfolgreich ist, wird der Befehl analysiert und die gesendete Nachricht wird nicht erneut an die anderen verbundenen Clients übertragen. In Zeile 99 wird dann einer dieser Befehle getestet. Es wird zwischen Groß- und Kleinschreibung unterschieden, was bedeutet, dass der Befehl genau so angegeben werden muss, wie er vom Server erwartet wird. Wenn dies geschieht, weisen wir den Server in Zeile 101 an, seinen Betrieb zu beenden und alle offenen Verbindungen zu schließen.

Wie Sie sehen können, ist alles sehr einfach. Bedenken Sie jedoch, dass es sich hierbei um einen rein didaktischen Code handelt, da jeder Client Befehle an den Server senden kann. Auf einem echten Server gäbe es Authentifizierungsstufen, um dies zu verhindern, sowie andere Sicherheitsvorkehrungen, die hier nicht notwendig sind.


Abschließende Überlegungen

In diesem Artikel schließen wir die Präsentation ab, wie Sie externe Programmierung zusammen mit MQL5 verwenden können, um einen Mini-Chat zu erstellen. Er dient rein pädagogischen Zwecken, kann aber abgeändert und verbessert werden und sogar unter Freunden als Grundlage für etwas Professionelleres dienen. Machen Sie sich dieses Wissen zunutze. In den Anhängen finden Sie den bereits kompilierten und einsatzbereiten Mini-Chat-MQL5-Code. Alles, was Sie tun müssen, ist, den Server zu erstellen. Ich überlasse es lieber Ihnen, dies selbst zu implementieren, da es das Öffnen von Ports auf Ihrem Computer erfordert. Das im Video zusammen mit dem Mini-Chat verwendete Programm ist PuTTY.

DateiBeschreibung
Experts\Expert Advisor.mq5
Demonstriert die Interaktion zwischen Chart Trade und dem Expert Advisor (für die Interaktion ist ein Mauszeiger erforderlich)
Indicators\Chart Trade.mq5Erzeugt ein Fenster zur Konfiguration des zu versendenden Auftrags (Mouse Study ist für die Interaktion erforderlich)
Indicators\Market Replay.mq5Erstellt Steuerelemente für die Interaktion mit dem Wiedergabe-/Simulationsdienst (Mausstudie ist für die Interaktion erforderlich)
Indicators\Mouse Study.mq5Ermöglicht die Interaktion zwischen den grafischen Steuerelementen und dem Nutzer (erforderlich für den Betrieb des Replay-Simulators und des Live-Markthandels)
Servicios\Market Replay.mq5Erstellt und pflegt den Marktwiedergabe- und Simulationsdienst (Hauptdatei des gesamten Systems)
VS C++ Server.cppErstellt und pflegt einen Socket-Server in C++ (Mini-Chat-Version)
Python-Code von Server.pyErstellt und pflegt einen Python-Socket für die Kommunikation zwischen MetaTrader 5 und Excel
ScriptsCheckSocket.mq5Überprüft die Verbindung zu einem externen Socket
Indicators\Mini Chat.mq5Implementiert einen Mini-Chat als Indikator (erfordert einen Server)
Experts\Mini Chat.mq5Implementiert einen Mini-Chat als Expert Advisor (erfordert einen Server)

Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/12673

Beigefügte Dateien |
Anexo.zip (560.03 KB)
Entwicklung eines Expert Advisors für mehrere Währungen (Teil 23): Ordnung in den Ablauf automatischer Projektoptimierungsstufe bringen (II) Entwicklung eines Expert Advisors für mehrere Währungen (Teil 23): Ordnung in den Ablauf automatischer Projektoptimierungsstufe bringen (II)
Unser Ziel ist es, ein System zur automatischen periodischen Optimierung von Handelsstrategien zu schaffen, die in einem endgültigen EA verwendet werden. Im Laufe der Entwicklung wird das System immer komplexer, sodass es von Zeit zu Zeit in seiner Gesamtheit betrachtet werden muss, um Engpässe und suboptimale Lösungen zu ermitteln.
Von der Grundstufe bis zur Mittelstufe: Ereignisse (I) Von der Grundstufe bis zur Mittelstufe: Ereignisse (I)
In Anbetracht dessen, was bisher gezeigt wurde, denke ich, dass wir jetzt damit beginnen können, eine Art Anwendung zu implementieren, um ein Symbol direkt auf dem Chart auszuführen. Zunächst müssen wir jedoch über ein Konzept sprechen, das für Anfänger ziemlich verwirrend sein kann. Es ist die Tatsache, dass Anwendungen, die in MQL5 entwickelt wurden und für die Anzeige in einem Chart bestimmt sind, nicht auf die gleiche Weise erstellt werden, wie wir es bisher gesehen haben. In diesem Artikel werden wir beginnen, dies ein wenig besser zu verstehen.
Blood inheritance optimization (BIO) Blood inheritance optimization (BIO)
Ich stelle Ihnen meinen neuen Algorithmus zur Populationsoptimierung vor – Blood Inheritance Optimization (BIO), inspiriert durch das menschliche Blutgruppenvererbungssystem. Bei diesem Algorithmus hat jede Lösung ihre eigene „Blutgruppe“, die bestimmt, wie sie sich weiterentwickelt. Wie in der Natur, wo die Blutgruppe eines Kindes nach bestimmten Regeln vererbt wird, erhalten neue Lösungen in BIO ihre Eigenschaften durch ein System von Vererbung und Mutationen.
Kreis-Such-Algorithmus (CSA) Kreis-Such-Algorithmus (CSA)
Der Artikel stellt einen neuen metaheuristischen Optimierungs-Kreis-Such-Algorithmus (CSA) vor, der auf den geometrischen Eigenschaften eines Kreises basiert. Der Algorithmus nutzt das Prinzip der Bewegung von Punkten entlang von Tangenten, um die optimale Lösung zu finden, und kombiniert die Phasen der globalen Erkundung und der lokalen Ausbeutung.