Connexus客户端(第七部分):添加客户端层(基础篇)
📘

Connexus客户端(第七部分):添加客户端层(基础篇)

第 1/3 篇

给 Connexus 补上客户端层

在 MetaTrader 5 里做 Connexus 客户端,第七步是把「客户端层」接进现有架构。这一层不负责策略计算,只管把终端里的信号、账户状态往外推,以及把远端指令收回来。 没有客户端层时,EA 直接调传输层,耦合重、难调试;加上这层后,逻辑边界清晰,后续换通信协议也不用动交易核心。2025 年 7 月 4 日发布的示例里,作者 joaopedrodev 的客户端骨架已被 542 次查看,说明这类分层写法在实盘圈有真实需求。 落地时先在 Includes 下建 Client.mqh,用 CClient 类封装 Socket 收发;MT5 里右键「文件」→「打开数据文件夹」就能看到对应目录,改完重编译即可在 Experts 里引用。外汇与贵金属杠杆高,联调客户端层时务必用模拟账户跑通再上真仓。

「Connexus 库当前架构与发送目标」

这个系列在搭一个名为 Connexus 的 MQL5 网络请求库。第一篇已经把 WebRequest 函数的参数和调用难点跑通,上一篇理清了 HTTP 方法与服务器状态码的含义,本篇接着把「发请求」这件事封装进类里。 目前库的状态是一张架构图:核心目标是接收一个 CHttpRequest 对象——它已经配好 URL、方法、请求头、请求体和超时时间——然后由内部调用 WebRequest 真正发出去。 发完之后要返回一个 CHttpResponse 对象,里面必须带响应头、响应体、状态码,以及这次请求耗的总时长。第七篇才会接上最受期待的发送实现,现在先认清楚输入输出边界,免得后面调参时搞混谁该管超时、谁该管重试。

◍ 把请求对象真正发出去的 HttpClient 核心类

做 HTTP 客户端的最后一步,是写一个由调用方直接实例化的类,把封装好的请求对象转成实际网络调用,再塞回响应对象里。文件放在 Include/Connexus/Core/CHttpClient.mqh,它不继承任何东西,只负责把 CHttpRequest 和 CHttpResponse 对接到 MT5 的 WebRequest 传输层。 类的对外入口只有一个 Send() 方法:接收 CHttpRequest 与 CHttpResponse 两个引用,返回 bool。内部先用 uchar 数组 body_request 从请求体里取出二进制内容;再建两个变量接响应体和响应头;紧接着调用 WebRequest 的精简参数版本,把 URL、超时以及上面两个接收变量一并传进去。 为了能量化延迟,发送前后各调一次 GetMicrosecondCount(),用 (结束时间 - 开始时间)/1000 算出毫秒级耗时。无论成败,都先对响应对象 Clear() 复位,再写入耗时、状态码、响应体与分析用的响应头。 判定成功的标准很硬:返回码落在 200–299 区间才视为请求完成并返回 true,否则返回 false,详细排查只看 CHttpResponse 里的字段。架构上,所有底层类最终都收口进这个 HttpClient,调用方只跟它打交道。 下面这段是类的头文件骨架,已经带好了三个 include 和类注释,复制进对应路径就能往下补 Send() 实现。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  HttpClient.mqh |
class=class="str">"cmt">//|                                                 Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                                    [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include files                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "HttpRequest.mqh"
class="macro">#include "HttpResponse.mqh"
class="macro">#include "../Constants/HttpMethod.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class : CHttpClient                                                      |
class=class="str">"cmt">//|                                                                          |
class=class="str">"cmt">//| [PROPERTY]                                                               |
class=class="str">"cmt">//| Name      : CHttpClient                                                  |
class=class="str">"cmt">//| Heritage  : No heritage                                                  |
class=class="str">"cmt">//| Description : Class responsible for linking the request and              |
class=class="str">"cmt">//|               response object with the transport layer.                 

给 EA 套一层 HTTP 客户端壳

想在 MT5 里直接拉取外部行情或把信号推到自建服务,第一步是把网络请求封装成类。下面这段先给出 CHttpClient 的空构造与析构,以及后续要填的 Send 方法声明,相当于把通信层骨架立起来。 核心发送能力依赖 MQL5 内置的 WebRequest 函数,它要求显式传入 method、url、headers、timeout 与请求体字节数组,并返回响应体与响应头。注意该函数只能在 EA / 脚本中调用,指标里默认禁用以防卡顿。 Send 方法目前只写了前三步:把请求对象转成二进制体、准备响应容器、标记发送节点。真要跑通,还得在「3. Send」处调用 WebRequest 并做错误码判断,外汇与贵金属行情接口普遍波动大,超时设 1000~3000 毫秒可能更稳。

