English Русский Español Deutsch 日本語 Português
preview
市场模拟(第九部分):套接字(三)

市场模拟(第九部分):套接字(三)

MetaTrader 5示例 |
33 0
Daniel Jose
Daniel Jose

概述

在上一篇文章市场模拟(第八部分):套接字(二)中,我们开始开发一个利用套接字的实用应用程序。目的是演示该工具在面向 MetaTrader 5 的编程中的使用。确实,MQL5 不允许我们直接使用纯 MQL5 创建服务器。但是,由于套接字的使用独立于任何特定语言甚至操作系统,我们仍然可以通过在 MQL5 中实现编程在 MetaTrader 5 中使用它们。

但是,由于 MetaTrader 5 平台本身的内部原因,我们不能将指标与套接字一起使用。或者更准确地说:我们不能在指标代码中放置对套接字相关过程的调用。原因在于,如果这样做,我们可能会冻结或损害指标内部执行的计算的性能。

然而,没有什么能阻止我们继续将指标用于其他目的。而这正是我们在上一篇文章中所做的,我们在指标中创建了整个迷你聊天室窗口,包括控件和文本面板。创建并放置在指标中的详细信息不会以任何方式干扰指标的执行流程。但是,如果不使用指标,创建上一篇文章中的操作将非常复杂,因为我们最终会干扰正在绘制的资产图表的某些区域。

然而,通过这种方式,我们没有这样的问题,从而将我们的迷你聊天隔离在自己的窗口中。

也就是说,上一篇文章中生成的可执行文件无法满足我们对迷你聊天实际运行的需求。我们还需要完善一些细节。因此,在这里,我们将完成创建必要的支持,使迷你聊天室在 MetaTrader 5 中正常工作。即便如此,正如开头所提到的,MQL5 目前还无法满足我们的所有需求。因此,我们需要借助外部编程。然而,在这一点上,我们可以做点什么。我稍后会在本文中对此进行解释。现在,让我们完成将在 MQL5 中完成的部分。


实现连接类

在查看 EA 交易的最终源代码(这将实际实现迷你聊天功能)之前,让我们先来检查一下实现连接类的头文件。完整代码如下所示:

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. //+------------------------------------------------------------------+

头文件 C_Study.mqh

我希望你注意这段代码中的某些细节,因为理解它们将使你真正了解如何使迷你聊天正常工作。我们的类包含一些私有的全局变量,如第 07 行和第 10 行之间所示。请注意,所有这些变量都出现在 private 子句之后。

因此,它们无法在类外访问。我们使用这个类时,首先要做的事情就是调用它的构造函数。因此,要执行的第一行是第 13 行。现在请注意,在第 14、15 和 16 行,我们做了几件事。类全局变量在那里初始化。细节:我在这里以这种方式做这件事是出于习惯,但主要是为了避免忘记在构造函数的体内做这件事情。不管你信不信,这种类型的疏忽是很常见的。通过这种方式,我确保不会忘记正确初始化主要变量。

非常好,构造函数开始执行后,在第 18 行,我们调用标准库来尝试创建一个套接字。如果失败,我们将在 MetaTrader 5 中打印一条消息。如果成功,我们将在第 19 行尝试连接到参数指定的套接字。但与上一行不同的是,在这里,如果我们成功了,我们将立即返回。原因在于连接已经建立。如果发生故障,第 20 行将告知我们发生了什么以及故障的原因。

总之,如果既不能创建套接字也不能连接到套接字,则第 21 行将有效地允许我们通知主代码(在本例中是 EA 交易)连接过程失败。当我们查看 EA 交易的代码时,我会解释如何检测到这一点。只有在连接尝试过程中发生故障时,才会执行第 21 行。

析构函数位于第 24 行,其唯一目的是关闭套接字。因此,它的主体只有一行代码。这就是第 26 行,这里释放了我们正在使用的套接字(前提是它已被创建)。

