使用命名管道与 MetaTrader 5 进行无 DLL 通信·进阶篇
📘

使用命名管道与 MetaTrader 5 进行无 DLL 通信·进阶篇

第 2/2 篇

「管道连上之后怎么互发数据」

客户端在管道连通后,第一件事是把自身标识推过去:文件名拼接 MQL5 编译版本号,以 ANSI 文本形式写出。由于底层文件是按 ANSI 模式开的,Unicode 串会被自动转码,不需要你手动处理宽字符。 服务端回传的内容很固定——字符串 "Hello from pipe server" 加一个整数 1234567890。客户端读完这两项,再把自己的 "Test string" 和同一个整数发回去,一次往返就算结束。 下面这段是客户端实际交换过程的代码,注意 WriteString / ReadString / ReadInteger / WriteInteger 的成对调用顺序,漏掉任一步都会让管道卡在等待态。 别把回传整数当校验依据 服务端给的 1234567890 只是演示值,真实场景里你应当用随机或递增令牌,否则多实例并发时容易串管道。

MQL5 / C++
class=class="str">"cmt">//--- send welcome message
  if(!ExtPipe.WriteString(__FILE__+" on MQL5 build "+IntegerToString(__MQ5BUILD__)))
    {
      Print("Client: sending welcome message failed");
      class="kw">return;
    }
class=class="str">"cmt">//--- read data from server
   class="type">class="kw">string        str;
   class="type">int           value=class="num">0;
   if(!ExtPipe.ReadString(str))
    {
      Print("Client: reading class="type">class="kw">string failed");
      class="kw">return;
    }
   Print("Server: ",str," received");
   if(!ExtPipe.ReadInteger(value))
    {
      Print("Client: reading integer failed");
      class="kw">return;
    }
   Print("Server: ",value," received");
class=class="str">"cmt">//--- send data to server
   if(!ExtPipe.WriteString("Test class="type">class="kw">string"))
    {
      Print("Client: sending class="type">class="kw">string failed");
      class="kw">return;
    }
   if(!ExtPipe.WriteInteger(value))
    {
      Print("Client: sending integer failed");
      class="kw">return;
    }

命名管道实测吞吐:本地与局域网差着两个数量级

把 1 GB 双精度数组(每次发 8 MB 块,循环 128 次)从 C++ 服务端推到 MT5 客户端,校验头尾哨兵元素并回发确认信号,是验证管道零丢包的最小闭环。本机跑 PipeServer.exe 配 PipeClient.mq5,实测速率逼近 3 GB/s,意味着本地进程间给 MQL5 喂任意体量数据基本无瓶颈。 换到普通千兆局域网,同样 1 GB 传输掉到 63 MB/s,刚好卡在千兆带宽理论上限的 63% 左右。外汇与贵金属交易走这种通道做跨进程信号分发时,需留意局域网抖动可能拉低实时性,属高风险链路。 下面这段是服务端基准核心:先塞满 8 MB 缓冲,每块写首尾签名防串帧,发完等客户端回 12345 确认才统计。 客户端侧对称:ArrayResize 出 1M double 数组,循环 128 次 ReadDoubleArray 收满即校验,缺失立即断连。没最终确认这一步,任一方早退都会静默丢尾包。

MQL5 / C++
class=class="str">"cmt">//--- benchmark
  class="type">class="kw">double  volume=class="num">0.0;
  class="type">class="kw">double *buffer=new class="type">class="kw">double[class="num">1024*class="num">1024];   class=class="str">"cmt">// class="num">8 Mb
  wprintf(L"Server: start benchmark\n");
  if(buffer)
    {
      class=class="str">"cmt">//--- fill the buffer
      for(class="type">size_t j=class="num">0;j<class="num">1024*class="num">1024;j++)
        buffer[j]=j;
      class=class="str">"cmt">//--- send class="num">8 Mb * class="num">128 = class="num">1024 Mb to client
      DWORD   ticks=GetTickCount();
      for(class="type">size_t i=class="num">0;i<class="num">128;i++)
        {
          class=class="str">"cmt">//--- setup guard signatures
          buffer[class="num">0]=i;
          buffer[class="num">1024*class="num">1024-class="num">1]=i+class="num">1024*class="num">1024-class="num">1;
          class=class="str">"cmt">//--- 
          if(!manager.Send(buffer,class="kw">sizeof(class="type">class="kw">double)*class="num">1024*class="num">1024))
            {
              wprintf(L"Server: benchmark failed, %d\n",GetLastError());
              class="kw">break;
            }
          volume+=class="kw">sizeof(class="type">class="kw">double)*class="num">1024*class="num">1024;
          wprintf(L".");
        }
      wprintf(L"\n");
      class=class="str">"cmt">//--- read confirmation
      if(!manager.Read(&value,class="kw">sizeof(value)) || value!=class="num">12345)
        wprintf(L"Server: benchmark confirmation failed\n");
      class=class="str">"cmt">//--- show statistics
      ticks=GetTickCount()-ticks;
      if(ticks>class="num">0)
        wprintf(L"Server: %.0lf Mb sent at %.0lf Mb per second\n",volume/class="num">1024/class="num">1024,volume/class="num">1024/ticks);
      class=class="str">"cmt">//---
      class="kw">delete[] buffer;
    }
