ネットワーク関数の適用、または.dll を使用しない MySQL: パート I - コネクタ
Serhii Shevchuk | 8 5月, 2020
目次
イントロダクション
約1年前、MQL5ネットワーク関数はソケットを使用するための関数を補充しました。 これはMarket向けのプロダクトを開発するプログラマに大きな可能性を提示しました。 これにより、以前は動的ライブラリが必要なものを実装できるようになりました。 この2つの一連の記事では、このような例の1つを検討します。 最初の記事では、MySQLコネクタの原則を検討しますが、2番目の記事では、コネクタを使用して最も簡単なアプリケーション、すなわちターミナルで利用可能なシグナルのプロパティを収集するためのサービスと時間の経過とともに変化を見るためのプログラムを開発します(図1参照)。
図1. 時間の経過に伴うシグナル特性の変化を表示するためのプログラム
ソケット
ソケットは、プロセス間でデータを交換するためのソフトウェアインタフェースです。 このプロセスは、単一のPC上とネットワークに接続された異なるPC上の両方で起動することができます。
MQL5 はクライアントの TCP ソケットのみを提供します。 これはMT5側から接続はできますが、外部からの接続を待機することができないことを意味します。 したがって、ソケット経由でMQL5プログラム間の接続を提供する必要がある場合は、仲介役となるサーバーが必要です。 このサーバーは、リッスンされたポートへの接続を待機し、クライアントのリクエストで特定の関数を実行します。 サーバーに接続するには、IP アドレスとポートを知る必要があります。
ポートは 0 から 65535 までの範囲の番号です。 システム (0 - 1023)、ユーザー (1024-49151) および動的なポート範囲 (49152-65535) の 3 つのポート範囲があります。 一部のポートは、特定の関数を使用するために割り当てられます。 割り当ては、IP アドレス ゾーンとトップレベル ドメインを管理し、MIME データ型を登録する組織である IANA によって実行されます。
port 3306 はデフォルトで MySQL に割り当てられます。 サーバーにアクセスするときに接続します。 この値は変更可能です。 したがって、EAを開発する場合は、IP アドレスとともにインプットにポートを設定する必要があります。
ソケットを操作する場合は、次の方法を使用します。
- ソケットを作成する (ハンドルまたはエラーを取得する)
- サーバーに接続する
- データの交換
- ソケットを閉じる
複数の接続を扱う場合は、1つのMQL5プログラムで同時に開けるソケットは上限128であることに注意してください。
Wireshark トラフィック分析
トラフィック アナライザは、ソケットを適用するプログラムのコードのデバッグを容易にします。 これがなければ、オシロスコープなしで電子ツールを修理することに似ています。 このアナライザーは、選択したネットワーク インターフェイスからデータをキャプチャし、読み取り可能な形式で表示します。 パケットのサイズ、間の時間差、再送信と接続ドロップの存在、およびその他の多くの有用なデータを追跡します。 また、多くのプロトコルを復号化します。
個人的には、Wiresharkを使用します。
図2. Wireshark トラフィック分析
図2 は、キャプチャされたパケットを含むトラフィック アナライザ ウィンドウを示します。
- フィルタラインを表示します。 "tcp.port===3306" は、ローカルまたはリモートの TCP ポートが 3306 のパケットのみが表示されることを意味します (デフォルトの MySQL サーバー ポート)。
- パケット. ここでは、接続設定プロセス、サーバーグリーティング、認証リクエスト、その後の交換を見ることができます。
- 選択したパケットの内容を 16 進形式で指定します。 この場合、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()関数を使ってそのハンドルを取得します。 この場合、ほとんど不可能な 2 つのケースでエラーが発生する可能性があると記載されています。
- ERR_NETSOCKET_TOO_MANY_OPENEDエラーは、128個を超えるソケットが開いていることを示します。
- この関数が無効になっているインジケータからソケット作成を呼び出そうとすると、ERR_FUNCTION_NOT_ALLOWEDエラーが表示されます。
ハンドルを受信したら、接続を確立します。 この例では、"google.com" サーバーに接続します (ターミナル設定で許可されたアドレスに追加することを忘れないでください)。つまり、タイムアウトが2 000 ミリ秒のポート80にします。 接続を確立したら、5 秒間待ってから閉じます。 では、トラフィック アナライザー ウィンドウでの表示方法を見てみましょう。
図4. 接続の確立と終了
図4 では、スクリプトと "google.com" サーバーとの間で、"172.217.16.14" ip アドレスを使用してデータが交換されていることがわかります。 フィルタラインには "tcp.port==80" という式が含まれているため、DNS クエリはここには表示されません。
上位 3 つのパケットは接続の確立を表し、下位 3 つのパケットは、その終了を表します。 [時間] 列にはパケット間の時間が表示され、5 秒間のダウンタイムが表示されます。 パケットは図2のものとは異なり、緑色で表示されます。 これは前のケースでは、アナライザーが交換で MySQL プロトコルを検出したためです。 現在のケースでは、データは渡されず、アナライザーはデフォルトの TCP カラーでパケットを強調表示しました。
データ交換
プロトコルによれば、MySQL サーバーは接続を確立した後に送信する必要があります。 それに応答して、クライアントは認証リクエストを送信します。 このメカニズムについては、dev.mysql.com Web サイトの「接続フェーズ」セクションで詳しく説明します。 グリーティングが受信されない場合、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()関数を読み取ると、ループが発生し、0 以外の値が返されるまで待ちます。 その後、データをバッファに読み取り、処理用に渡します。
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値は、クライアントの複数のクエリに対するサーバーの応答の 1 つが受け入れられたことを示します。 読み取りアルゴリズムのMYSQL_TRANSACTION_IN_PROGRESSと同等です。
図5. MySQL パケット
MySQL パケット形式は図5 に表示されます。 最初の 3 バイトはパケット内の有用な負荷のサイズを定義し、次のバイトはシーケンス内のパケットのシリアル番号を表し、その後にデータが続きます。 各交換の開始時にシリアル番号はゼロに設定されます。 たとえば、グリーティング パケットは 0、クライアント認証リクエストは 1、サーバー応答 - 2 (接続フェーズの終了) です。 次に、クライアント クエリを送信する場合、シーケンス番号の値を再度 0 に設定し、各サーバー応答パケットで増加します。 パケット数が 255 を超えると、値は 0 を渡します。
最もシンプルなパケット (MySQL ping) は、トラフィック アナライザで次のようになります。
図6. トラフィック アナライザでの ping パケット
Ping パケットには、14 (または 16 進数形式の 0x0E) の値を持つ 1 バイトのデータがあります。
パケットにデータを収集し、ハンドラに渡す 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" フィールドと "元のテーブル" フィールドのプロパティが一致することが多いため、多少冗長に見えるかもしれません。 また、フラグの値はめったに必要ありません(図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 の場合、シリアル番号は常に0になります。 その後、SocketSend()関数を使用して、アセンブルされたパケットを送信します。
クエリ (クエリ) の送信方法は、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 forEAch 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 };
次のプライベート メンバについて詳しく見てみましょう。
- m_packet の CMySQL パケットタイプ — 現在処理されている MySQL パケットのクラス (MySQLPacket.mqh ファイル内のコメントを含むソースコード)
- m_tx_buf の CData 型 — クエリを生成するために作成された転送バッファのクラス (Data.mqh ファイル)
- m_auth の CMySQLLoginRequest タイプ — 許可を処理するためのクラス (パスワードスクランブリング、取得したサーバーパラメータおよび指定されたクライアントパラメータの保存、ソースコードは MySQLLoginRequest.mqh)
- m_rbuf の CMySQL 応答タイプ — サーバー・リプソン・バッファ。ここでの応答は"OK"または"データ"タイプのパケットです(MySQLResponse.mqh)
- reader のタイプ - MySQL パケット パーサー クラス
パブリック メソッドについては、ドキュメントで詳しく説明します。
アプリケーション層の場合、トランザクション クラスは図8 のように表示されます。
図8. クラス構造
ただし:
- CMySQLLoginRequest — 事前定義された値とは異なる値を持つクライアントパラメータを指定する場合は、接続を確立する前に設定する必要があります(オプション)。
- CMySQLResponse — トランザクションがエラーなしで完了した場合のサーバー応答
- CMySQLField — フィールドの説明;
- CMySQLRow — 行(テキスト形式のフィールド値のバッファ)
- MySQLServerError — トランザクションが失敗した場合のエラー記述構造。
接続の確立と終了を行うパブリック メソッドはありません。 CMySQL トランザクション::クエリ() メソッドを呼び出すときに自動的に行われます。 定数接続モードを使用する場合、CMySQLTransaction::Query() の最初の呼び出し時に確立され、定義されたタイムアウト後にクローズされます。
重要:定数接続モードでは、OnTimerイベント ハンドラは CMySQL トランザクション::OnTimer() メソッドの呼び出しを受け取る必要があります。 この場合、タイマー期間は ping およびタイムアウト期間よりも短くする必要があります。
接続のパラメータ、ユーザー アカウント、および特殊なクライアント パラメータ値は、CMySQLTransaction::Query() を呼び出す前に設定する必要があります。
一般に、トランザクションクラスとのデータのやり取りは、以下の原則に従って実行されます。
図9. クラスの操作
アプリケーション
コネクタを適用する最も簡単な例を考えてみましょう。 これを行うには、SELECT クエリをワールド テスト データベースに送信するスクリプトを記述します。
//--- 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()); } }
今回のタスクは、"オセアニア"の国のリストを、リスト内の最大10項目で最大から最小の面積で並べ替えることでしました。 次の操作を実行してみましょう。
- mysqlt トランザクション・クラスのインスタンスを宣言する
- 接続パラメータの設定
- 適切なクエリを作成する
- トランザクションが正常に終了した場合は、応答数が期待値と等しいことを確認します。
- サーバー応答クラスへのポインタを取得します。
- 応答の行数を取得します。
- 行の値を表示する
次の 3 つの理由のいずれかが原因で、トランザクションが失敗する可能性があります。
- サーバー エラー - CMySQLTransaction::GetServerError()を使用して詳細を取得します。
- 内部エラー - EnumToString()を使用して詳細を取得します。
- それ以外の場合は、GetLastError() を使用してエラー コードを取得します。
インプットが正しく指定されている場合、スクリプト操作の結果は次のようになります。
図10. テスト スクリプト操作の結果
複数のクエリを適用するより複雑な例と定数接続モードについては、第 2 部で説明します。
ドキュメンテーション
目次
- CMySQLLoginRequest認証管理クラス
- CMySQLResponseサーバー応答クラス
- MySQLServerErrorサーバー エラー構造
- CMySQLFieldフィールド クラス
- CMySQLRow行クラス
トランザクション クラス
クラス メソッドの一覧
メソッド |
アクション |
---|---|
Config |
接続パラメータの設定 |
KeepAliveTimeout |
キープアライブモードのタイムアウトを秒単位で設定する |
PingPeriod |
キープアライブモードの ping 期間を秒単位で設定する |
PingBeforeQuery |
クエリの前に ping を有効/無効にする |
OnTimer |
タイマー イベントの処理 (Keep Alive を使用する場合に関連) |
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' パケットを送信できるようにします。 クエリ間の時間間隔で、定数接続モードで何らかの理由で接続が閉じられるか、終了することがあります。 この場合、クエリを送信する前に接続がアクティブであることを確認するために、MySQL サーバーに ping を送信できます。
void PingBeforeQuery( bool st // enable (true)/disable (false) ping before a query );
戻り値: なし。
OnTimer
定数接続モードで使用します。 このメソッドは、OnTimerイベント ハンドラから呼び出す必要があります。 タイマー期間は、キープアライブタイムアウト期間と Ping 期間の最小値を超えないようにする必要があります。
void OnTimer(void);
戻り値: なし。
Handshake
認証を処理するためのクラスへのポインタを取得します。 サーバーへの接続を確立する前に、クライアント関数のフラグと最大パケット サイズを設定するために使用できます。 認証後、サーバー関数のバージョンとフラグを受け取ることができます。
CMySQLLoginRequest *Handshake(void);
戻り値: 認証を処理するための CMySQLLoginRequest クラスへのポインタ。
Query
クエリを送信します。
bool Query( string q // query body );
戻り値: 実行結果。成功 - true、エラー - false。
Responses
応答の数を取得します。
uint Responses(void);
戻り値: サーバー応答の数。
"OK" または "データ" タイプのパケットは応答と見なされます。 クエリが正常に実行されると、1 つ以上の応答 (複数のクエリの場合) が受け入れられます。
Response
MySQL サーバー応答クラスへのポインタを取得します。
CMySQLResponse *Response( uint idx // server response index );
戻り値: CMySQL 応答サーバー応答クラスへのポインタ。 引数として無効な値を渡すと NULL が返されます。
インデックスを指定せずにオーバーロードされたメソッドは、Response(0) と同じです。
CMySQLResponse *Response(void);
戻り値: CMySQL 応答サーバー応答クラスへのポインタ。 応答がない場合は、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認証管理クラス
クラスメソッド
メソッド |
アクション |
---|---|
SetClientCapabilities |
クライアント関数フラグ を設定します。 定義済み値: 0x005FA685 |
SetMaxPacketSize |
許容される最大パケット サイズをバイト単位で設定します。 定義済み値: 16777215 |
SetCharset |
使用済みシンボルのセットを定義します。 定義済みの値: 8 |
Version |
MySQL サーバーのバージョンを返します。 たとえば、"5.7.21-log" とします。 |
ThreadId |
現在の接続スレッド ID を返します。 CONNECTION_ID 値に対応します。 |
ServerCapabilities |
サーバー関数のフラグを取得します。 |
ServerLanguage |
エンコーディングとデータベース表現 ID を返します。 |
CMySQLResponseサーバー応答クラス
"Ok" または "Data" タイプのパケットは、サーバー応答と見なされます。 このクラスは、大きく異なっているため、各タイプのパケットを処理するためのメソッドのセットを個別に持っています。
一般的な CMySQL 応答クラスメソッド:
メソッド |
戻り値 |
---|---|
Type |
サーバー応答の種類: MYSQL_RESPONSE_DATAまたはMYSQL_RESPONSE_OK |
データ型パケットのメソッド:
メソッド |
戻り値 |
---|---|
Fields |
フィールド数 |
Field |
インデックスによるフィールド クラスへのポインタ (オーバーロードされたメソッド - 名前によるフィールド インデックスの取得) |
Field | 名前によるフィールド インデックス |
Rows |
サーバー応答の行数 |
Row |
インデックスによる行クラスへのポインタ |
Value |
行およびフィールドインデックスによる文字列値 |
Value | 行インデックスとフィールド名による文字列値 |
ColumnToArray | string タイプ配列への列の読み取りの結果 |
ColumnToArray |
型検証を伴う int タイプ配列への列の読み取りの結果 |
ColumnToArray |
型検証を伴う long タイプ配列への列の読み取りの結果 |
ColumnToArray |
型検証を伴う double タイプ配列への列の読み取りの結果 |
メソッド |
戻り値 |
---|---|
AffectedRows |
直近の操作によって影響を受ける行数 |
LastId |
LAST_INSERT_ID
value |
ServerStatus |
サーバーステータスフラグ |
Warnings |
アラートの数 |
Message |
サーバーのテキスト メッセージ |
MySQLServerErrorサーバー エラー構造
構造体の要素
Element |
Type |
Purpose |
---|---|---|
code |
ushort | エラー コード |
sqlstate |
uint | State |
message | string | サーバーのテキスト メッセージ |
CMySQLFieldフィールド クラス
クラスメソッド
メソッド |
戻り値 |
---|---|
Catalog |
テーブルが属するディレクトリの名前 |
Database |
テーブルが属するデータベースの名前 |
Table |
フィールドが属するテーブルの仮名 |
OriginalTable |
フィールドが属するテーブルの元の名前 |
Name |
フィールドの仮名 |
OriginalName |
元のフィールド名 |
Charset |
適用されたエンコード番号 |
Length |
値の長さ |
Type |
値の種類 |
Flags |
値属性を定義するフラグ |
Decimals |
許容小数点以下の桁数 |
MQLType |
ENUM_DATABASE_FIELD_TYPE値の形式のフィールド型(DATABASE_FIELD_TYPE_NULLを除く) |
CMySQLRow row class
クラスメソッド
メソッド |
アクション |
---|---|
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 array の形式の値を取得します。 |
Blob |
型検証を使用してフィールド名によって uchar array の形式で値を取得します。 |
Note. 型検証とは、int型を処理するメソッドの読み取り可能なフィールドが D ATABASE_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 コネクタの実装を使用してソケットを操作するための関数の使用を検討しました。 これは長い間理論にとどまっていました。 この記事の2番目の部分は、より実用的な性質を持つもので、シグナルのプロパティを収集するためのサービスと、その変化を見るためのプログラムを開発しました。
添付されたアーカイブには、次のファイルがあります。
- Include\MySQL\パス: コネクタソースコード
- Scripts\test_mysql.mq5ファイル:アプリケーションセクションで考慮されているコネクタを使用する例。