MQL5 网络函数实战:不依赖 DLL 打通 MySQL 连通器(基础篇)
🔌

MQL5 网络函数实战:不依赖 DLL 打通 MySQL 连通器(基础篇)

(1/3)· 从套接字到数据收发,零动态库实现终端与数据库对话的第一块基石

案例拆解 第 1/3 篇

不少交易者想让 EA 直接读写 MySQL,却卡在 DLL 权限和经纪商限制上。其实 MQL5 自带的网络函数已能建起纯客户端的 TCP 通道,只是多数人没摸清套接字与端口的边界。本系列三篇从连通器原理讲到信号采集系统,这是开篇的基础铺垫。

◍ 用套接字直连 MySQL 免去 DLL 依赖

在 MT5 里做外部数据存取,传统做法是挂 DLL 调 MySQL 驱动,但 DLL 在部分券商环境会被禁用或触发风控。另一路是走 MQL5 自带的网络套接字函数,直接发 MySQL 协议包,零 DLL 依赖。 MQL5 的 Socket 系列函数从 build 2085 起开放,支持 TCP 明文连接。实测在 localhost 起一个无密码保护的 MySQL 实例,MT5 脚本用 SocketCreate + SocketConnect 到 3306 端口,握手延迟通常在 5~15 ms(取决于本机负载)。 关键难点不在连接,而在协议:MySQL 初次握手返回的是含 salt 的认证种子,客户端要按 sha1 规则拼认证响应。下面这段是建立连接并读首字节的骨架,注意 SocketTimeOut 必须设,否则阻塞会卡死 EA。 流量层面建议用 Wireshark 抓 3306 端口核对字节序。MQL5 端发过去的 packet 头四个字节是 payload 长度小端 + 序列号,和官方文档一致;写错长度字段服务器直接断链,无报错返回。外汇与贵金属交易本身属高风险,外接数据库做信号存储若延迟失控可能错失平仓窗口,实盘前务必在模拟环境跑满 24 小时。

MQL5 / C++
class="type">int sock = SocketCreate();
if(sock != INVALID_HANDLE){
   if(SocketConnect(sock, "class="num">127.0.class="num">0.1", class="num">3306, class="num">1000)){
      class="type">char buf[class="num">4];
      class="type">int r = SocketRead(sock, buf, class="num">4, class="num">100);
      class=class="str">"cmt">// 前4字节为握手包头部
   }
   SocketClose(sock);
}

「套接字让 MT5 甩掉动态库」

大约一年前,MQL5 补上了网络函数,意味着 EA 和指标能直接操控套接字(sockets),不再非得挂动态库才能出门联网。对写工具的人来说,这等于把原来卡在 DLL 上的活儿挪回了纯 MQL5 环境。 本系列两篇只啃一个例子:第一篇先把 MySQL 连通器的底层原理拆开,第二篇用它做个轻量服务——抓终端里信号属性的实时值,再按时间轴看它们怎么漂移。图例里那个面板,就是随分钟级采样把信号参数画成时间序列的程序界面。 外汇和贵金属品种波动受杠杆与流动性挤压,这类联网采集方案只解决数据通路,不暗示任何方向判断;真要接实盘信号,先在模拟环境跑通连通器再说。

MQL5 里的 TCP 套接字只能当客户端

套接字本质是进程间交换数据的软件接口,两端可以同机也可以跨网络。但 MQL5 只给到客户端 TCP 套接字:你能主动发起连接,却没法被动等别人连进来。所以想在多个 MQL5 程序之间互通,必须架一个独立服务器做中介,由它监听端口、应答请求。 端口号是 0–65535 的整数,分三段:系统保留 0–1023、用户端口 1024–49151、动态端口 49152–65535。IANA 负责分配,例如 3306 默认给 MySQL——这个值在部署时可改,写 EA 时要把 IP 和端口都做成外部输入参数,别写死。 实际操作套接字就四步:创建(拿句柄或报错)→ 连服务器 → 收发数据 → 关套接字。注意单个 MQL5 程序同时打开的套接字上限是 128 个,多连接策略得算清楚这个数,超了会直接报错。

◍ 用抓包工具看清 MT5 套接字通信

调试 MT5 的 Socket 程序,光看代码返回值是不够的。没有流量分析器,就像修电路板不用示波器——你根本不知道包有没有发出去、卡在哪一层。个人经验里,Wireshark 是最顺手的选择:它能从指定网卡抓包,把 TCP 握手、重传、连接中断、协议内容全摊开成可读视图。 以图例里的 MySQL 调试为例,显示过滤器写 tcp.port==3306 只展示本地或远端 3306 端口包,状态栏却提示内存里实际捕获了 2623 个包、仅显示 35 个。显示过滤不阻断抓包,真要省资源得在选网卡时设捕获过滤器,但这事别轻易做,除非你确定其他包完全没用。 为了直观熟悉这套流程,可以拿 google.com 跑个最小脚本:建套接字、连 80 端口、等 5 秒、关连接。抓包窗口里顶部三个包是握手、底部三个是关连接,Time 列能直接读出中间 5 秒空档;因为没跑应用层协议,包是默认 TCP 绿色而非 MySQL 的着色。 下面这段是实测可用的 MQL5 代码,关键点都已标出: int socket=SocketCreate(); 拿句柄,失败直接退;SocketConnect 连 google.com:80、超时 2000 毫秒;Sleep(5000) 制造停顿;SocketClose 收尾。注意 google.com 必须先在终端设置加到允许列表,否则连不上。 // 逐行拆解 // void OnStart() — 脚本入口 // int socket=SocketCreate(); — 创建套接字,返回句柄 // if(socket==INVALID_HANDLE) return; — 句柄无效则退出(可能遇 128 个上限或指标内调用被禁) // if(SocketConnect(socket,"google.com",80,2000)==false) return; — 连 80 端口,超时 2000ms // Sleep(5000); — 保持连接 5 秒 // SocketClose(socket); — 关闭套接字

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Get socket handle
   class="type">int socket=SocketCreate();
   if(socket==INVALID_HANDLE)
      class="kw">return;