现在让我们来看一下我们这个类的两个主要函数。第一个负责通过连接发送数据。没错,我们说的就是 write 函数,它位于第 29 行。这个函数需要像下一个函数一样谨慎地实现,因为无论你想发送什么:无论是图像、声音、视频还是文本,其余的代码都不会改变,就像这里要做的那样。真正发生变化的只有这两个函数:写和读。在这种情况下,我们采用最简单的方式做事。因此,在发送信息时,我们不会包括加密或任何其他类型的安全措施。原因很简单:我们只是在测试和学习套接字是如何工作的。

鉴于我们实际上只传输文本,首先要做的是确定将传输多少个字符。这是在第 31 行完成的。之所以要从字符数中减去 1,是因为接收到的字符串遵循 C/C++ 标准,即以 null 字符结尾。我们不会传输此字符。由于构造函数可能在创建过程中甚至在连接过程中失败,因此在尝试通过套接字传输任何内容之前,我们需要检查套接字是否有效。该测试在第 33 行执行。如果套接字因任何原因无效,则不会进行传输。

一旦测试完毕,我们必须再测试一件事。我们需要检查要传输的字符数。这是因为如果没有东西可以发送,那么尝试发送东西是没有意义的。该测试在第 34 行执行。如果所有这些测试都成功通过,在第 35 行,我们将尝试通过打开的套接字发送所需的数据。在这种情况下,我们通过正常连接发送,也就是说,我们没有使用任何加密或身份验证过程。但是,MQL5 确实允许安全传输。研究文档以更好地理解此主题。

如果我们成功发送数据,在第 36 行,我们将向调用者返回一个 true 值。如果任何测试失败,我们将在第 37 行在 MetaTrader 5 中打印一条消息,并在第 39 行向调用者返回 false 值。

现在让我们来看一下位于第 42 行的 read 函数。在这里,值得稍作停顿来解释一些事情。每个 write 函数都应该有一个对应的 read 函数。这并不是强制性的,但请考虑以下情况:您可以创建多个函数来发送不同类型的数据。在这种情况下,数据应该由相应的函数接收。

换句话说,你为什么要在一个旨在读取文本的函数中尝试读取代表视频的数据?这根本说不通,对吧?因此,无论是传输的数据类型还是使用的协议类型,都要始终尝试正确匹配。

协议的这一部分与 TCP 或 UDP 连接本身无关,而是与信息在传输之前的结构方式有关。始终将套接字中的数据视为您正在读取的文件。要正确理解其内容,您必须使用正确的阅读协议。

试图使用为读取 JPEG 图像而设计的协议来读取位图是没有意义的。尽管两者都是图像,但它们在文件中的结构方式完全不同。对于套接字来说,情况也是如此。给出警告后,让我们继续进行套接字读取例程。

与文章市场模拟(第七部分):套接字(一)中描述的情况不同,之前我们在服务器上产生了回应,而在这里我们并不一定会一直阻塞等待服务器响应。事实上,我们等待一小段时间它就会做出反应。关键在于,无论它的响应速度是快还是慢,我们都会在从套接字读取整个消息之前返回给调用者。

但怎么做呢?我们能否在完全读取套接字之前返回到我们的 EA 交易代码?是的,我们可以,但这样做时我们必须采取一些预防措施。这就是为什么我之前提到,你应该注意正确处理套接字上预期的信息类型。在这里,在我们的 read 函数中,默认情况下,我们将等待 500 毫秒后返回。如果这段时间已经过去,即使套接字中没有信息,我们仍将返回主代码。

然而,如果在此期间确实出现了一些信息,我们将在第 51 行阅读。现在,请密切关注以下事实,如果您打算实现自己的服务器,这一点很重要。在第 54 行,我们将套接字中存在的任何内容添加到返回字符串中。但是,只有在字符串中找到换行符或回车符时,此返回字符串才会向调用者返回某些内容。为了验证这一点,我们使用第 55 行的循环,它扫描接收到的内容以搜索上述字符。

当找到这些字符之一时,变量 m_NewLine 将获得 true 值。因此,在第 59 行,允许将从套接字读取的内容传递回调用者。同样,当发生新的调用时,第 46 行中的测试将允许清除字符串内容,从而开始新的循环。