MQL5 / C++
class CHttpClient
  {
class="kw">public:
                     CHttpClient(class="type">void);
                    ~CHttpClient(class="type">void);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
CHttpClient::CHttpClient(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
CHttpClient::~CHttpClient(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class CHttpClient
  {
class="kw">public:
                     CHttpClient(class="type">void);
                    ~CHttpClient(class="type">void);

   class=class="str">"cmt">//--- Basis function
   class="type">bool              Send(CHttpRequest &request, CHttpResponse &response);
  };
class="type">int   WebRequest(
   class="kw">const class="type">class="kw">string      method,      class=class="str">"cmt">// HTTP method
   class="kw">const class="type">class="kw">string      url,         class=class="str">"cmt">// URL
   class="kw">const class="type">class="kw">string      headers,     class=class="str">"cmt">// headers 
   class="type">int               timeout,     class=class="str">"cmt">// timeout
   class="kw">const class="type">char       &data[],      class=class="str">"cmt">// the array of the HTTP message body
   class="type">char             &result[],    class=class="str">"cmt">// an array containing server response data
   class="type">class="kw">string           &result_headers  class=class="str">"cmt">// headers of server response
);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Basis function                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CHttpClient::Send(CHttpRequest &request, CHttpResponse &response)
  {
   class=class="str">"cmt">//--- class="num">1. Request
   class="type">uchar body_request[];
   request.Body().GetAsBinary(body_request);

   class=class="str">"cmt">//--- class="num">2. Response
   class="type">uchar body_response[];
   class="type">class="kw">string headers_response;

   class=class="str">"cmt">//--- class="num">3. Send

「用微秒计时抓 WebRequest 的真实耗时」

在 MT5 里发 HTTP 请求,最容易被忽略的是网络往返时间。上面这段把 GetMicrosecondCount 放在 WebRequest 前后,能直接拿到一次调用的微秒级开销,再除以 1000 转成毫秒写进 response.Duration,比靠日志猜快得多。 WebRequest 本身同步阻塞,参数依次是方法、完整 URL、序列化头、超时、请求体数组、响应体数组、响应头数组;返回值是 HTTP 状态码,靠 IsSuccess 判断成败。外汇与贵金属行情接口若走这条通道,阻塞期间 EA 不做其他事,高频轮询要评估卡顿风险。 响应回来后先 response.Clear 防止上次残留,再把耗时、状态码、二进制体和解析后的头填进去。开 MT5 把这段塞进你的请求封装函数,打印 Duration 字段,就能知道本地到券商 API 的延迟大致落在几毫秒到几十毫秒区间,方便后续设超时阈值。

MQL5 / C++
  class="type">class="kw">ulong start = GetMicrosecondCount();
  class="type">int status_code = WebRequest(
        request.Method().GetMethodDescription(),   class=class="str">"cmt">// HTTP method
        request.Url().FullUrl(),                   class=class="str">"cmt">// URL
        request.Header().Serialize(),              class=class="str">"cmt">// Headers
        request.Timeout(),                         class=class="str">"cmt">// Timeout
        body_request,                              class=class="str">"cmt">// The array of the HTTP message body
        body_response,                             class=class="str">"cmt">// An array containing server response data
        headers_response                           class=class="str">"cmt">// Headers of server response
  );
  class="type">class="kw">ulong end = GetMicrosecondCount();
  
  class=class="str">"cmt">//--- class="num">4. Add data in Response
  response.Clear();
  response.Duration((end-start)/class="num">1000);
  response.StatusCode() = status_code;
  response.Body().AddBinary(body_response);
  response.Header().Parse(headers_response);
  
  class=class="str">"cmt">//--- class="num">5. Return is success
  class="kw">return(response.StatusCode().IsSuccess());
}

◍ 用 httpbin 跑通第一笔请求

验证外部 API 连通性,最快的办法是拿 httpbin.org 当镜子:你发什么,它原样回什么。这个免费服务适合在 MT5 里确认请求是否真的发出、服务端收到了哪些字段,而不用自己搭后端。 在 Experts/Connexus/Test/ 下新建 TestRequest.mq5,只在 OnInit 里写逻辑即可。核心就三个对象:CHttpRequest 定 URL 和方法(GET 下超时默认 5000 毫秒,body 和 header 可空),CHttpResponse 接状态码、状态描述、耗时毫秒数和响应体,CHttpClient 负责真正把请求送出去。 调 Send() 后,终端会打印出状态码、耗时、响应头与响应体。对比裸写 WinAPI 的方式,套这层封装后开发者能用字符串、JSON 或二进制直接读响应体,也能改 URL/body/header 复用同一对象连发多个请求。外汇与贵金属行情接口调用涉及网络与券商限制,实盘前务必先在模拟环境验证,失败概率始终存在。 下面这段是最小可运行骨架,注意 #include 路径要跟你的库放置一致,否则编译期就报找不到文件。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                     TestRequest.mq5 |
class=class="str">"cmt">//|                                     Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   class=class="str">"cmt">//--- Initialize
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include files                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Connexus/Core/HttpClient.mqh>
class=class="str">"cmt">//--- Request object
CHttpRequest request;
request.Method() = HTTP_METHOD_GET;
request.Url().Parse("https:class=class="str">"cmt">//httpbin.org/get");
class=class="str">"cmt">//--- Response object
CHttpResponse response;
class=class="str">"cmt">//--- Client http object
CHttpClient client;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                     TestRequest.mq5 |
class=class="str">"cmt">//|                                     Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

客户端层负责把请求对象封装并真正发出去;不加这层 EA 只能构造数据但无法完成对外通信,必须补上 HttpClient 类才能跑通。
在发送前后取微秒级时间戳相减即可,例如用 GetMicrosecondCount 记录起止,差值就是真实网络往返耗时,便于定位慢请求。
可以,把 EA 日志或请求回执丢给小布,它能帮你比对 httpbin 返回结构、标出字段缺失或超时,省去手动翻日志。
多半是发送目标 URL 拼错或请求头缺 Content-Type;先用 httpbin 的 /get 或 /post 核对回显字段,再回头查客户端壳参数。
不用大改,把原先散落的发送逻辑收进 HttpClient 类,EA 主循环只调一个 Send 方法,后续换接口只动客户端层。