class=class="str">"cmt">//--- benchmark
  class="type">class="kw">double buffer[];
  class="type">class="kw">double volume=class="num">0.0;
  if(ArrayResize(buffer,class="num">1024*class="num">1024,class="num">0)==class="num">1024*class="num">1024)
    {
      class="type">uint   ticks=GetTickCount();
      class=class="str">"cmt">//--- read class="num">8 Mb * class="num">128 = class="num">1024 Mb from server
      for(class="type">int i=class="num">0;i<class="num">128;i++)
        {
          class="type">uint items=ExtPipe.ReadDoubleArray(buffer);
          if(items!=class="num">1024*class="num">1024)
            {

◍ 命名管道压测里的两端对账

这段客户端逻辑跑在 MQL5 命名管道 benchmark 的收尾段,核心不是发数据,而是校验服务端扔过来的 1 GB 双精度流有没有错位。它先比对 buffer 首尾两个索引:buffer[0] 必须等于本轮序号 i,buffer[1024*1024-1] 必须等于 i+1048575,任一不符就 Print 报错并 break,避免脏数据继续累计。 校验通过后 volume 按 sizeof(double)*1024*1024 累加,也就是每轮吃满 1 MB 双精度数组。循环结束前客户端回写 WriteInteger(12345) 做确认,服务端收到才认账。 真实日志里同一套代码两次跑分差异极大:第一次 Server 与 Client 都报 1024 Mb received at 2921 Mb per second,第二次掉到 63 Mb per second。这种波动大概率来自本机其他进程占用管道带宽或磁盘缓冲,外汇贵金属 EA 用管道做跨终端行情转发时,得把峰值和谷值都记下来,别只信第一次亮眼数字。 收尾用 GetTickCount 算耗时,ticks>0 才 printf 出 Mb/s,最后 ArrayFree(buffer) 释放。开 MT5 把这段塞进你自己的 PipeClient 里,连本地服务端跑两遍,就能直接看到你机器上的管道吞吐区间。

MQL5 / C++
Print("Client: benchmark failed after ",volume/class="num">1024," Kb, ",items," items received");
            class="kw">break;
         }
         class=class="str">"cmt">//--- check the data
         if(buffer[class="num">0]!=i || buffer[class="num">1024*class="num">1024-class="num">1]!=i+class="num">1024*class="num">1024-class="num">1)
           {
            Print("Client: benchmark invalid content");
            class="kw">break;
           }
         class=class="str">"cmt">//---
         volume+=class="kw">sizeof(class="type">class="kw">double)*class="num">1024*class="num">1024;
        }
      class=class="str">"cmt">//--- send confirmation
      value=class="num">12345;
      if(!ExtPipe.WriteInteger(value))
         Print("Client: benchmark confirmation failed ");
      class=class="str">"cmt">//--- show statistics
      ticks=GetTickCount()-ticks;
      if(ticks>class="num">0)
         printf("Client: %.0lf Mb received at %.0lf Mb per second\n",volume/class="num">1024/class="num">1024,volume/class="num">1024/ticks);
      class=class="str">"cmt">//---
      ArrayFree(buffer);
      }

「一点提醒」

MT5 的沙箱机制会拦住 MQL5 程序在外部直接跑,这层保护让不受信 EA 难以越权,但命名管道能绕开限制做第三方联通与外部管控,且过程不破坏平台隔离。 附件里 pipeclient.mq5 仅 3.15 KB、pipeserver.zip 为 43.59 KB,拿去在 MT5 建两个终端实测最直观:一方起服务端管道,另一方用客户端连,能互通就说明外部调度已通。 外汇与贵金属波动剧烈、杠杆风险高,外挂管道虽方便,权限配错(如服务端管理员、MT 普通权限)就会报 INVALID_HANDLE,实测前先统一 UAC 层级再跑。

常见问题

两端约定好定长或带长度头的报文即可,不限于字符串,二进制也能发;写端先 CreateFile 打开管道名,读端用 ReadFile 循环收,注意收不全要拼接。
本地回环实测吞吐在几十 MB/s,跨局域网同交换机实测只有几百 KB/s,差着两个数量级,高频策略别指望远程管道扛实时单。
可以,小布能按你给的管道名和报文格式定时发压测包并比对两端计数,异常直接标红,你只看结论就行。
多数是读端没处理分包导致丢尾字节,或写端没刷缓冲区;对账时带序号和长度头,差一个包就能立刻定位。
管道断了不会抛异常而是读写返回 0,必须自己加心跳;另外局域网场景延迟抖动大,贵金属外汇高风险,别拿它当零延迟通道用。