现在请注意:在位于第 29 行的套接字写入函数代码中,我们没有添加读取函数所需的字符。OBJ_EDIT 对象也不会添加此类字符。同样,调用 ConnectionWrite 函数的代码也没有这样做。那么是谁添加了这些字符呢?是服务器做的。因此,如果您要实现自己的服务器,则必须添加这些字符。否则,迷你聊天室将无法区分不同的已发出消息。

这样做可能看起来有点愚蠢或毫无意义。但通过这样做,它使我们能够实现一些你可以在本文中包含的视频中看到的东西。非常好,现在我们已经看到了连接类的代码,让我们来看看 EA 交易的代码。


实现 EA 交易

下面完整展示了用于实现迷你聊天功能的完整 EA 交易代码:

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. //+------------------------------------------------------------------+

EA 交易源代码

请注意,这段代码非常简单。我们实际上不需要在这里编写太多程序。即便如此,我们仍需采取一些预防措施。请注意,在第 07 行和第 08 行,我们将上一篇文章中创建的指标添加到了 EA 交易代码中。这样一来,EA 交易代码编译完成后,指标可执行文件就可以删除,因为它将被嵌入到 EA 交易系统内部。在上一篇文章中,我解释了采取这一措施的原因。与前一篇文章中看到的相比,代码没有太多变化。

现在的细节是,其中包含一个调用,以便接收定时器事件。这是在第 27 行完成的。因此,大约每秒一次,负责处理定时器事件的代码将被执行,也就是说,我们将调用 OnTimer。在这个 EA 交易代码中,真正重要的是 OnTimer 调用和 OnChartEvent 调用。正是这两项功能使得迷你聊天室能够以目前的方式运行。我们先来了解一下 OnTimer 过程。

每当 MetaTrader 5 触发 OnTimer 事件时,我们的 EA 交易系统就会拦截该调用。然后,在第 45 行,我们尝试从第 13 行和第 14 行指定的套接字读取数据。没错 —— 我们将连接到网络。这一点在两篇文章前已经解释过了,当时我讨论了套接字。如果您对此有任何疑问,请阅读文章 “套接字(一)”

非常好,读取操作可能返回一个字符串,也可能不返回。如果从 read 函数收到返回值,则第 46 行的测试将成功。在这种情况下,我们会触发一个自定义事件,如第 47 行所示。此事件的目的是允许迷你聊天指标访问发布到套接字的信息。如果迷你聊天功能直接在 EA 交易中实现,那么这种自定义事件就完全没有必要了。但是既然我们已经将功能分开了,就必须将数据发送到指标,以便它可以处理在文本面板中显示信息。你会发现这很有趣:根据我们想要显示的内容,我们必须调整方案以最佳方式来实现解决方案。

关于 OnTimer 过程没有更多细节,就这么简单。同样,OnChartEvent 过程也非常简单。这次,我们拦截了来自迷你聊天指标的写入套接字的请求。为了检查我们是否有这样的调用,我们使用第 52 行的测试。如果确认我们正在处理自定义事件,则第 53 行会将迷你聊天指标提供的数据转发到写入函数。

在这个通信系统中,有一个细节对于那些不熟悉事件驱动编程的人来说可能不太明显。具体来说,如果任何其他程序(另一个指标、脚本,甚至是服务)触发了与 evChatReadSocket 值相同的自定义事件,则迷你聊天指标会将其解释为来自 EA 交易。如果触发的自定义事件的值与 evChatWriteSocket 的值相同,也会发生同样的情况。EA 交易会将其解读为来自迷你聊天指标的信息。

我不知道你是否注意到这所带来的可能性。尤其是如果您知道接收您通过套接字发送的数据的地址和端口,并且同样可以从给定的地址或端口读取数据。这种机制为制造一个小型“间谍”提供了可能,该“间谍”可以观察特定地点正在发生的事情。所以,我们必须始终对所有内容进行正确的加密和编码。但由于我们在这里只是将其用于演示目的,因此数据泄露的风险被降到了最低,几乎为零。正如您将在视频中看到的,即使没有安装实际的网络,您也可以测试网络连接。