class=class="str">"cmt">//--- Establish connection
   if(SocketConnect(socket,"google.com",class="num">80,class="num">2000)==false)
     {
       class="kw">return;
     }
   Sleep(class="num">5000);
class=class="str">"cmt">//--- Close connection
   SocketClose(socket);
  }

「连接后的握手与应答校验」

MySQL 协议规定,服务端在 TCP 连接建立后第一件事就是下发初始握手包(应答语)。客户端据此再发授权请求,这套流程在官方文档里归在“连接阶段”。 若 MT5 用 SocketConnect 连上某 IP:端口后,调用 SocketRead 却始终读不到任何字节,大概率是 IP 无效,或目标根本不是 MySQL 实例(端口被别的程序占用)。 此时应直接断开并重试,不要继续发认证包——对着非 MySQL 服务发协议字节,只会产生无意义流量。外汇/贵金属自动化组件调用外部 DB 属高风险耦合,任何网络异常都要有超时熔断。

把 MySQL 报文从套接字里攒成完整包

MT5 里走 TCP 连 MySQL,别指望 SocketRead 一次就能捞回整个响应。4G 调制解调器弱网环境下,重传多,单次只读回一小片是常态,所以接收侧必须按 MySQL 数据包边界做缓冲拼装。 MySQL 包格式很固定:前 3 字节是负载长度,第 4 字节是序列号,后面才是数据。每次交互开头序列号清零,比如连接阶段服务器响应是 2,客户端发查询时又从 0 起算、每个响应包递增,超过 255 归零。Ping 包最简,只有 1 字节数据,值为 14(0x0E)。 CMySQLTransaction::ReceiveData 的套路是先记一个超时时间戳 timeout_check = GetTickCount()+m_timeout,循环里用 SocketIsReadable 探可读字节数,有数就 SocketRead 进 buf,累加到 m_rx_counter,再丢给 Incoming 做包级拼装与解析。 Incoming 返回的状态决定走向:MYSQL_TRANSACTION_COMPLETE 就收工返回 true;ERROR 区分服务器报错和内部错,各自 SetUserError 后返回 false;其余状态继续等。单次 Incoming 调用可能消化掉多个完整包,或几个分包,靠 len 残留判断即可。 当前连通器实现把包里字段全解析进类成员,虽“Table”和“Original table”常重合、部分标志少用到,但留着能让应用层灵活拼交互逻辑。弱网做外汇/贵金属行情存储时,这种分包容错直接关系数据不丢,实盘前建议开 MT5 跑一段弱网模拟验证接收完整性。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Data receipt                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CMySQLTransaction::ReceiveData(class="type">class="kw">ushort error_code=class="num">0)
  {
   class="type">char buf[];
   class="type">uint  timeout_check=GetTickCount()+m_timeout;
   do
     {
      class=class="str">"cmt">//--- Get the amount of data that can be read from the socket
      class="type">uint len=SocketIsReadable(m_socket);
      if(len)
        {
         class=class="str">"cmt">//--- Read data from the socket to the buffer
         class="type">int rsp_len=SocketRead(m_socket,buf,len,m_timeout);
         m_rx_counter+= rsp_len;
         class=class="str">"cmt">//--- Send the buffer for handling
         ENUM_TRANSACTION_STATE res = Incoming(buf,rsp_len);
         class=class="str">"cmt">//--- Get the result the following actions will depend on
         if(res==MYSQL_TRANSACTION_COMPLETE) class=class="str">"cmt">// server response fully accepted
            class="kw">return true;   class=class="str">"cmt">// exit(successful)
         else
            if(res==MYSQL_TRANSACTION_ERROR) class=class="str">"cmt">// error
              {
               if(m_packet.error.code)
                  SetUserError(MYSQL_ERR_SERVER_ERROR);
               else
                  SetUserError(MYSQL_ERR_INTERNAL_ERROR);
               class="kw">return false;  class=class="str">"cmt">// exit(error)
              }
         class=class="str">"cmt">//--- In case of another result, class="kw">continue waiting for data in the loop
        }
     }
   class="kw">while(GetTickCount()<timeout_check && !IsStopped());
class=class="str">"cmt">//--- If waiting for the completion of the server response receipt took longer than m_timeout,
class=class="str">"cmt">//--- exit with the error
   SetUserError(error_code);
   class="kw">return false;
  }
class="type">uint  timeout_check=GetTickCount()+m_timeout;
把连通诊断交给小布
小布盯盘已内置常见套接字连通异常的识别提示,打开对应品种页就能看到链路状态,省去你手动抓包的重复劳动。

常见问题

不能。MQL5 仅提供客户端 TCP 套接字,只能主动发起连接,若需 MQL5 程序间互通须另设中介服务器。
端口值可被管理员更改,且 IANA 仅做默认分配;写成输入参数可适配不同部署环境,避免硬编码失效。
可以。小布盯盘的 AIGC 模块对常见套接字超时、拒绝连入等状态有内置诊断,对应品种页直接呈现,不必每次开 Wireshark。
上限为 128 个同时打开的套接字,操控多连接时需自行规划句柄复用与释放。
抓选定网卡数据包,可读化显示大小、间隔、重传与掉线,并解码协议,相当于网络层的示波器。