内容
- 概述
- 套接字
- Wireshark 流量分析器
- 数据交换
- 接收
- 发送
- MySQL 业务类
- 应用系统
- 文档
- 结束语
概述
大约一年前,MQL5 补充了网络函数,从而可以操控套接字(sockets) 了。 这为程序员开发市场所需产品提供了巨大的机遇。 如今,他们能够实现以前需要动态库支持的功能。 在本系列的两篇文章中,我们将研究这样的示例之一。 在第一篇文章中,我将研究 MySQL 连通器原理,而在第二篇文章中,我将利用连通器开发最简单的应用系统,即收集终端里所提供信号属性的服务,和查看它们 随时间流逝而变化的程序。(参见图例 1)。
图例 1. 观察信号属性随时间变化的程序
套接字
套接字是软件实现的进程间交换数据的接口。 这些过程既可以在单台 PC 上启动,也可在连接到网络里的不同 PC 上启动。
MQL5 仅提供客户端 TCP 套接字。 这意味着我们能够发起连接,但我们不能等待并响应外部的连接请求。 所以,如果我们需要通过套接字在 MQL5 程序之间提供连通,则需要一个充当中介的服务器。 服务器等待并侦听端口的连接,并响应客户端的请求执行某些功能。 若要连接到服务器,我们需要知道其 IP 地址和端口。
端口是一个介于 0 到 65535 之间的数字。 有三个端口范围:系统保留(0 - 1023),用户(1024 - 49151),和动态端口(49152 - 65535)。 一些端口已分配给某些功能占用。 分配是由 IANA 执行,该组织负责管理 IP 地址区段和顶级域名,并注册 MIME 数据类型。
默认情况下,端口 3306 已分配给 MySQL。 当访问服务器时,我们将与其连接。 请注意,此端口值可以更改。 因此,在开发 EA 时,应在输入中设置端口以及 IP 地址。
在操控套接字时,会用到以下步骤:
- 创建一个套接字(得到控柄或错误)
- 连接服务器
- 交换数据
- 关闭套接字
当操控多个连接时,请记住单个 MQL5 程序有最多同时打开 128 个套接字的限制。
Wireshark 流量分析器
流量分析器有助于调试应用套接字的程序代码。 缺了它,整个过程就像不用示波器进行电子设备维修一样。 分析器从选定的网络接口捕获数据,并以可读形式显示它们。 它跟踪数据包的大小,它们之间的时间间隔,重传和连接丢失是否存在,以及许多其他有用的数据。 它还解码许多协议。
出于这些目的,我个人选用 Wireshark。
图例 2. Wireshark 流量分析器
图例 2 流量分析器窗口展示,其内含有捕获的数据包:
- 显示过滤行。 "tcp.port==3306" 表示仅显示本地或远程 TCP 端口 3306 的数据包(默认的 MySQL 服务器端口)。
- 数据包。 在此,我们可以看到连接设置过程,服务器应答,授权请求和后续交换。
- 所选数据包内容显示为十六进制形式。 在这种情况下,我们可以看到 MySQL 服务器应答数据包的内容。
- 传输等级 (TCP)。 当操控套接字函数时,我们定位于此。
- 应用系统级别(MySQL)。 这就是我们在本文中要研究的内容。
显示过滤器不会限制数据包捕获。 从状态栏中可清晰看到,此刻正在处理内存中捕获到的 2623 个数据包中的 35 个。 为了降低 PC 的负担,我们应在选择网络接口时设置捕获过滤器,如图例 3 所示。 仅当所有其他数据包确实没有用时,才应这样做。
图例 3. 数据包捕获过滤器
为了令我们自己熟悉流量分析器,我们尝试与 “google.com” 服务器建立连接,并跟踪该过程。 为此,编写一个小脚本。
void OnStart() { //--- Get socket handle int socket=SocketCreate(); if(socket==INVALID_HANDLE) return; //--- Establish connection if(SocketConnect(socket,"google.com",80,2000)==false) { return; } Sleep(5000); //--- Close connection SocketClose(socket); }
为此,首先我们创建一个套接字,并利用 SocketCreate() 函数获取其控柄。 在这种情况下,按参考资料的说辞,您可能会得到两种情况的错误,尽管这几乎是不可能的:
- ERR_NETSOCKET_TOO_MANY_OPENED 错误信号表示已有多于 128 个以上的套接字被打开。
- 出现 ERR_FUNCTION_NOT_ALLOWED 错误则表示尝试从指标里调用套接字创建,而此功能是被禁止的。
得到控柄后,尝试建立连接。 在本示例中,我们连接到 “google.com” 服务器(不要忘记在终端设置中将其添加到允许的服务器地址中),即连接到端口 80,且超时为 2000 毫秒。 建立连接之后,等待 5 秒钟,然后将其关闭。 现在,我们在流量分析器窗口中看看它的样子。
图例 4. 建立和关闭连接
在图例 4 中,我们能看到脚本与 IP 地址为 “172.217.16.14” 的 “google.com” 服务器之间的数据交换。 由于过滤器行里含有 "tcp.port==80" 表达式,因此此处未显示 DNS 查询。
顶部的三个数据包代表建立连接,而底部的三个数据包代表将其关闭。 “Time(时间)” 列显示数据包之间的时间,我们可以看到 5 秒钟的停顿时间。 请注意,数据包的颜色为绿色,与图例 2 中的不同。 这是因为在之前的情况下,分析器在数据交换中检测到 MySQL 协议。 在当前情况下,没有数据经过,分析器使用默认的 TCP 颜色显示数据包。
数据交换
根据协议,MySQL 服务器应在建立连接后发送应答语。 作为响应,客户端发送授权请求。 在 dev.mysql.com 网站上,该机制于连接阶段部分进行了详细阐述。 如果未收到服务端的应答语,则 IP 地址无效,或服务器正在侦听其它端口。 无论何种情况,这意味着我们已连接的绝对不是 MySQL 服务器。 在正常状况下,我们需要接收数据(从套接字读取数据),并解析它。
接收
在 CMySQLTransaction类中(稍后将详细讲述),数据接收已如下实现:
//+------------------------------------------------------------------+ //| Data receipt | //+------------------------------------------------------------------+ bool CMySQLTransaction::ReceiveData(ushort error_code=0) { char buf[]; uint timeout_check=GetTickCount()+m_timeout; do { //--- Get the amount of data that can be read from the socket uint len=SocketIsReadable(m_socket); if(len) { //--- Read data from the socket to the buffer int rsp_len=SocketRead(m_socket,buf,len,m_timeout); m_rx_counter+= rsp_len; //--- Send the buffer for handling ENUM_TRANSACTION_STATE res = Incoming(buf,rsp_len); //--- Get the result the following actions will depend on if(res==MYSQL_TRANSACTION_COMPLETE) // server response fully accepted return true; // exit (successful) else if(res==MYSQL_TRANSACTION_ERROR) // error { if(m_packet.error.code) SetUserError(MYSQL_ERR_SERVER_ERROR); else SetUserError(MYSQL_ERR_INTERNAL_ERROR); return false; // exit (error) } //--- In case of another result, continue waiting for data in the loop } } while(GetTickCount()<timeout_check && !IsStopped()); //--- If waiting for the completion of the server response receipt took longer than m_timeout, //--- exit with the error SetUserError(error_code); return false; }此处的 m_socket 是早前创建的套接字控柄,而 m_timeout 是读取数据的超时,可作为 SocketRead() 函数接受 数据片的参数,以及整体形式接收数据的超时。 进入循环之前,设置时间戳。 若达到它所认定的数据接收超时:
uint timeout_check=GetTickCount()+m_timeout;
接下来,在循环中读取 SocketIsReadable() 函数结果,并一直等到其返回非零值。 之后,将数据读取到缓冲区,并传递给处理过程。
uint len=SocketIsReadable(m_socket); if(len) { //--- Read data from the socket to the buffer int rsp_len=SocketRead(m_socket,buf,len,m_timeout); m_rx_counter+= rsp_len; //--- Send the buffer for handling ENUM_TRANSACTION_STATE res = Incoming(buf,rsp_len); ... }
如果套接字中有数据,我们不要指望一次性收到整个数据包的能力。 在大多情况下,数据可能只有一小部分到达。 例如,4G 调制解调器的连接也许很差,并且会有大量重传。 故此,我们的处理程序应该能够将数据收集到一些不可分割的分组中,之后再处理它们。 我们为此使用 MySQL 数据包。
CMySQLTransaction::Incoming() 方法汇集并处理数据:
//--- Handle received data ENUM_TRANSACTION_STATE Incoming(uchar &data[], uint len);
它返回的结果令我们知道下一步该怎么做 — 继续,完成或中断接收数据的过程:
enum ENUM_TRANSACTION_STATE { MYSQL_TRANSACTION_ERROR=-1, // Error MYSQL_TRANSACTION_IN_PROGRESS=0, // In progress MYSQL_TRANSACTION_COMPLETE, // Fully completed MYSQL_TRANSACTION_SUBQUERY_COMPLETE // Partially completed };
如果发生内部错误,以及出现服务器错误,或完成数据接收时,应停止从套接字读取数据。 在所有其他情况下,则应继续。 MYSQL_TRANSACTION_SUBQUERY_COMPLETE 值表示服务器已接受并响应来自客户端多的个查询之一。 它等效于 MYSQL_TRANSACTION_IN_PROGRESS 读取算法。
图例 5. MySQL 数据包
MySQL数据包格式如图例 5 所示。 前三个字节定义数据包中有用负载的大小,而下一个字节表示序列中数据包的序列号,再后跟随数据。 每次交换开始时,序列号都设置为零。 例如,应答数据包为 0,客户端授权请求 — 1,服务器响应 — 2(连接阶段结束)。 接下来,在发送客户端查询时,序列号的值应再次设置为零,并在每个服务器响应数据包中递增。 如果数据包数量超过 255,则所传递数值归零。
在流量分析器中,最简单的数据包(MySQL ping)如下所示:
图例 6. 流量分析器中的 Ping 数据包
Ping 数据包只包含一个字节的数据,其值为 14(或十六进制形式的 0x0E)。
我们研究一下 CMySQLTransaction::Incoming() 方法,该方法将数据收集到数据包中,并将其传递给处理程序。 其节略版源代码提供如下。
ENUM_TRANSACTION_STATE CMySQLTransaction::Incoming(uchar &data[], uint len) { int ptr=0; // index of the current byte in the 'data' buffer ENUM_TRANSACTION_STATE result=MYSQL_TRANSACTION_IN_PROGRESS; // result of handling accepted data while(len>0) { if(m_packet.total_length==0) { //--- If the amount of data in the packet is unknown while(m_rcv_len<4 && len>0) { m_hdr[m_rcv_len] = data[ptr]; m_rcv_len++; ptr++; len--; } //--- Received the amount of data in the packet if(m_rcv_len==4) { //--- Reset error codes etc. m_packet.Reset(); m_packet.total_length = reader.TotalLength(m_hdr); m_packet.number = m_hdr[3]; //--- Length received, reset the counter of length bytes m_rcv_len = 0; //--- Highlight the buffer of a specified size if(ArrayResize(m_packet.data,m_packet.total_length)!=m_packet.total_length) return MYSQL_TRANSACTION_ERROR; // internal error } else // if the amount of data is still not accepted return MYSQL_TRANSACTION_IN_PROGRESS; } //--- Collect packet data while(len>0 && m_rcv_len<m_packet.total_length) { m_packet.data[m_rcv_len] = data[ptr]; m_rcv_len++; ptr++; len--; } //--- Make sure the package has been collected already if(m_rcv_len<m_packet.total_length) return MYSQL_TRANSACTION_IN_PROGRESS; //--- Handle received MySQL packet //... //--- m_rcv_len = 0; m_packet.total_length = 0; } return result; }
第一步是收集数据包头 — 前 4 个字节包含序列中的数据长度和序列号。 为了汇集包头,利用 m_hdr 缓冲区和 m_rcv_len 字节计数器。 再收集到 4 个字节后,获取它们的长度,并基于它更改 m_packet.data 缓冲区。 接收到的数据包数据被复制到其中。 数据包准备好后,将其传递给处理程序。
如果收到数据包后,len 表示的所收长度仍然非零,则表明我们已收到了若干个数据包。 我们可以在单一的 Incoming() 方法调用中处理若干个完整数据包,或几个部分。
数据包类型如下:
enum ENUM_PACKET_TYPE { MYSQL_PACKET_NONE=0, // None MYSQL_PACKET_DATA, // Data MYSQL_PACKET_EOF, // End of file MYSQL_PACKET_OK, // Ok MYSQL_PACKET_GREETING, // Greeting MYSQL_PACKET_ERROR // Error };
它们当中的每一个都有自己的处理程序,该处理程序根据协议解析其序列和内容。 解析期间收到的值将分配给相应的类成员。 在当前的连通器实现中,会解析数据包中接收到的所有数据。 这似乎有些多余,因为 “Table(表格)” 和 “Original table(原始表格)” 字段的属性经常重合。 此外,某些标志的值很少用到(参见图例 7)。 不过,这些属性的可用性能够在程序的应用层灵活地构建与 MySQL 服务器的交互逻辑。
图例 7. 数据包的字段说明
发送
发送数据于此要容易一些。
//+------------------------------------------------------------------+ //| Form and send ping | //+------------------------------------------------------------------+ bool CMySQLTransaction::ping(void) { if(reset_rbuf()==false) { SetUserError(MYSQL_ERR_INTERNAL_ERROR); return false; } //--- Prepare the output buffer m_tx_buf.Reset(); //--- Reserve a place for the packet header m_tx_buf.Add(0x00,4); //--- Place the command code m_tx_buf+=uchar(0x0E); //--- Form a header m_tx_buf.AddHeader(0); uint len = m_tx_buf.Size(); //--- Send a packet if(SocketSend(m_socket,m_tx_buf.Buf,len)!=len) return false; m_tx_counter+= len; return true; }
上面提供了 ping 发送方法的源代码。 将数据复制到准备好的缓冲区。 若执行 ping 操作,这就是 0x0E 命令的代码。 接下来,研究形成包头的数据量和数据包序列号。 对于 ping,序列号始终等于零。 之后,尝试利用 SocketSend() 函数发送封装好的数据包。
发送查询(Query)的方法类似于发送 ping:
//+------------------------------------------------------------------+ //| Form and send a query | //+------------------------------------------------------------------+ bool CMySQLTransaction::query(string s) { if(reset_rbuf()==false) { SetUserError(MYSQL_ERR_INTERNAL_ERROR); return false; } //--- Prepare the output buffer m_tx_buf.Reset(); //--- Reserve a place for the packet header m_tx_buf.Add(0x00,4); //--- Place the command code m_tx_buf+=uchar(0x03); //--- Add the query string m_tx_buf+=s; //--- Form a header m_tx_buf.AddHeader(0); uint len = m_tx_buf.Size(); //--- Send a packet if(SocketSend(m_socket,m_tx_buf.Buf,len)!=len) return false; m_tx_counter+= len; return true; }
唯一的区别在于,有用负载由 (0x03)命令码和查询字符串组成。
发送数据后总是跟随我们之前研究过的 CMySQLTransaction::ReceiveData() 接收方法。 如果没有返回错误,则认为业务成功。
MySQL 业务类
现在是时候详细研究 CMySQLTransaction 类了。
//+------------------------------------------------------------------+ //| MySQL transaction class | //+------------------------------------------------------------------+ class CMySQLTransaction { private: //--- Authorization data string m_host; // MySQL server IP address uint m_port; // TCP port string m_user; // User name string m_password; // Password //--- Timeouts uint m_timeout; // timeout of waiting for TCP data (ms) uint m_timeout_conn; // timeout of establishing a server connection //--- Keep Alive uint m_keep_alive_tout; // time(ms), after which the connection is closed; the value of 0 - Keep Alive is not used uint m_ping_period; // period of sending ping (in ms) in the Keep Alive mode bool m_ping_before_query; // send 'ping' before 'query' (this is reasonable in case of large ping sending periods) //--- Network int m_socket; // socket handle ulong m_rx_counter; // counter of bytes received ulong m_tx_counter; // counter of bytes passed //--- Timestamps ulong m_dT; // last query time uint m_last_resp_timestamp; // last response time uint m_last_ping_timestamp; // last ping time //--- Server response CMySQLPacket m_packet; // accepted packet uchar m_hdr[4]; // packet header uint m_rcv_len; // counter of packet header bytes //--- Transfer buffer CData m_tx_buf; //--- Authorization request class CMySQLLoginRequest m_auth; //--- Server response buffer and its size CMySQLResponse m_rbuf[]; uint m_responses; //--- Waiting and accepting data from the socket bool ReceiveData(ushort error_code); //--- Handle received data ENUM_TRANSACTION_STATE Incoming(uchar &data[], uint len); //--- Packet handlers for each type ENUM_TRANSACTION_STATE PacketOkHandler(CMySQLPacket *p); ENUM_TRANSACTION_STATE PacketGreetingHandler(CMySQLPacket *p); ENUM_TRANSACTION_STATE PacketDataHandler(CMySQLPacket *p); ENUM_TRANSACTION_STATE PacketEOFHandler(CMySQLPacket *p); ENUM_TRANSACTION_STATE PacketErrorHandler(CMySQLPacket *p); //--- Miscellaneous bool ping(void); // send ping bool query(string s); // send a query bool reset_rbuf(void); // initialize the server response buffer uint tick_diff(uint prev_ts); // get the timestamp difference //--- Parser class CMySQLPacketReader reader; public: CMySQLTransaction(); ~CMySQLTransaction(); //--- Set connection parameters bool Config(string host,uint port,string user,string password,uint keep_alive_tout); //--- Keep Alive mode void KeepAliveTimeout(uint tout); // set timeout void PingPeriod(uint period) {m_ping_period=period;} // set ping period in seconds void PingBeforeQuery(bool st) {m_ping_before_query=st;} // enable/disable ping before a query //--- Handle timer events (relevant when using Keep Alive) void OnTimer(void); //--- Get the pointer to the class for working with authorization CMySQLLoginRequest *Handshake(void) {return &m_auth;} //--- Send a request bool Query(string q); //--- Get the number of server responses uint Responses(void) {return m_responses;} //--- Get the pointer to the server response by index CMySQLResponse *Response(uint idx); CMySQLResponse *Response(void) {return Response(0);} //--- Get the server error structure MySQLServerError GetServerError(void) {return m_packet.error;} //--- Options ulong RequestDuration(void) {return m_dT;} // get the last transaction duration ulong RxBytesTotal(void) {return m_rx_counter;} // get the number of received bytes ulong TxBytesTotal(void) {return m_tx_counter;} // get the number of passed bytes void ResetBytesCounters(void) {m_rx_counter=0; m_tx_counter=0;} // reset the counters of received and passed bytes };
我们仔细看看以下私密成员:
- CMySQLPacket 类型的 m_packet — 当前正在处理的 MySQL 数据包的类(含有注释的源代码在 MySQLPacket.mqh 文件中)
- CData 类型的 m_tx_buf — 为方便生成一条查询而创建的传输缓冲区的类(Data.mqh 文件)
- CMySQLLoginRequest 类型的 m_auth — 处理授权的类(密码加扰,存储获得的服务器参数,和指定的客户端参数,源代码在 MySQLLoginRequest.mqh 中)
- CMySQLResponse 类型的 m_rbuf — 服务器响应缓冲区;这里的响应是 “Ok” 或 “Data” 类型的数据包(MySQLResponse.mqh)
- CMySQLPacketReader 类型的 reader — MySQL 数据包解析器类
公开方法已在文档中详细说明。
对于应用层,业务类看起来如图例 8 所示。
图例 8. CMySQLTransaction 类的结构
其中:
- CMySQLLoginRequest — 若客户端参数指定值与预定义值不同时(可选),应在建立连接之前进行配置;
- CMySQLResponse — 服务器响应(如果业务已完成,且没有错误)
- CMySQLField — 字段描述;
- CMySQLRow — 数据行(保存文本形式字段值的缓冲区);
- MySQLServerError — 在业务失败情况下,错误描述结构。
没有公开方法负责建立和关闭连接。 当调用 CMySQLTransaction::Query() 方法时,这会自动完成。 当使用持续连接模式时,它将在第一次调用 CMySQLTransaction::Query() 时建立,并在定义的超时后关闭。
重要提示:在持续连接模式下,OnTimer 事件处理程序应接收 CMySQLTransaction::OnTimer() 方法的调用。 在这种情况下,计时器周期应小于 ping 和超时周期。
在调用 CMySQLTransaction::Query() 之前,应先设置连接参数,用户帐户,以及特殊的客户端参数值。
通常,与业务类的交互是根据以下原理执行的:
图例 9. 操控 CMySQLTransaction 类
应用系统
我们来研究最简单的连接器应用示例。 为此,编写一个脚本,将 SELECT 查询发送到 world 测试数据库。
//--- input parameters input string inp_server = "127.0.0.1"; // MySQL server address input uint inp_port = 3306; // TCP port input string inp_login = "admin"; // Login input string inp_password = "12345"; // Password input string inp_db = "world"; // Database name //--- Connect MySQL transaction class #include <MySQL\MySQLTransaction.mqh> CMySQLTransaction mysqlt; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Configure MySQL transaction class mysqlt.Config(inp_server,inp_port,inp_login,inp_password); //--- Make a query string q = "select `Name`,`SurfaceArea` "+ "from `"+inp_db+"`.`country` "+ "where `Continent`='Oceania' "+ "order by `SurfaceArea` desc limit 10"; if(mysqlt.Query(q)==true) { if(mysqlt.Responses()!=1) return; CMySQLResponse *r = mysqlt.Response(); if(r==NULL) return; Print("Name: ","Surface Area"); uint rows = r.Rows(); for(uint i=0; i<rows; i++) { double area; if(r.Row(i).Double("SurfaceArea",area)==false) break; PrintFormat("%s: %.2f",r.Row(i)["Name"],area); } } else if(GetLastError()==(ERR_USER_ERROR_FIRST+MYSQL_ERR_SERVER_ERROR)) { // in case of a server error Print("MySQL Server Error: ",mysqlt.GetServerError().code," (",mysqlt.GetServerError().message,")"); } else { if(GetLastError()>=ERR_USER_ERROR_FIRST) Print("Transaction Error: ",EnumToString(ENUM_TRANSACTION_ERROR(GetLastError()-ERR_USER_ERROR_FIRST))); else Print("Error: ",GetLastError()); } }
假设我们的任务是获取比邻 “Oceania” 大陆值的国家/地区清单,并按国家/地区的面积从最大到最小的顺序排列,清单中最多包含 10 个数据项。 我们执行以下动作:
- 声明 mysqlt 业务类的实例
- 设置连通参数
- 完成相应的查询
- 如果业务成功,则确保响应数量等于期望值
- 获取指向服务器响应类的指针
- 获取响应中的数据行数量
- 显示数据行的值
业务可能由以下三个原因之一而失败:
- 服务器错误 - 利用 CMySQLTransaction::GetServerError() 方法获取其描述
- 内部错误 - 利用 EnumToString() 函数获取描述
- 否则,利用 GetLastError() 获取错误代码。
如果在输入里正确指定,则脚本操作的结果如下:
图例 10. 测试脚本操作结果
在第二部分中将探讨更复杂的应用多个查询和持续连接模式的示例。
文档
内容
- CMySQLTransaction 业务类
- Config
- KeepAliveTimeout
- PingPeriod
- PingBeforeQuery
- OnTimer
- Handshake
- Query
- Responses
- Response
- GetServerError
- RequestDuration
- RxBytesTotal
- TxBytesTotal
- ResetBytesCounters
- CMySQLLoginRequest 授权管理类
- CMySQLResponse 服务器响应类
- MySQLServerError 服务器错误结构
- CMySQLField 字段类
- CMySQLRow 数据行类
CMySQLTransaction 业务类
CMySQLTransaction 类方法列表
方法 |
动作 |
---|---|
Config |
设置连通参数 |
KeepAliveTimeout |
设置以秒为单位的“保持活动”模式的超时 |
PingPeriod |
设置以秒为单位的“保持活动”模式的 ping 周期 |
PingBeforeQuery |
启用/禁用查询前 ping |
OnTimer |
处理计时器事件(与“保持活动”有关) |
Handshake |
获取指向类的指针从而处理授权 |
Query |
发送查询 |
Responses |
获取服务器响应数量 |
Response |
获取指向服务器响应类的指针 |
GetServerError |
获取服务器错误结构 |
RequestDuration |
业务持续时间(以微秒为单位) |
RxBytesTotal |
自程序启动以来接收的字节计数 |
TxBytesTotal |
自程序启动以来发送的字节计数 |
ResetBytesCounters |
重置接收和发送字节的计数器 |
以下是每种方法的简要说明。
Config
设置连通参数。bool Config( string host, // server name uint port, // port string user, // user name string password, // password string base, // database name uint keep_alive_tout // constant connection timeout (0 - not used) );
返回值:如果成功,则返回 true;否则返回 false(字符串类型参数中的无效符号)。
KeepAliveTimeout
启用持续连接模式,并设置其超时。 超时值是以秒为单位的时间,从发送上一个查询起计算,该时间过后则连接被关闭。 如果重复查询超过了定义的超时值,则不会关闭连接。
void KeepAliveTimeout( uint tout // set the constant connection timeout in seconds (0 - disable) );
PingPeriod
设置在持续连接模式下发送 “ping” 数据包的周期。 这样可以防止服务器关闭连接。 在最后一次查询或上次 ping 后的指定时间过后发送 ping。
void PingPeriod( uint period // set the ping period in seconds (for the constant connection mode) );
返回值:无。
PingBeforeQuery
启用在查询之前发送 “ping” 数据包。 在持续连接模式下,查询之间的间隔中,由于某种原因,连接可能会由于某种原因而关闭或中断。 在这种情况下,可以在发送查询之前发送 ping 到 MySQL服务器,从而确保连接处于活动状态。
void PingBeforeQuery( bool st // enable (true)/disable (false) ping before a query );
返回值:无。
OnTimer
在持续连接模式下使用。 该方法应从 OnTimer 事件处理程序中调用。 计时器周期不应超过 KeepAliveTimeout 和 PingPeriod 周期的最小值。
void OnTimer(void);
返回值:无。
Handshake
获取指向授权类的指针。 建立与服务器的连接之前,可以用它来设置客户端职能和数据包最大尺寸的标志。 授权后,它接收服务器的职能版本和标志。
CMySQLLoginRequest *Handshake(void);
返回值:指向授权 CMySQLLoginRequest 类的指针。
Query
发送查询。
bool Query( string q // query body );
返回值:执行结果; 成功 - true,错误 - false。
Responses
获取响应数量。
uint Responses(void);
返回值:服务器响应的数量。
"Ok" 或 "Data" 类型的数据包被视为响应。 如果查询成功执行,则接受一个或多个响应(对于多个查询)。
Response
获取指向 MySQL 服务器响应类的指针
CMySQLResponse *Response( uint idx // server response index );
返回值:指向 CMySQLResponse 服务器响应类的指针。 若传递无效值作为参数,则返回 NULL。
未指定索引的重载方法等效于 Response()。
CMySQLResponse *Response(void);
返回值:指向 CMySQLResponse 服务器响应类的指针。 如果没有响应,则返回 NULL。
GetServerError
获取存储代码和服务器错误消息的结构。 若业务类返回 MYSQL_ERR_SERVER_ERROR 错误,则可调用它。
MySQLServerError GetServerError(void);
返回值:MySQLServerError 错误结构
RequestDuration
获取请求执行的持续时间。
ulong RequestDuration(void);
返回值:自发送时刻到处理结束的查询持续时间(以微秒为单位)
RxBytesTotal
获取接受的字节数。
ulong RxBytesTotal(void);
返回值:自程序启动以来接收的字节数(TCP 级别)。 ResetBytesCounters 方法用于重置。
TxBytesTotal
获取发送的字节数。
ulong TxBytesTotal(void);
返回值:自程序启动以来传递的字节数(TCP 级别)。 ResetBytesCounters 方法用于重置。
ResetBytesCounters
重置已接受和已发送字节的计数器。
void ResetBytesCounters(void);
CMySQLLoginRequest 授权管理类
CMySQLLoginRequest 类的方法
方法 |
动作 |
---|---|
SetClientCapabilities |
设置客户端职能标志。 预定义值:0x005FA685 |
SetMaxPacketSize |
设置最大允许的数据包大小(以字节为单位)。 预定义值: 16777215 |
SetCharset |
定义用到的字符集。 预定义值: 8 |
Version |
返回 MySQL 服务器版本。 例如: "5.7.21-log". |
ThreadId |
返回当前的连接线程 ID。 它对应于 CONNECTION_ID 值。 |
ServerCapabilities |
获取服务器职能的标志 |
ServerLanguage |
返回编码和数据库表述 ID |
CMySQLResponse 服务器响应类
“Ok” 或 “Data” 类型的数据包被视为服务器响应。 考虑到它们之间的显著差异,该类有一套单独的方法来处理每种类型的数据包。
General CMySQLResponse 类的方法:
方法 |
返回值 |
---|---|
类型 |
服务器响应类型: MYSQL_RESPONSE_DATA 或 MYSQL_RESPONSE_OK |
“Data” 类型数据包方法:
方法 |
返回值 |
---|---|
Fields |
字段数量 |
Field |
按索引指向 field 类的指针(重载方法 - 按名称获取字段索引) |
Field | 按名称的字段索引 |
Rows |
服务器响应中的数据行数量 |
Row |
按索引指向数据行类的指针 |
Value |
按数据行和字段索引的字符串值 |
Value | 按数据行和字段名称的字符串值 |
ColumnToArray | 将数据列读取到 string 型数组的结果 |
ColumnToArray |
将数据列读取到 int 型数组的结果,并经类型验证 |
ColumnToArray |
将数据列读取到 long 型数组的结果,并经类型验证 |
ColumnToArray |
将数据列读取到 double 型数组的结果,并经类型验证 |
方法 |
返回值 |
---|---|
AffectedRows |
由上次操作影响的数据行数量 |
LastId |
LAST_INSERT_ID 值 |
ServerStatus |
服务器状态 标志 |
Warnings |
警告次数 |
Message |
服务器文本消息 |
MySQLServerError 服务器错误结构
MySQLServerError 结构元素
元素 |
类型 |
目的 |
---|---|---|
code |
ushort | 错误代码 |
sqlstate |
uint | 状态 |
message | string | 服务器文本消息 |
CMySQLField 字段类
CMySQLField 类的方法
方法 |
返回值 |
---|---|
Catalog |
数据表所属目录的名称 |
Database |
数据表所属数据库的名称 |
Table |
字段所属数据表的别名 |
OriginalTable |
字段所属数据表的原名称 |
Name |
字段别名 |
OriginalName |
原字段名称 |
Charset |
实施的编码代号 |
Length |
数值长度 |
类型 |
数值类型 |
Flags |
定义值属性的标志 |
Decimals |
允许的小数位 |
MQLType |
字段类型为 ENUM_DATABASE_FIELD_TYPE 值的形式(DATABASE_FIELD_TYPE_NULL 除外) |
CMySQLRow 数据行类
CMySQLRow 类的方法
方法 |
动作 |
---|---|
Value |
按编号返回字段值的字符串形式 |
operator[] |
按名称返回字段值的字符串 |
MQLType |
按编号返回字段类型为 ENUM_DATABASE_FIELD_TYPE 值 |
MQLType |
按名称返回字段类型为 ENUM_DATABASE_FIELD_TYPE 值 |
Text |
按编号返回经类型验证的字段值的字符串 |
Text |
按名称返回经类型验证的字段值的字符串 |
Integer |
按字段编号返回经类型验证的 int 型数值 |
Integer |
按字段名称返回经类型验证的 int 型数值 |
Long |
按字段编号返回经类型验证的 long 型数值 |
Long |
按字段名称返回经类型验证的 long 型数值 |
Double |
按字段编号返回经类型验证的 double 型数值 |
Double |
按字段名称返回经类型验证的 double 型数值 |
Blob |
按字段编号返回经类型验证的 uchar 数组 形式的数值 |
Blob |
按字段名称返回经类型验证的 uchar 数组 形式的数值 |
请注意: 类型验证意味着处理 int 类型的方法的可读字段应等于 DATABASE_FIELD_TYPE_INTEGER。 如果不匹配,则不会收到任何值,且该方法将返回 “false”。 将 MySQL 字段类型 ID 转换为 ENUM_DATABASE_FIELD_TYPE 类型值是在 CMySQLField::MQLType() 方法中实现的,其源代码如下所供。
//+------------------------------------------------------------------+ //| Return the field type as the ENUM_DATABASE_FIELD_TYPE value | //+------------------------------------------------------------------+ ENUM_DATABASE_FIELD_TYPE CMySQLField::MQLType(void) { switch(m_type) { case 0x00: // decimal case 0x04: // float case 0x05: // double case 0xf6: // newdecimal return DATABASE_FIELD_TYPE_FLOAT; case 0x01: // tiny case 0x02: // short case 0x03: // long case 0x08: // longlong case 0x09: // int24 case 0x10: // bit case 0x07: // timestamp case 0x0c: // datetime return DATABASE_FIELD_TYPE_INTEGER; case 0x0f: // varchar case 0xfd: // varstring case 0xfe: // string return DATABASE_FIELD_TYPE_TEXT; case 0xfb: // blob return DATABASE_FIELD_TYPE_BLOB; default: return DATABASE_FIELD_TYPE_INVALID; } }
结束语
在本文中,我们以 MySQL 连通器的实现为例,探讨了利用函数操控套接字的实验。 这已成为一条理论。 本文的第二部分会更加实用:我们将开发一个收集信号属性的服务,和一个查看其变化的程序。
随附的存档包含以下文件:
- Include\MySQL\ 路径: 连通器源代码
- Scripts\test_mysql.mq5 文件: 在应用系统章节里用到的连通器示例。