由于到目前为止的一切都是 MQL5 的一部分,您可能会对结果感到满意,并能够自己实现服务器。但对于那些不知道如何实现服务器的人来说,让我们在一个新的部分来看看,纯粹是出于好奇。


实现迷你聊天服务器

搭建服务器远非一项复杂的任务,事实上,这很简单。然而,实现服务器和使其投入运行是两回事。实现指的是编写代码,投入使用是指确保它不会泄漏任何它原本不应该泄漏的东西。

本文不涉及服务器运行的具体操作方面,我们将只负责实现部分。但我要明确指出,您将看到的这段代码不应用于生产用途,所示代码仅用于教学目的。换句话说,在完全了解自己在做什么之前,不要使用它。仅将其作为学习和了解服务器实际实现方式的一种途径。完整代码如下所示:

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

服务源代码

关于此服务器代码的一个细节:虽然它是用 C++ 编写的,但您必须使用 Visual Studio 工具对其进行编译。但是,如果你理解了正在做的事情,你可以修改它,使其可以使用 GCC 进行编译。但不要过于执着于这段代码,试着理解它的运作原理,因为这才是真正重要的。

因此,我将为那些不太熟悉 C++ 和使用 C/C++ 进行套接字编程的人解释一些关键点。第 01 行和第 08 行与 Visual Studio 编译器有关。第 10 行定义了用于从套接字读取数据的缓冲区的大小。您可以使用更大或更小的缓冲区,但请记住,如果缓冲区太小,您将需要执行多次读取才能从套接字中获取完整的信息。无论如何,对于我们在这里做的事情(纯粹是出于教学目的),256 个字符的缓冲区就足够了。

第 11 行对我们来说确实很重要,因为它定义了将使用哪个端口。我们可以通过命令行参数以不同的方式做到这一点,但这里的想法是让事情尽可能简单,这样解释就可以毫无困难地进行。在第 17 行到第 23 行之间,我声明了一些将要更频繁使用的变量。虽然我把所有代码都放在了 main 函数体中,但理想的做法是将其拆分成小的例程。但由于这台服务器非常简单,我们可以在 main 过程中完成所有操作。

现在请注意:在配置套接字并允许服务器监听端口以便客户端建立连接的每个步骤中,我们都会向控制台打印一条消息。因此,当套接字创建时,我们在第 37 行看到了确认信息。请注意,这需要两个步骤。首先,我们调用 WSAStartup ,然后调用 socket 本身。Linux 等系统不需要 WSAStartup 调用,甚至 Windows 上的一些套接字实现也不使用它。但是,在大多数情况下,在 Windows 系统上使用 WSAStartup 是一个不错的做法。

无论如何,套接字都会被创建。然后,在第 39 行和第 41 行之间,我们对其进行配置。此配置指定将使用哪个端口、将监控哪个地址以及套接字类型。由于我们正在进行试验,因此允许任何地址连接。可以限制地址范围,但对于服务器,我们通常允许所有连接。再次强调,了解如何配置这一部分非常重要,因为如果配置错误,可能会打开地狱之门。完成此操作后,我们必须告知系统套接字的配置方式。这发生在第 43 行。

在第 52 行,我们定义了服务器将接受的最大同时打开连接数。这一步并不复杂。从此刻开始,服务器已配置完毕,可以接受连接。然后,我们准备一些结构来跟踪将发生的连接。这是在第 61 行和第 62 行完成的。其思路是创建一个动态数组,用于保存所有打开的连接,从而使服务器能够像演示视频中显示的那样运行。

完成此操作后,我们从第 65 行进入循环。如演示视频所示,客户端可以终止此循环。在这个循环中有几件事值得解释。其中大部分涉及读取一个客户端发布到套接字的内容并将其转发给所有其他客户端,这并不特别值得注意。

