许多开发人员面临同样的问题 - 如何在不使用不安全 DLL 的情况下到达交易端沙箱。
一种最简单和最安全的方法是使用作为普通文件操作的标准命名管道。它们允许您组织程序之间的处理器间客户端-服务器通信。虽然《一个使用命名管道在 MetaTrader 5 客户端之间进行通信的无 DLL 解决方案》一文已经就此主题说明了对 DLL 访问的启用,我们将使用客户端的标准和安全的功能。
您可以在 MSDN 库中找到有关命名管道的更多信息,但我们将着手处理 C++ 和 MQL5 实例。我们将实施服务器、客户端、其间的数据交换以及性能基准。
我们以 C++ 编写一个简单的服务器。一个来自终端的脚本将连接到此服务器并与其交换数据。服务器核心具有下述 WinAPI 函数组:
打开一个命名管道后,它将返回一个文件句柄,用于常规的读/写文件操作。结果,您获得一个十分简单的机制,不需要任何网络操作的专门知识。
命名管道具有一个区别性特征 - 它们可以是本地的也可以是网络的。这就是说,实施远程服务器是很容易的,只要服务器与客户端通过网络连接。
下面是创建一个本地服务器的简单示例,该服务器作为全双工信道以字节交换模式工作:
//--- open CPipeManager manager; if(!manager.Create(L"\\\\.\\pipe\\MQL5.Pipe.Server")) return(-1); //+------------------------------------------------------------------+ //| Create named pipe | //+------------------------------------------------------------------+ bool CPipeManager::Create(LPCWSTR pipename) { //--- check parameters if(!pipename || *pipename==0) return(false); //--- close old Close(); //--- create named pipe m_handle=CreateNamedPipe(pipename,PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES,256*1024,256*1024,1000,NULL); if(m_handle==INVALID_HANDLE_VALUE) { wprintf(L"Creating pipe '%s' failed\n",pipename); return(false); } //--- ok wprintf(L"Pipe '%s' created\n",pipename); return(true); }
要获得客户端连接,您需要使用 ConnectNamedPipe 函数:
//+------------------------------------------------------------------+ //| Connect client | //+------------------------------------------------------------------+ bool CPipeManager::ConnectClient(void) { //--- pipe exists? if(m_handle==INVALID_HANDLE_VALUE) return(false); //--- connected? if(!m_connected) { //--- connect if(ConnectNamedPipe(m_handle,NULL)==0) { //--- client already connected before ConnectNamedPipe? if(GetLastError()!=ERROR_PIPE_CONNECTED) return(false); //--- ok } m_connected=true; } //--- return(true); }
数据交换使用 4 个简单函数实现:
它们允许您将数据作为二进制数据或 ANSI 文本字符串以 MQL5 兼容的模式发送/接收。此外,在默认情况下,由于 MQL5 中的 CFilePipe 以 ANSI 模式打开一个文件,字符串在接收和发送时自动转换为 Unicode。如果您的 MQL5 程序以 Unicode 模式 (FILE_UNICODE) 打开文件,则它可以交换 Unicode 字符串(具有 BOM 开始签名)。
我们将以 MQL5 编写我们的客户端。它将能够使用来自标准库的 CFilePipe 类执行常规文件操作。此类几乎与 CFileBin 相同,但它在读取数据前包含对虚拟文件中数据可用性的重要验证。
//+------------------------------------------------------------------+ //| Wait for incoming data | //+------------------------------------------------------------------+ bool CFilePipe::WaitForRead(const ulong size) { //--- check handle and stop flag while(m_handle!=INVALID_HANDLE && !IsStopped()) { //--- enough data? if(FileSize(m_handle)>=size) return(true); //--- wait a little Sleep(1); } //--- failure return(false); } //+------------------------------------------------------------------+ //| Read an array of variables of double type | //+------------------------------------------------------------------+ uint CFilePipe::ReadDoubleArray(double &array[],const int start_item,const int items_count) { //--- calculate size uint size=ArraySize(array); if(items_count!=WHOLE_ARRAY) size=items_count; //--- check for data if(WaitForRead(size*sizeof(double))) return FileReadArray(m_handle,array,start_item,items_count); //--- failure return(0); }
命名管道在实施本地模式和网络模式时具有显著差异。如果没有这样一个验证,网络模式操作在发送较大数据量时(超过 64K)将总是返回读取错误。
我们通过两个检查连接至服务器:连接至名为 "RemoteServerName" 的远程计算机或连接至本地机器。
void OnStart() { //--- wait for pipe server while(!IsStopped()) { if(ExtPipe.Open("\\\\RemoteServerName\\pipe\\MQL5.Pipe.Server",FILE_READ|FILE_WRITE|FILE_BIN)!=INVALID_HANDLE) break; if(ExtPipe.Open("\\\\.\\pipe\\MQL5.Pipe.Server",FILE_READ|FILE_WRITE|FILE_BIN)!=INVALID_HANDLE) break; Sleep(250); } Print("Client: pipe opened");
在成功连接后,我们发送一个文本字符串和标识信息至服务器。由于文件以 ANSI 模式打开,Unicode 字符串将被自动转换为 ANSI。
//--- send welcome message if(!ExtPipe.WriteString(__FILE__+" on MQL5 build "+IntegerToString(__MQ5BUILD__))) { Print("Client: sending welcome message failed"); return; }
作为答复,服务器将发送其字符串 "Hello from pipe server" 和整数 1234567890。客户端将再次发送字符串 "Test string" 和整数 1234567890。
//--- read data from server string str; int value=0; if(!ExtPipe.ReadString(str)) { Print("Client: reading string failed"); return; } Print("Server: ",str," received"); if(!ExtPipe.ReadInteger(value)) { Print("Client: reading integer failed"); return; } Print("Server: ",value," received"); //--- send data to server if(!ExtPipe.WriteString("Test string")) { Print("Client: sending string failed"); return; } if(!ExtPipe.WriteInteger(value)) { Print("Client: sending integer failed"); return; }
好了,我们完成了简单的数据交换。现在,是时候转到性能基准了。
作为测试,我们将从服务器发送 8 兆字节块的双精度类型数字数组的千兆字节数据至客户端,然后检查数据块的正确性并测量传输速率。
下面是 C++ 服务器中的代码:
//--- benchmark double volume=0.0; double *buffer=new double[1024*1024]; // 8 Mb wprintf(L"Server: start benchmark\n"); if(buffer) { //--- fill the buffer for(size_t j=0;j<1024*1024;j++) buffer[j]=j; //--- send 8 Mb * 128 = 1024 Mb to client DWORD ticks=GetTickCount(); for(size_t i=0;i<128;i++) { //--- setup guard signatures buffer[0]=i; buffer[1024*1024-1]=i+1024*1024-1; //--- if(!manager.Send(buffer,sizeof(double)*1024*1024)) { wprintf(L"Server: benchmark failed, %d\n",GetLastError()); break; } volume+=sizeof(double)*1024*1024; wprintf(L"."); } wprintf(L"\n"); //--- read confirmation if(!manager.Read(&value,sizeof(value)) || value!=12345) wprintf(L"Server: benchmark confirmation failed\n"); //--- show statistics ticks=GetTickCount()-ticks; if(ticks>0) wprintf(L"Server: %.0lf Mb sent at %.0lf Mb per second\n",volume/1024/1024,volume/1024/ticks); //--- delete[] buffer; }
以及 MQL5 客户端中的代码:
//--- benchmark double buffer[]; double volume=0.0; if(ArrayResize(buffer,1024*1024,0)==1024*1024) { uint ticks=GetTickCount(); //--- read 8 Mb * 128 = 1024 Mb from server for(int i=0;i<128;i++) { uint items=ExtPipe.ReadDoubleArray(buffer); if(items!=1024*1024) { Print("Client: benchmark failed after ",volume/1024," Kb, ",items," items received"); break; } //--- check the data if(buffer[0]!=i || buffer[1024*1024-1]!=i+1024*1024-1) { Print("Client: benchmark invalid content"); break; } //--- volume+=sizeof(double)*1024*1024; } //--- send confirmation value=12345; if(!ExtPipe.WriteInteger(value)) Print("Client: benchmark confirmation failed "); //--- show statistics ticks=GetTickCount()-ticks; if(ticks>0) printf("Client: %.0lf Mb received at %.0lf Mb per second\n",volume/1024/1024,volume/1024/ticks); //--- ArrayFree(buffer); }
注意,检查传输块的第一个和最后一个元素是为了确保传输期间未发生错误。同样,当传输完成,客户端发送数据成功接收的确认信号至服务器。如果您不使用最终确认,在一方过早地关闭连接的情况下您将很容易丢失数据。
本机运行 PipeServer.exe 服务器,并将 PipeClient.mq5 脚本附加至任意图表:
PipeServer.exe | PipeClient.mq5 |
---|---|
MQL5 Pipe Server Copyright 2012, MetaQuotes Software Corp. Pipe '\\.\pipe\MQL5.Pipe.Server' created Client: waiting for connection... Client: connected as 'PipeClient.mq5 on MQL5 build 705' Server: send string Server: send integer Server: read string Server: 'Test string' received Server: read integer Server: 1234567890 received Server: start benchmark ...................................................... ........ Server: 1024 Mb sent at 2921 Mb per second |
PipeClient (EURUSD,H1) Client: pipe opened PipeClient (EURUSD,H1) Server: Hello from pipe server received PipeClient (EURUSD,H1) Server: 1234567890 received PipeClient (EURUSD,H1) Client: 1024 Mb received at 2921 Mb per second |
对于本地交换,传输速率相当惊人 - 几乎 3 千兆字节每秒。这意味着命名管道可用于传输几乎任意的数据量至 MQL5 程序。
现在,我们在一个普通的千兆局域网中测试数据传输性能:
PipeServer.exe | PipeClient.mq5 |
---|---|
MQL5 Pipe Server Copyright 2012, MetaQuotes Software Corp. Pipe '\\.\pipe\MQL5.Pipe.Server' created Client: waiting for connection... Client: connected as 'PipeClient.mq5 on MQL5 build 705' Server: send string Server: send integer Server: read string Server: 'Test string' received Server: read integer Server: 1234567890 received Server: start benchmark ...................................................... ........ Server: 1024 Mb sent at 63 Mb per second |
PipeClient (EURUSD,H1) Client: pipe opened PipeClient (EURUSD,H1) Server: Hello from pipe server received PipeClient (EURUSD,H1) Server: 1234567890 received PipeClient (EURUSD,H1) Client: 1024 Mb received at 63 Mb per second |
在本地网络中,千兆字节数据以 63 兆字节每秒的速率传输,这相当不错。事实上,这是千兆网络最大带宽的 63%。
MetaTrader 5 交易平台的保护系统不允许 MQL5 程序在外部运行它们的沙箱,从而保护交易人员在使用不受信的“EA 交易”时免受威胁。使用命名管道,您可以轻松创建与第三方软件的整合和从外部管理 EA。且安全无虞。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程