ネットワーク関数の適用、または.dll を使用しない MySQL: パート I - コネクタ

8 5月 2020, 11:19
Serhii Shevchuk
0
428

目次

イントロダクション

約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 は、キャプチャされたパケットを含むトラフィック アナライザ ウィンドウを示します。

  1. フィルタラインを表示します。 "tcp.port===3306" は、ローカルまたはリモートの TCP ポートが 3306 のパケットのみが表示されることを意味します (デフォルトの MySQL サーバー ポート)。
  2. パケット. ここでは、接続設定プロセス、サーバーグリーティング、認証リクエスト、その後の交換を見ることができます。
  3. 選択したパケットの内容を 16 進形式で指定します。 この場合、MySQL サーバーグリーティング パケットの内容を確認できます。
  4. トランスポート レベル (TCP)。 ソケットを操作するための関数を使用する場合は、ここに配置します。
  5. アプリケーション レベル (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 つのケースでエラーが発生する可能性があると記載されています。

  1. ERR_NETSOCKET_TOO_MANY_OPENEDエラーは、128個を超えるソケットが開いていることを示します。
  2. この関数が無効になっているインジケータからソケット作成を呼び出そうとすると、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と同等です。

MySQL パケット

図5. MySQL パケット

MySQL パケット形式は図5 に表示されます。 最初の 3 バイトはパケット内の有用な負荷のサイズを定義し、次のバイトはシーケンス内のパケットのシリアル番号を表し、その後にデータが続きます。 各交換の開始時にシリアル番号はゼロに設定されます。 たとえば、グリーティング パケットは 0、クライアント認証リクエストは 1、サーバー応答 - 2 (接続フェーズの終了) です。 次に、クライアント クエリを送信する場合、シーケンス番号の値を再度 0 に設定し、各サーバー応答パケットで増加します。 パケット数が 255 を超えると、値は 0 を渡します。

最もシンプルなパケット (MySQL ping) は、トラフィック アナライザで次のようになります。

トラフィック アナライザでの 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 サーバーとデータのやり取りするロジックを柔軟に構築できます。


Wireshark アナライザのパケット

図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 つの理由のいずれかが原因で、トランザクションが失敗する可能性があります。

インプットが正しく指定されている場合、スクリプト操作の結果は次のようになります。

テスト スクリプト操作の結果

図10. テスト スクリプト操作の結果

複数のクエリを適用するより複雑な例と定数接続モードについては、第 2 部で説明します。


ドキュメンテーション

目次


    トランザクション クラス

    クラス メソッドの一覧

    メソッド
    アクション
    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 タイプ配列への列の読み取りの結果
    "OK" タイプのパケットのメソッド:
    メソッド
    戻り値
    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ファイル:アプリケーションセクションで考慮されているコネクタを使用する例。

    MetaQuotes Software Corp.によりロシア語から翻訳された
    元の記事: https://www.mql5.com/ru/articles/7117

    添付されたファイル |
    MQL5.zip (23.17 KB)
    MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第28部): 未決取引リクエスト - 特定の条件下でのポジションの決済 MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第28部): 未決取引リクエスト - 特定の条件下でのポジションの決済

    保留中リクエストを使用した取引を特徴とするライブラリ機能の開発を継続します。ポジションを開き、未決注文を出すための条件付き取引リクエストの送信をすでに実装しています。現在の記事では、条件付きのポジション決済(完全決済、部分決済、反対ポジションによる決済)を実装します。

    MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第27部): 未決取引リクエスト - 特定の条件下でポジションを開く MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第27部): 未決取引リクエスト - 特定の条件下でポジションを開く

    ユーザが保留中リクエストを使用して取引できるようにする機能の開発を継続します。本稿では、特定の条件下で指値注文を出す機能を実装します。

    MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第29部): 未決取引リクエスト - 特定の条件下での注文とポジションの削除と変更 MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第29部): 未決取引リクエスト - 特定の条件下での注文とポジションの削除と変更

    本稿では、保留中リクエスト取引の概念の説明を完了し、未決注文を削除する機能と、特定の条件下で注文とポジションを変更する機能を作成します。したがって、シンプルなカスタム戦略、またはユーザ定義の条件でアクティブ化されるEA動作ロジックを開発できるようになります。

    ネットワーク関数の適用、または DLL を使用しない MySQL: パートII - シグナル特性の変化を監視するプログラム ネットワーク関数の適用、または DLL を使用しない MySQL: パートII - シグナル特性の変化を監視するプログラム

    前のパートでは、MySQLコネクタの実装をしました。 この記事では、シグナルプロパティを収集するためのサービスを実装し、時間の経過とともに変化を表示するためのプログラムを実装することで、そのアプリケーションを検討します。 実装された例は、ユーザーがシグナルのWebページに表示されないプロパティの変化を観察する必要がある場合、実用的な意味を持ちます。