然而,在解释 MQL5 代码时,我提到过必须包含回车符或换行符,并且客户端不会添加此类字符 —— 这是由服务器完成的。现在我们正在检查服务器代码,请看第 114 行,我们在那里附加必要的字符,以便 MQL5 客户端知道消息已完全接收。如果服务器上没有这样做,MQL5 中实现的迷你聊天将无法正常运行。

如果您决定实现自己的服务器,这是您必须确保的部分。您必须保证服务器添加的是相同的字符。顺序无关紧要,但它们必须出现在消息的末尾。

现在,有趣的部分开始了。前面我提到客户端可以关闭服务器(这可以在演示视频中看到)。怎么会这样?很简单,请看第 95 行。在那里,我们定义消息开头的字符将指示服务器要执行的命令。如果此测试成功,则会分析命令,并且不会将发布的消息重新传输给其他连接的客户端。然后,在第 99 行,我们测试其中一个命令。它区分大小写,这意味着必须按照服务器的预期提供命令。如果发生这种情况,在第 101 行,我们指示服务器终止其操作并关闭所有打开的连接。

如你所见,一切都很简单。然而,请记住,这纯粹是教学代码,因为任何客户端都可以向服务器发送命令。在真实的服务器中,会有身份验证级别来防止这种情况,以及这里不必要的其他安全预防措施。


最后的探讨

在本文中,我们总结了如何使用外部编程和 MQL5 创建迷你聊天的演示。它的目的纯粹是教育性的,尽管它可以被修改、改进,甚至在朋友之间用作更专业的基础。好好利用这些知识。在附件中,您会发现迷你聊天 MQL5 代码已经编译并准备好可以使用。您需要做的就是创建服务器。我更喜欢把这个留给你自己实现,因为它涉及到在你的计算机上打开端口。视频中与迷你聊天一起使用的程序是 PuTTY

文件 描述
Experts\Expert Advisor.mq5
演示 Chart Trade 和 EA 交易之间的交互(交互需要 Mouse Study)
Indicators\Chart Trade.mq5 创建一个窗口,用于配置要发送的订单(需要 Mouse Study 才能进行交互)
Indicators\Market Replay.mq5 创建用于与回放/模拟器服务交互的控件(交互需要 Mouse Study)
Indicators\Mouse Study.mq5 实现图形控件和用户之间的交互(操作回放模拟器和实时市场交易所需)
Servicios\Market Replay.mq5 创建并维护市场回放和模拟服务(整个系统的主文件)
VS C++ Server.cpp 用 C++ 创建并维护一个套接字服务器(迷你聊天版)
Python code Server.py 创建并维护用于 MetaTrader 5 和 Excel 之间通信的 Python 套接字。
ScriptsCheckSocket.mq5 检查与外部套接字的连接
Indicators\Mini Chat.mq5 实现迷你聊天功能作为指标(需要服务器)
Experts\Mini Chat.mq5 实现迷你聊天功能作为 EA 交易(需要服务器)

本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12673

附加的文件 |
Anexo.zip (560.03 KB)
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
您应当知道的 MQL5 向导技术(第 58 部分):配以移动平均和随机振荡器形态的强化学习(DDPG) 您应当知道的 MQL5 向导技术(第 58 部分):配以移动平均和随机振荡器形态的强化学习(DDPG)
移动平均线和随机振荡器是十分常用的指标,我们在前一篇文章中探讨了它们的共通形态,并通过监督学习网络,见识了哪些“形态能粘附”。我们自该文加以分析,进一步研究当使用该已训练网络时,强化学习的效能。读者应当注意,我们的测试时间窗口非常有限。无论如何,我们在展示这一点时,会继续追求由 MQL5 向导提供最低编码需求。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
价格行为分析工具包开发(第 24 部分):价格行为量化分析工具 价格行为分析工具包开发(第 24 部分):价格行为量化分析工具
K线形态为潜在的市场走势提供了宝贵的线索。根据其在价格走势中所处的位置,有些单根K线预示着当前趋势的延续,而另一些则是反转的前兆。本文介绍了一款能够自动识别四种关键K线形态的EA。请参阅以下章节,了解该工具如何助您提升价格行为分析能力。