English Русский 中文 Español Deutsch Português
preview
MetaTrader5のWebSocket

MetaTrader5のWebSocket

MetaTrader 5 | 26 1月 2021, 08:43
1 207 0
Francis Dube
Francis Dube

はじめに

MetaTrader 5は何年にもわたって大規模に成長しており、トレーダーに幅広い機能を提供しています。その際立った機能は、独自のプログラミング言語を使用しているにもかかわらず、さまざまなシステムやプラットフォームと統合できることです。この能力は、潜在的に有益な取引戦略を探求する際にトレーダーに多くのオプションを提供するため、非常に重要です。

この統合の鍵は、より効率的で実装が容易な最新のネットワークプロトコルを利用できることです。このような観点から、MetaTrader5アプリケーション用のDLL(ダイナミックリンクライブラリ)を使用しないWebSocketクライアントの実装を調査します。

WebSocketネットワークプロトコルの簡単な紹介で始めます。

Websocketの概要

Websocketプロトコルは、複数のHTTP(hyper text transfer protocol)ベースの要求を行うことなく、サーバとクライアント間の双方向の情報フローを可能にする通信方法です。ブラウザとほとんどのWebインターフェイスアプリケーションは、インスタントメッセージング、動的Webコンテンツ、オンラインマルチプレイヤーゲームなどのさまざまなサービスを提供するのにWebsocketプロトコルを使用しています。

Websocketが必要な理由

WebSocketプロトコルが存在する前は、開発者はサーバとクライアント間の非同期通信を実現するために非効率的でコストのかかる手法を採用する必要がありました。

これらが含まれていました。

  • ポーリング - これは本質的に同期的な方法であり、送信が必要なデータがない場合でも継続的に要求を行うため、コンピューティングリソースが無駄になります。
  • ロングポーリング- ポーリングと似ていますが、違いはその名前が示すとおりです。クライアントはサーバに対して頻繁に要求を行う代わりに、比較的少ない要求を行います。サーバは、何らかの交換が行われるかタイムアウトが有効になるまで、接続を開いてアクティブに保つことで応答します。
  • ストリーミング - この方法では、クライアントがデータを要求する必要があり、サーバは接続を無期限に維持します。主な欠点は、HTTPヘッダーが広範囲に使用されて取得するデータのサイズが増加することです。
  • Ajax - 主に、非同期Webコンテンツの先駆けとなるブラウザテクノロジーの非同期javascriptおよびxmlです。Webサイトに投稿すると、Webページ全体を更新しなくても、ほぼ瞬時にコンテンツがWebページに表示されます。

上記のすべての方法では、クライアントとサーバ間でさまざまなレベルの双方向データ交換が可能でしたが、WebSocketと比較すると、次の3つの主な理由により問題が発生します。

  • 前述のように、上記の手法ではさまざまなレベルの非同期送信が提供されます。実際、通信タイプはせいぜい半二重であると説明できます。つまり、交換の各参加者は、応答が提供される前に、他の参加者が終了するのを待つ必要があります。
  • 上記の方法では、HTTPヘッダーを多用しています。これを、時々必要なHTTP要求の頻度と組み合わせると、比較的過剰なデータ使用量につながります。これは、帯域幅を効率的に使用することが重要な場合に不利になる可能性があります。
  • 効率にも関係するのはコストです。サーバ接続を必要のないときに長期間存続させたり、すでにナビゲートしている可能性のあるクライアントにデータを送信したりすることは、サーバの実行コストを押し上げるため、大企業にとっては無駄です。

WebSocketの機能

WebSocketはTCPベースのプロトコルであり、他のアプリケーションまたは業界で定義されたサブプロトコルをサポートするためにさらに拡張できます。TCPベースであるため、標準のHTTPポート80、443で機能し、同様のURL(ユニバーサルリソースロケータ)スキーマを備えています。WebSocketサーバアドレスには、httpではなくwsまたはwssのプレフィックスが付いていますが、HTTP Webアドレスと同じURLアドレス構造に従います。例: 

ws(s)://websocketexampleurl.com:80/hello.php

                   

Websocketについて

WebSocketクライアントをMql5に実装する方法を理解するには、一般的なコンピューターネットワークの基礎に精通している必要があります。WebsocketプロトコルはHTTP(ハイパーテキスト転送プロトコル)に似ていて、サーバへのクライアントリクエストでヘッダが使用されます。HTTPと同様に、WebSocketで定義された接続を確立するにはヘッダを使用する必要があります。WebSocketの主な違いは、このような要求はWebSocketを確立または初期化するためにのみ必要なことです。クライアントが通常のHTTPリクエストのように見える要求を行いますが、使用されるプロトコルはHTTPプロトコルからWebsocketプロトコルに切り替わります。

このプロセスは、WebSocketハンドシェイクと呼ばれます。プロトコルの切り替えは、サーバへの最初のHTTP要求に特定のヘッダが含まれている場合にのみ行われます。次に、サーバは、WebSocket接続を確立したいという希望を確認してそれに応答する必要があります。特別なヘッダの性質とサーバの応答方法に関する情報はすべてRFC 6455に記載されています。

WebSocketが確立されると、HTTPのような要求を使用する必要がなくなります。ここで、プロトコルは操作の点で分岐します。WebSocketプロトコルを使用してデータを交換する場合は、別の形式が採用されます。この形式はHTTPリクエストに比べてより合理化されており、使用するrawビットがはるかに少なくなっています。使用される形式はフレーミングプロトコルと呼ばれ、ホスト間で1回のトランザクションで交換されるデータはフレームと呼ばれます。

各フレームは、RFC 6455で規定されているフレーミングプロトコルに準拠する特定の方法で配置されたビットのシーケンスです。すべてのWebSocketフレームには、オペコード、ペイロードのサイズ、および実際のペイロード自体を定義するビットが含まれています。プロトコルは、これらのビットがどのように配置され、最終的にフレーム内にパッケージ化されるかも定義します。オペコードは、フレームを分類するために使用される予約済みの数値です。

WebSocketの場合、基本オペコードは次のように定義されます。

0 — 継続フレーム: この値は、不完全なペイロードデータを示しているため、より多くのフレームが予想されます。この機能により、フレームの断片化が可能になり、データを異なるフレームにパッケージ化されたチャンクに分割できます。

1 — テキストフレーム: この値は、ペイロードデータが本質的にテキストであることを示します。

2 — バイナリフレーム: この値では、ペイロードはバイナリ形式です。

8 — クローズフレーム: この値は、いずれかのエンドポイントが確立されたWebSocket接続を閉じるときに送信される特殊フレームを示します。これは、制御フレームと呼ばれるフレームタイプです。制御フレームにはすでに特別な意味があるため、ペイロードデータが常に含まれているとは限りません。つまり、ペイロードはオプションです。

9 — pingフレーム: エンドポイントがまだ接続されているかどうかを判断するために使用される別の制御フレームです。

10 — pongフレーム: エンドポイントがpingフレームを受信するたびに、pongフレームが応答として使用されます。このような状況では、受信者は適切なpongフレームでできるだけ早く応答する必要があります。通常、pingフレームに含まれているペイロードをエコーするだけで十分です。

WebSocketでサポートされる必要がある基本オペコードはこれだけです。このプロトコルでは、WebSocketベースのAPIまたはWebSocketサブプロトコルでこれらの予約値を拡張できます。

フレームに関する最後の重要な側面はマスキングです。RFC 6455では、クライアントからサーバに送信されるすべてのフレームをマスクする必要があります。マスキングは、WebSocketプロトコルの基本的なセキュリティ対策として機能します。これには、事前定義されたアルゴリズムを使用して、キーと呼ばれるランダムに生成された4バイトの値でペイロードを操作する必要があります。これは一種のデータの難読化と考えることができます。アルゴリズムはRFC 6455ドキュメントに記載されています。クライアントから送信されるすべてのフレームは、フラグメント化されたフレームの場合でも、新しく生成されたランダムキーを使用する必要があります。

このセクションでは、WebSocketプロトコルの重要な特性について簡単に説明しました。詳細については、RFC6455のドキュメントを参照してください。この知識があれば、コードの実装を理解するのはずっと簡単になると思います。


MQL5 WebSocketクライアント — ライブラリの概要

まずコードを3つのクラスに分割します。

CSocket - MQL5 APIのネットワーキング機能をカプセル化します。

CFrame — フレームクラスはWebSocketフレームを表し、主にサーバから受信したフレームをデコードするために使用されます。

CWebSocketClient — WebSocketクライアント自体を表します。

CSocket

//+------------------------------------------------------------------+
//| structs                                                          |
//+------------------------------------------------------------------+
struct CERT
  {
   string            cert_subject;
   string            cert_issuer;
   string            cert_serial;
   string            cert_thumbprint;
   datetime          cert_expiry;
  };


//+------------------------------------------------------------------+
//| Class CSocket.                                                   |
//| Purpose: Base class of socket operations.                        |
//|                                                                  |
//+------------------------------------------------------------------+

class CSocket
  {
private:
   static int        m_usedsockets;   // tracks number of sockets in use in single program
   bool              m_log;           // logging state
   bool              m_usetls;        //  tls state
   uint              m_tx_timeout;    //  send system socket timeout in milliseconds
   uint              m_rx_timeout;    //  receive system socket timeout in milliseconds
   int               m_socket;        //  socket handle
   string            m_address;       //  server address
   uint              m_port;          //  port


   CERT              m_cert;          //  Server certificate info

public:
                     CSocket();
                    ~CSocket();
   //--- methods to get private properties
   int               SocketID(void)           const { return(m_socket); }
   string            Address(void)            const { return(m_address);   }
   uint              Port(void)               const { return(m_port);  }
   bool              IsSecure(void)           const { return(m_usetls); }
   uint              RxTimeout(void)          const { return(m_rx_timeout); }
   uint              TxTimeout(void)          const { return(m_tx_timeout); }
   bool              ServerCertificate(CERT& certificate);


   //--- methods to set private properties
   bool              SetTimeouts(uint tx_timeout, uint rx_timeout);
   //--- general methods for working sockets
   void              Log(const string custom_message,const int line,const string func);
   static uint       SocketsInUse(void)        {   return(m_usedsockets);  }
   bool              Open(const string server,uint port,uint timeout,bool use_tls=false,bool enablelog=false);
   bool              Close(void);
   uint              Readable(void);
   bool              Writable(void);
   bool              IsConnected(void);
   int               Read(uchar& out[],uint out_len,uint ms_timeout,bool read_available);
   int               Send(uchar& in[],uint in_len);

  };

int CSocket::m_usedsockets=0;
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSocket::CSocket():m_socket(INVALID_HANDLE),
   m_address(""),
   m_port(0),
   m_usetls(false),
   m_log(false),
   m_rx_timeout(150),
   m_tx_timeout(150)
  {
   ZeroMemory(m_cert);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CSocket::~CSocket()
  {
//--- check handle
   if(m_socket!=INVALID_HANDLE)
      Close();
  }
//+------------------------------------------------------------------+
//| set system socket timeouts                                       |
//+------------------------------------------------------------------+
bool CSocket::SetTimeouts(uint tx_timeout,uint rx_timeout)
  {
   if(m_socket==INVALID_HANDLE)
     {
      Log("Invalid socket",__LINE__,__FUNCTION__);
      return(false);
     }

   if(SocketTimeouts(m_socket,tx_timeout,rx_timeout))
     {
      m_tx_timeout=tx_timeout;
      m_rx_timeout=rx_timeout;
      Log("Socket Timeouts set",__LINE__,__FUNCTION__);
      return(true);
     }

   return(false);
  }

//+------------------------------------------------------------------+
//| certificate                                                      |
//+------------------------------------------------------------------+
bool CSocket::ServerCertificate(CERT& certificate)
  {

   if(m_socket==INVALID_HANDLE)
     {
      Log("Invalid socket",__LINE__,__FUNCTION__);
      return(false);
     }

   if(SocketTlsCertificate(m_socket,m_cert.cert_subject,m_cert.cert_issuer,m_cert.cert_serial,m_cert.cert_thumbprint,m_cert.cert_expiry))
     {
      certificate=m_cert;
      Log("Server certificate retrieved",__LINE__,__FUNCTION__);
      return(true);
     }

   return(false);

  }
//+------------------------------------------------------------------+
//|connect()                                                         |
//+------------------------------------------------------------------+
bool CSocket::Open(const string server,uint port,uint timeout,bool use_tls=false,bool enablelog=false)
  {
   if(m_socket!=INVALID_HANDLE)
      Close();

   if(m_usedsockets>=128)
     {
      Log("Too many sockets open",__LINE__,__FUNCTION__);
      return(false);
     }

   m_usetls=use_tls;

   m_log=enablelog;

   m_socket=SocketCreate();
   if(m_socket==INVALID_HANDLE)
     {
      Log("Invalid socket",__LINE__,__FUNCTION__);
      return(false);
     }
   ++m_usedsockets;
   m_address=server;

   if(port==0)
     {
      if(m_usetls)
         m_port=443;
      else
         m_port=80;
     }
   else
      m_port=port;
//---
   if(!m_usetls && m_port==443)
      m_usetls=true;
//---
   Log("Connecting to "+m_address,__LINE__,__FUNCTION__);
//---
   if(m_usetls)
     {
      if(m_port!=443)
        {
         if(SocketConnect(m_socket,server,port,timeout))
            return(SocketTlsHandshake(m_socket,server));
        }
      else
        {
         return(SocketConnect(m_socket,server,port,timeout));
        }
     }

   return(SocketConnect(m_socket,server,port,timeout));
  }
//+------------------------------------------------------------------+
//|close()                                                           |
//+------------------------------------------------------------------+
bool CSocket::Close(void)
  {
//---
   if(m_socket==INVALID_HANDLE)
     {
      Log("Socket Disconnected",__LINE__,__FUNCTION__);
      return(true);
     }
//---
   if(SocketClose(m_socket))
     {
      m_socket=INVALID_HANDLE;
      --m_usedsockets;
      Log("Socket Disconnected from "+m_address,__LINE__,__FUNCTION__);
      m_address="";
      ZeroMemory(m_cert);
      return(true);
     }
//---
   Log("",__LINE__,__FUNCTION__);
   return(false);
  }
//+------------------------------------------------------------------+
//|readable()                                                        |
//+------------------------------------------------------------------+
uint CSocket::Readable(void)
  {
   if(m_socket==INVALID_HANDLE)
     {
      Log("Invalid socket",__LINE__,__FUNCTION__);
      return(0);
     }
//---
   Log("Is Socket Readable ",__LINE__,__FUNCTION__);
//---
   return(SocketIsReadable(m_socket));
  }
//+------------------------------------------------------------------+
//|writable()                                                        |
//+------------------------------------------------------------------+
bool CSocket::Writable(void)
  {
   if(m_socket==INVALID_HANDLE)
     {
      Log("Invalid socket",__LINE__,__FUNCTION__);
      return(false);
     }
//---
   Log("Is Socket Writable ",__LINE__,__FUNCTION__);
//---
   return(SocketIsWritable(m_socket));
  }
//+------------------------------------------------------------------+
//|isconnected()                                                     |
//+------------------------------------------------------------------+
bool CSocket::IsConnected(void)
  {
   if(m_socket==INVALID_HANDLE)
     {
      Log("Invalid socket",__LINE__,__FUNCTION__);
      return(false);
     }
//---
   Log("Is Socket Connected ",__LINE__,__FUNCTION__);
//---
   return(SocketIsConnected(m_socket));
  }
//+------------------------------------------------------------------+
//|read()                                                            |
//+------------------------------------------------------------------+
int CSocket::Read(uchar& out[],uint out_len,uint ms_timeout,bool read_available=false)
  {
   if(m_socket==INVALID_HANDLE)
     {
      Log("Invalid socket",__LINE__,__FUNCTION__);
      return(-1);
     }
//---
   Log("Reading from "+m_address,__LINE__,__FUNCTION__);

   if(m_usetls)
     {
      if(read_available)
         return(SocketTlsReadAvailable(m_socket,out,out_len));
      else
         return(SocketTlsRead(m_socket,out,out_len));
     }
   else
      return(SocketRead(m_socket,out,out_len,ms_timeout));

   return(-1);
  }
//+------------------------------------------------------------------+
//|send()                                                            |
//+------------------------------------------------------------------+
int CSocket::Send(uchar& in[],uint in_len)
  {
   if(m_socket==INVALID_HANDLE)
     {
      Log("Invalid socket",__LINE__,__FUNCTION__);
      return(-1);
     }
//---
   Log("Sending to "+m_address,__LINE__,__FUNCTION__);
//---
   if(m_usetls)
      return(SocketTlsSend(m_socket,in,in_len));
   else
      return(SocketSend(m_socket,in,in_len));
//---
   return(-1);
  }
//+------------------------------------------------------------------+
//|log()                                                             |
//+------------------------------------------------------------------+
void CSocket::Log(const string custom_message,const int line,const string func)
  {
   if(m_log)
     {
      //---
      int eid=GetLastError();
      //---
      if(eid!=0)
        {
         PrintFormat("[MQL error ID: %d][%s][Line: %d][Function: %s]",eid,custom_message,line,func);
         ResetLastError();
         return;
        }
      if(custom_message!="")
         PrintFormat("[%s][Line: %d][Function: %s]",custom_message,line,func);
     }
//---
  }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

ソケットクラスでは、サーバ証明書情報をカプセル化する構造体CERTを定義します。

  •  cert_subject — 証明書所有者名
  •  cert_issuer — 証明書発行者名
  •  cert_serial — 証明書のシリアル番号
  •  cert_thumbprint — 証明書SHA-1ハッシュ
  •  cert_expiry — 証明書の有効期限

privateプロパティを取得するメソッド:

SocketID — 正常に作成されたソケットのソケットハンドルを返します
Address — ソケットが接続されているリモートアドレスを文字列として返します
Port  — アクティブなソケットが接続されているリモートポートを返します
IsSecure — ソケットでTLSセキュリティが有効になっているかどうかに応じてtrueまたはfalseを返します
RxTimeout —ソケットからの読み取りに設定されたタイムアウトをミリ秒単位で返します
TxTimeout — ソケットへの書き込みに設定されたタイムアウトをミリ秒単位で返します
ServerCertificate — ソケットが接続されているサーバのサーバ証明書情報を返します
SocketsInUse — 1つのプログラムで現在使用されているソケットの総数を返します

privateプロパティを設定するメソッド:

SetTimeouts — ソケットの読み取りと書き込みのタイムアウトをミリ秒単位で設定します
ソケットを操作するための一般的なメソッド:
Log — ソケットのアクティビティをログに記録するためのユーティリティメソッド。ターミナルの操作ログにメッセージを出力するには、Openメソッドでソケットを初期化するときに設定する必要があります。
Open — リモートサーバへの接続を確立し、新しいソケットを作成するメソッド
Close - リモートサーバから切断し、ソケットを初期化解除するためのメソッド
Readable — ソケットでの読み取りに使用できるバイト数を返します
Writable - ソケットが送信操作に使用できるかどうかを照会します
IsConnected — ソケット接続がまだアクティブかどうかを確認します
Read — ソケットからデータを読み取ります
Send - アクティブなソケットで送信操作を実行するためのメソッド

CFrame

//+------------------------------------------------------------------+
//| enums                                                            |
//+------------------------------------------------------------------+
enum ENUM_FRAME_TYPE     // type of websocket frames (ie, message types)
  {
   CONTINUATION_FRAME=0x0,
   TEXT_FRAME=0x1,
   BINARY_FRAME= 0x2,
   CLOSE_FRAME = 8,
   PING_FRAME = 9,
   PONG_FRAME = 0xa,
  };
//+------------------------------------------------------------------+
//| class frame                                                      |
//| represents a websocket message frame                             |
//+------------------------------------------------------------------+



//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CFrame
  {
private:
   uchar             m_array[];
   uchar             m_isfinal;
   ENUM_FRAME_TYPE   m_msgtype;

   int               Resize(int size) {return(ArrayResize(m_array,size,size));}

public:
                     CFrame():m_isfinal(0),m_msgtype(0) {   }

                    ~CFrame() {      }
   int               Size(void) {return(ArraySize(m_array));}
   bool              Add(const uchar value);
   bool              Fill(const uchar &array[],const int src_start,const int count);
   void              Reset(void);
   uchar             operator[](int index);
   string            ToString(void);
   ENUM_FRAME_TYPE   MessageType(void) { return(m_msgtype);}
   bool              IsFinal(void) { return(m_isfinal==1);}
   void              SetMessageType(ENUM_FRAME_TYPE mtype) { m_msgtype=mtype;}
   void              SetFinal(void) { m_isfinal=1;}

  };
//+------------------------------------------------------------------+
//| Receiving an element by index                                    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
uchar CFrame::operator[](int index)
  {
   static uchar invalid_value;
//---
   int max=ArraySize(m_array)-1;
   if(index<0 || index>=ArraySize(m_array))
     {
      PrintFormat("%s index %d is not in range (0-%d)!",__FUNCTION__,index,max);
      return(invalid_value);
     }
//---
   return(m_array[index]);
  }
//+------------------------------------------------------------------+
//| Adding element                                                   |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CFrame::Fill(const uchar &array[],const int src_start,const int count)
  {
   int p_size=Size();
//---
   int size=Resize(p_size+count);
//---
   if(size>0)
      return(ArrayCopy(m_array,array,p_size,src_start,count)==count);
   else
      return(false);
//---
  }
//+------------------------------------------------------------------+
//| Assigning for the array                                          |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CFrame::Add(const uchar value)
  {
   int size=Resize(Size()+1);
//---
   if(size>0)
      m_array[size-1]=value;
   else
      return(false);
//---
   return(true);
//---
  }
//+------------------------------------------------------------------+
//|  Reset                                                           |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CFrame::Reset(void)
  {
   if(Size())
      ArrayFree(m_array);
//---

   m_isfinal=0;

   m_msgtype=0;

  }
//+------------------------------------------------------------------+
//|converting array to string                                        |
//+------------------------------------------------------------------+
string CFrame::ToString(void)
  {
   if(Size())
      if(m_msgtype==CLOSE_FRAME)
         return(CharArrayToString(m_array,2,WHOLE_ARRAY,CP_UTF8));
   else
      return(CharArrayToString(m_array,0,WHOLE_ARRAY,CP_UTF8));
   else
      return(NULL);
  }

フレームクラスは、WebSocketプロトコルによって文書化されたさまざまなフレームタイプを記述する列挙型ENUM_FRAME_TYPEを定義します。

CFrameクラスのインスタンスは、サーバから受信した単一のフレームを表します。つまり、完全なメッセージはフレームのコレクションで構成されている可能性があります。このクラスを使用すると、フレームを構成する個々のバイト値など、各フレームのさまざまな特性を照会できます。 

Sizeメソッドは、フレームのバイト単位のサイズを返します。クラスは符号なし文字型の配列をフレームのコンテナとして使用するためです。このメソッドは、基になる配列のサイズを返すだけです。 

MessageTypeメソッドは、フレームの型をENUM_FRAME_TYPE型として返します。

IsFinalメソッドは、フレームが最後のフレームであるか最後のフレームであるかを確認します。つまり、受信したデータはすべて完全であると見なす必要があります。これにより、断片化されたため不完全なメッセージと完全なメッセージを区別できます。

operator [] - 添え字演算子のオーバーロードにより、フレーム内の任意の要素を配列形式で個別に取得できます。

CFrameクラスは、CSocketオブジェクトから読み取るときにWebSocketクライアントで使用されます。フレームを埋めるために使用されるメソッドは、AddとFillです。これにより、個々の要素または適切な配列を使用してフレームに入力できます。

ユーティリティメソッドResetは、フレームをフラッシュしてそのプロパティをリセットするために使用できますが、ToStringメソッドは、フレームの内容を使い慣れた文字列値に変換するための便利なツールです。

CWebSocketClient

このクラスには、#definesとして実装される定数があります。HEADERプレフィックス記号は、オープニングハンドシェイクの作成に必要なHTTPヘッダフィールドに関連付けられています。GUIDは、応答ヘッダの一部を生成するときにサーバサイドのWebSocketプロトコルによって使用されるグローバル一意識別子です。ここでのクラスはそれを使用してハンドシェイクプロセスの正確さを確認および実証しますが、本質的には不要であり、クライアントはハンドシェイクが成功したことを確認するためのヘッダフィールド| Sec-WebSocket-Accept |の存在を確認するだけで済みます。

#include <Socket.mqh>
#include <Frame.mqh>


//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
#define SH1                 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
#define HEADER_EOL          "\r\n"
#define HEADER_GET          "GET /"
#define HEADER_HOST         "Host: "
#define HEADER_UPGRADE      "Upgrade: websocket"+HEADER_EOL
#define HEADER_CONNECTION   "Connection: Upgrade"+HEADER_EOL
#define HEADER_KEY          "Sec-WebSocket-Key: "
#define HEADER_WS_VERSION   "Sec-WebSocket-Version: 13"+HEADER_EOL+HEADER_EOL
#define HEADER_HTTP         " HTTP/1.1"


列挙型ENUM_STATUS_CLOSE_CODEは、クローズフレームとともに送信または受信できるクローズコードを一覧表示します。列挙型ENUM_WEBSOCKET_CLIENT_STATEは、WebSocketが取得できるさまざまな状態を表します。

CLOSEDは、クライアントにソケットが割り当てられる前、またはクライアントが接続を切断して基になるソケットが閉じられた後の初期状態です。

オープニングハンドシェイク(ヘッダ)を送信する前に最初の接続が確立されると、クライアントはCONNECTING状態にあると言われます。オープニングハンドシェイクが送信され、WebSocketプロトコルの使用を許可する応答が受信されると、クライアントが接続されます(CONNECTED)。

CLOSING状態は、クライアントがクライアントの初期化以降に初めてクローズフレームを受信したとき、またはクライアントが最初のクローズフレームを送信して、接続を切断していることをサーバーに通知したときに現れます。CLOSING状態では、クライアントはクローズフレームのみをサーバーに送信でき、他のタイプのフレームを送信しようとすると失敗します。クローズ通知を送信または受信した後はサービスを継続する義務がないため、CLOSING状態ではサーバが応答しない場合があることに注意してください。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum ENUM_CLOSE_CODE                 // possible reasons for disconnecting sent with a close frame
  {
   NORMAL_CLOSE = 1000,            // normal closure initiated by choice
   GOING_AWAY_CLOSE,               // close code for client navigating away from end point, used in browsers
   PROTOCOL_ERROR_CLOSE,           // close caused by some violation of a protocol, usually application defined
   FRAME_TYPE_ERROR_CLOSE,         // close caused by an endpoint receiving frame type that is not supportted or allowed
   UNDEFINED_CLOSE_1,              // close code is not defined by websocket protocol
   UNUSED_CLOSE_1,                 // unused
   UNUSED_CLOSE_2,                 // values
   ENCODING_TYPE_ERROR_CLOSE,      // close caused data in message is of wrong encoding type, usually referring to strings
   APP_POLICY_ERROR_CLOSE,         // close caused by violation of user policy
   MESSAGE_SIZE_ERROR_CLOSE,       // close caused by endpoint receiving message that is too large
   EXTENSION_ERROR_CLOSE,          // close caused by non compliance to or no support for specified extension of websocket protocol
   SERVER_SIDE_ERROR_CLOSE,        // close caused by some error that occurred on the server
   UNUSED_CLOSE_3 = 1015,          // unused
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum ENUM_WEBSOCKET_STATE
  {
   CLOSED=0,
   CLOSING,
   CONNECTING,
   CONNECTED
  };


ClientStateメソッドは、特定のWebSocketクライアントの接続状態を定義するプロパティを取得します

//+------------------------------------------------------------------+
//| ClientState()                                                    |
//+------------------------------------------------------------------+
ENUM_WEBSOCKET_STATE CWebSocketClient::ClientState(void)
  {
   if(m_socket.IsConnected())
      return(m_wsclient);
//---
   if(m_wsclient!=CLOSED)
     {
      m_socket.Close();
      m_wsclient=CLOSED;
     }
//---
   return(m_wsclient);
  }

SetMaxSendSize()は、WebSocketクライアントのフレームフラグメンテーション特性を構成するために使用されます。このメソッドは、クライアントからサーバに送信される単一フレームの最大サイズをバイト単位で設定し、フレームサイズの制限を適用するAPIでクライアントを柔軟に使用できるようにします。

void              SetMaxSendSize(int maxsend) {if(maxsend>=0) m_maxsendsize=maxsend;  else m_maxsendsize=0; }


Connectメソッドは、WebSocket接続を確立するために使用されます。secureパラメータは、TLSを使用してWebSocketを構成するかどうかのブール値です。このメソッドは、最初にCSocketクラスのopenメソッドを呼び出して、最初のTCP接続を確立します。成功すると、WebSocketの状態が接続に変わり、その後、アップグレードヘルパーメソッドが機能します。その責任は、WebSocketプロトコルに切り替えるために必要なHttpヘッダーを作成することです。最後に、関数の終了時にWebSocketの状態が確認されます。

//+------------------------------------------------------------------+
//| Connect(): Used to establish connection  to websocket server     |
//+------------------------------------------------------------------+
bool CWebSocketClient::Connect(const string url,const uint port,const uint timeout,bool use_tls=false,bool enablelog=false)
  {
   reset();
//---
   m_timeout=timeout;
//---
   if(!m_socket.Open(url,port,m_timeout,use_tls,enablelog))
     {
      m_socket.Log("Connect error",__LINE__,__FUNCTION__);
      return(false);
     }
   else
      m_wsclient=CONNECTING;
//---
   if(!upgrade())
      return(false);
//---
   m_socket.Log("ws client state "+EnumToString(m_wsclient),__LINE__,__FUNCTION__);
//---
   if(m_wsclient!=CONNECTED)
     {
      m_wsclient=CLOSED;
      m_socket.Close();
      reset();
     }
//---
   return(m_wsclient==CONNECTED);
  }

接続を閉じるまたは切断するには、ClientCloseメソッドが使用されます。これには、クローズコードと、サーバにクローズフレームとして送信されるメッセージ本文の2つのデフォルトパラメータがあります。122文字の制限より長いメッセージ本文は切り捨てられます。WebSocketの仕様によると、エンドポイント(サーバまたはクライアント)のいずれかがクローズフレームを(初めて)受信した場合、受信者は応答する必要があり、送信者はクローズ要求の確認応答として応答を期待する必要があります。clientCloseコードからわかるように、クローズフレームが送信されると、クライアントによってクローズが開始された場合でも、基になるTCPソケットは応答を待たずにクローズされます。クライアントのライフサイクルのこの時点で応答を待つことは、リソースの浪費のように思われるため、実装されませんでした。

//+------------------------------------------------------------------+
//| Close() inform server client is disconnecting                    |
//+------------------------------------------------------------------+
bool CWebSocketClient::Close(ENUM_CLOSE_CODE close_code=NORMAL_CLOSE,const string close_reason="")
  {
   ClientState();
//---
   if(m_wsclient==0)
     {
      m_socket.Log("Client Disconnected",__LINE__,__FUNCTION__);
      //---
      return(true);
     }
//---
   if(ArraySize(m_txbuf)<=0)
     {
      if(close_reason!="")
        {
         int len=StringToCharArray(close_reason,m_txbuf,2,120,CP_UTF8)-1;
         if(len<=0)
            return(false);
         else
            ArrayRemove(m_txbuf,len,1);
        }
      else
        {
         if(ArrayResize(m_txbuf,2)<=0)
           {
            m_socket.Log("array resize error",__LINE__,__FUNCTION__);
            return(false);
           }
        }
      m_txbuf[0]=(uchar)(close_code>>8) & 0xff;
      m_txbuf[1]=(uchar)(close_code>>0) & 0xff;
      //---
     }
//---
   m_msgsize=ArraySize(m_txbuf);
   m_sent=false;
//---
   send(CLOSE_FRAME);
//---
   m_socket.Close();
//---
   reset();
//---
   return(true);
//---
  }


 サーバに任意のデータを送信する場合、使用できる2つのメソッドから選択できます。SendStringは文字列を受け取り、SendDataは入力として配列を受け取ります。

SendPingとSendPongは、pingとpongを送信するための特別なメソッドです。どちらも、122文字の制限が適用されるオプションのメッセージ本文を許可します。


すべてのpublic送信メソッドは、それぞれの入力をm_txbuff配列にパッケージ化します。privateのsendメソッドは、フレームのタイプを設定し、filltxbuffer()を使用して、m_maxsendsizeプロパティの値に応じてメッセージの断片化を有効にします。FillTxbuffer()は単一のフレームを準備し、それを配列m_sendにパッケージ化します。m_sendが準備されると、サーバに送信されます。これはすべて、m_txbufferのすべての内容が送信されるまでループで実行されます。

//+------------------------------------------------------------------+
//| Send() sends text data to websocket server                       |
//+------------------------------------------------------------------+
int CWebSocketClient::SendString(const string message)
  {
   ClientState();
//---
   if(m_wsclient==CLOSED || m_wsclient==CLOSING)
     {
      m_socket.Log("invalid ws client handle",__LINE__,__FUNCTION__);
      return(0);
     }
//---
   if(message=="")
     {
      m_socket.Log("no message specified",__LINE__,__FUNCTION__);
      return(0);
     }
//---
   int len=StringToCharArray(message,m_txbuf,0,WHOLE_ARRAY,CP_UTF8)-1;
   if(len<=0)
     {
      m_socket.Log("string char array error",__LINE__,__FUNCTION__);
      return(0);
     }
   else
      ArrayRemove(m_txbuf,len,1);
//---
   m_msgsize=ArraySize(m_txbuf);
   m_sent=false;
//---
   return(send(TEXT_FRAME));
  }
//+------------------------------------------------------------------+
//| Send() sends user supplied array buffer                          |
//+------------------------------------------------------------------+
int CWebSocketClient::SendData(uchar &message_buffer[])
  {
   ClientState();
//---
   if(m_wsclient==CLOSED || m_wsclient==CLOSING)
     {
      m_socket.Log("invalid ws client handle",__LINE__,__FUNCTION__);
      return(0);
     }
//---
   if(ArraySize(message_buffer)==0)
     {
      m_socket.Log("array is empty",__LINE__,__FUNCTION__);
      return(0);
     }
//---
   if(ArrayResize(m_txbuf,ArraySize(message_buffer))<0)
     {
      m_socket.Log("array resize error",__LINE__,__FUNCTION__);
      return(0);
     }
   else
      ArrayCopy(m_txbuf,message_buffer);
//---
   m_msgsize=ArraySize(m_txbuf);
   m_sent=false;
//---
   return(send(BINARY_FRAME));
  }
//+------------------------------------------------------------------+
//| SendPong() sends pong response upon receiving ping               |
//+------------------------------------------------------------------+
int CWebSocketClient::SendPong(const string msg="")
  {
   ClientState();
//---
   if(m_wsclient==CLOSED || m_wsclient==CLOSING)
     {
      m_socket.Log("invalid ws client handle",__LINE__,__FUNCTION__);
      return(0);
     }
//---
   if(ArraySize(m_txbuf)<=0)
     {
      if(msg!="")
        {
         int len=StringToCharArray(msg,m_txbuf,0,122,CP_UTF8)-1;
         if(len<=0)
           {
            m_socket.Log("string to char array error",__LINE__,__FUNCTION__);
            return(0);
           }
         else
            ArrayRemove(m_txbuf,len,1);
        }
     }
//---
   m_msgsize=ArraySize(m_txbuf);
   m_sent=false;
//---
   return(send(PONG_FRAME));
  }
//+------------------------------------------------------------------+
//| SendPing() ping  the server                                      |
//+------------------------------------------------------------------+
int CWebSocketClient::SendPing(const string msg="")
  {
   ClientState();
//---
   if(m_wsclient==CLOSED || m_wsclient==CLOSING)
     {
      m_socket.Log("invalid ws client handle",__LINE__,__FUNCTION__);
      return(0);
     }
//---
   if(ArraySize(m_txbuf)<=0)
     {
      if(msg!="")
        {
         int len=StringToCharArray(msg,m_txbuf,0,122,CP_UTF8)-1;
         if(len<=0)
           {
            m_socket.Log("string to char array error",__LINE__,__FUNCTION__);
            return(0);
           }
         else
            ArrayRemove(m_txbuf,len,1);
        }
     }
//---
   m_msgsize=ArraySize(m_txbuf);
   m_sent=false;
//---
   return(send(PING_FRAME));
  }


//+------------------------------------------------------------------+
//|prepareSendBuffer()prepares array buffer for socket dispatch      |
//+------------------------------------------------------------------+
bool CWebSocketClient::fillTxBuffer(ENUM_FRAME_TYPE ftype)
  {
   uchar header[];
   static int it;
   static int start;
   uchar masking_key[4]={0};
   int maxsend=(m_maxsendsize<7)?m_msgsize:((m_maxsendsize<126)?m_maxsendsize-6:((m_maxsendsize<65536)?m_maxsendsize-8:m_maxsendsize-14));
//---
   for(int i=0; i<4; i++)
     {
      masking_key[i]=(uchar)(255*MathRand()/32767);
     }
//---
   m_socket.Log("[send]max size - "+IntegerToString(maxsend),__LINE__,__FUNCTION__);
   m_socket.Log("[send]should be max size - "+IntegerToString(m_maxsendsize),__LINE__,__FUNCTION__);
   int message_size=(((start+maxsend)-1)<=(m_msgsize-1))?maxsend:m_msgsize%maxsend;
   bool isfinal=((((start+maxsend)-1)==(m_msgsize-1)) || (message_size<maxsend) ||(message_size<=0))?true:false;
   bool isfirst=(start==0)?true:false;
//---
   m_socket.Log("[send]message size - "+IntegerToString(message_size),__LINE__,__FUNCTION__);
   if(isfirst)
      m_socket.Log("[send]first frame",__LINE__,__FUNCTION__);
   if(isfinal)
      m_socket.Log("[send]final frame",__LINE__,__FUNCTION__);
//---
   if(ArrayResize(header,2+(message_size>=126 ? 2 : 0)+(message_size>=65536 ? 6 : 0)+(4))<0)
     {
      m_socket.Log("array resize error",__LINE__,__FUNCTION__);
      return(false);
     }
//header[0] = (isfinal)? (0x80 | 0x1) :( );
   switch(ftype)
     {
      case CLOSE_FRAME:
         header[0]=uchar(0x80|CLOSE_FRAME);
         m_socket.Log("[building]close frame",__LINE__,__FUNCTION__);
         break;
      case PING_FRAME:
         header[0]=uchar(0x80|PING_FRAME);
         m_socket.Log("[building]ping frame",__LINE__,__FUNCTION__);
         break;
      case PONG_FRAME:
         header[0]=uchar(0x80|PONG_FRAME);
         m_socket.Log("[building]pong frame",__LINE__,__FUNCTION__);
         break;
      default:
         header[0]=(isfinal)? 0x80:0x0;
         m_socket.Log("[building]"+EnumToString(ftype),__LINE__,__FUNCTION__);
         if(isfirst)
            header[0]|=uchar(ftype);
         break;

     }
//---
   if(message_size<126)
     {
      header[1] = (uchar)(message_size & 0xff) |  0x80;
      header[2] = masking_key[0];
      header[3] = masking_key[1];
      header[4] = masking_key[2];
      header[5] = masking_key[3];
     }
   else
   if(message_size<65536)
     {
      header[1] = 126 |  0x80;
      header[2] = (uchar)(message_size >> 8) & 0xff;
      header[3] = (uchar)(message_size >> 0) & 0xff;
      header[4] = masking_key[0];
      header[5] = masking_key[1];
      header[6] = masking_key[2];
      header[7] = masking_key[3];
     }
   else
     {
      header[1] = 127 | 0x80;
      header[2] = (uchar)(message_size >> 56) & 0xff;
      header[3] = (uchar)(message_size >> 48) & 0xff;
      header[4] = (uchar)(message_size >> 40) & 0xff;
      header[5] = (uchar)(message_size >> 32) & 0xff;
      header[6] = (uchar)(message_size >> 24) & 0xff;
      header[7] = (uchar)(message_size >> 16) & 0xff;
      header[8] = (uchar)(message_size >>  8) & 0xff;
      header[9] = (uchar)(message_size >>  0) & 0xff;

      header[10] = masking_key[0];
      header[11] = masking_key[1];
      header[12] = masking_key[2];
      header[13] = masking_key[3];

     }
//---
   if(ArrayResize(m_send,ArraySize(header),message_size)<0)
     {
      m_socket.Log("array resize error",__LINE__,__FUNCTION__);
      return(false);
     }
//---
   ArrayCopy(m_send,header,0,0);
//---
   if(message_size)
     {
      if(ArrayResize(m_send,ArraySize(header)+message_size)<0)
        {
         m_socket.Log("array resize error",__LINE__,__FUNCTION__);
         return(false);
        }
      //---
      ArrayCopy(m_send,m_txbuf,ArraySize(header),start,message_size);
      //---
      int bufsize=ArraySize(m_send);
      //---
      int message_offset=bufsize-message_size;
      //---
      for(int i=0; i<message_size; i++)
        {
         m_send[message_offset+i]^=masking_key[i&0x3];
        }
     }
//---
   if(isfinal)
     {
      it=0;
      start=0;
      m_sent=true;
      ArrayFree(m_txbuf);
     }
   else
     {
      it++;
      start=it*maxsend;
     }
//---
   return(true);

  }


//+------------------------------------------------------------------+
//|int  sendMessage() helper                                         |
//+------------------------------------------------------------------+
int  CWebSocketClient::send(ENUM_FRAME_TYPE frame_type)
  {
//---
   bool done=false;
   int bytes_sent=0,sum_sent=0;

   while(!m_sent)
     {
      done=fillTxBuffer(frame_type);
      if(done && m_socket.Writable())
        {
         bytes_sent=m_socket.Send(m_send,(uint)ArraySize(m_send));
         //---
         if(bytes_sent<0)
            break;
         else
           {
            sum_sent+=bytes_sent;
            ArrayFree(m_send);
           }
         //---
        }
      else
         break;
     }
//---
   if(ArraySize(m_send)>0)
      ArrayFree(m_send);
//---
   m_socket.Log("",__LINE__,__FUNCTION__);
//---
   return(sum_sent);
  }

クライアントに送信されるデータは、Readable() publicメソッドが呼び出されるたびに、fillrxbuffer() privateメソッドによってm_rxbuff配列にバッファリングされます。これは、Read()メソッドの呼び出しを使用して取得可能なデータの可用性を示すm_rxbuff配列のサイズを返します。

//+------------------------------------------------------------------+
//| receiver()fills rxbuf with raw message                           |
//+------------------------------------------------------------------+
int CWebSocketClient::fillRxBuffer(void)
  {
   uint leng=0;
   int rsp_len=-1;

//---
   uint timeout_check=GetTickCount()+m_timeout;
//---
   do
     {
      leng=m_socket.Readable();
      if(leng)
         rsp_len+=m_socket.Read(m_rxbuf,leng,m_timeout);
      leng=0;
     }
   while(GetTickCount()<timeout_check);
//---
   m_socket.Log("receive size "+IntegerToString(rsp_len),__LINE__,__FUNCTION__);
//---
   int m_rxsize=ArraySize(m_rxbuf);
//---
   if(m_rxsize<3)
      return(0);
//---
   switch((uint)m_rxbuf[1])
     {
      case 126:
         if(m_rxsize<4)
           {
            m_rxsize=0;
           }
         break;
      case 127:
         if(m_rxsize<10)
           {
            m_rxsize=0;
           }
         break;
      default:
         break;
     }
//---
   return(m_rxsize);
  }


int               Readable(void) {  return(fillRxBuffer());}


Read()メソッドは、すべてのフレームが書き込まれるCFrame型の配列を入力として受け取ります。このメソッドは、parse() private関数を使用してバイトデータをデコードし、読みやすくするために正しく編成できるようにします。parse()メソッドは、受信したフレームに関する説明情報をエンコードするヘッダバイトからペイロードを分離します。

//+------------------------------------------------------------------+
//| parse() cleans up raw data buffer discarding unnecessary elements|
//+------------------------------------------------------------------+
bool CWebSocketClient::parse(CFrame &out[])
  {
   uint i,data_len=0,frames=0;
   uint s=0;
   m_total_len=0;
//---
   int shift=0;
   for(i=0; i<(uint)ArraySize(m_rxbuf); i+=(data_len+shift))
     {
      ++frames;
      m_socket.Log("value of frame is "+IntegerToString(frames)+" Value of i is "+IntegerToString(i),__LINE__,__FUNCTION__);
      switch((uint)m_rxbuf[i+1])
        {
         case 126:
            data_len=((uint)m_rxbuf[i+2]<<8)+((uint)m_rxbuf[i+3]);
            shift=4;
            break;
         case 127:
            data_len=((uint)m_rxbuf[i+2]<<56)+((uint)m_rxbuf[i+3]<<48)+((uint)m_rxbuf[i+4]<<40)+
            ((uint)m_rxbuf[i+5]<<32)+((uint)m_rxbuf[i+6]<<24)+((uint)m_rxbuf[i+7]<<16)+
            ((uint)m_rxbuf[i+8]<<8)+((uint)m_rxbuf[i+9]);
            shift=10;
            break;
         default:
            data_len=(uint)m_rxbuf[i+1];
            shift=2;
            break;
        }
      m_total_len+=data_len;
      if(data_len>0)
        {
         if(ArraySize(out)<(int)frames)
           {
            if(ArrayResize(out,frames,1)<=0)
              {
               m_socket.Log("array resize error",__LINE__,__FUNCTION__);
               return(false);
              }
           }
         //---
         if(!out[frames-1].Fill(m_rxbuf,i+shift,data_len))
           {
            m_socket.Log("Error adding new frame",__LINE__,__FUNCTION__);
            return(false);
           }
         //---
         switch((uchar)m_rxbuf[i])
           {
            case 0x1:
               if(out[frames-1].MessageType()==0)
               out[frames-1].SetMessageType(TEXT_FRAME);
               break;
            case 0x2:
               if(out[frames-1].MessageType()==0)
               out[frames-1].SetMessageType(BINARY_FRAME);
               break;
            case 0x80:
            case 0x81:
               if(out[frames-1].MessageType()==0)
               out[frames-1].SetMessageType(TEXT_FRAME);
            case 0x82:
               if(out[frames-1].MessageType()==0)
               out[frames-1].SetMessageType(BINARY_FRAME);
               m_socket.Log("received last frame",__LINE__,__FUNCTION__);
               out[frames-1].SetFinal();
               break;
            case 0x88:
               m_socket.Log("received close frame",__LINE__,__FUNCTION__);
               out[frames-1].SetMessageType(CLOSE_FRAME);
               if(m_wsclient==CONNECTED)
                 {
                  ArrayCopy(m_txbuf,m_rxbuf,0,i+shift,data_len);
                  m_wsclient=CLOSING;
                 }
               break;
            case 0x89:
               m_socket.Log("received ping frame",__LINE__,__FUNCTION__);
               out[frames-1].SetMessageType(PING_FRAME);
               if(m_wsclient==CONNECTED)
                  ArrayCopy(m_txbuf,m_rxbuf,0,i+shift,data_len);
               break;
            case 0x8a:
               m_socket.Log("received pong frame",__LINE__,__FUNCTION__);
               out[frames-1].SetMessageType(PONG_FRAME);
               break;
            default:
               break;
           }
        }
     }
//---  
   return(true);
  }


uint CWebSocketClient::Read(CFrame &out[])
  {
   ClientState();
//---
   if(m_wsclient==0)
     {
      m_socket.Log("invalid ws client handle",__LINE__,__FUNCTION__);
      return(0);
     }
//---
   int rx_size=ArraySize(m_rxbuf);
//---
   if(rx_size<=0)
     {
      m_socket.Log("receive buffer is empty, Make sure to call Readable first",__LINE__,__FUNCTION__);
      return(0);
     }
//---clean up rxbuf
   if(!parse(out))
     {
      ArrayFree(m_rxbuf);
      return(0);
     }
//---
   ArrayFree(m_rxbuf);
//---
   return(m_total_len);
  }


クラスの使用

WebSocketクラスを定義したら、MT5プログラムでどのように使用できるかを見ていきましょう。このクラスを実装するアプリケーションの開発を開始する前に、まず、ターミナル設定で許可されているエンドポイントのリストに、アクセスするリモートサーバのアドレスを入力する必要があります。



WebsocketClient.mqhをインクルードしてから、以下の手順に従ってください。

CWebSocketClient wsc;
  • WebsocketClientインスタンスを宣言します。

接続に関連するすべての送信操作の最大送信サイズを指定する場合は、これが適切なタイミングです。インスタンスの初期化では、m_maxsendsizeは0であり、フレームサイズの制限がないことを示します。

   

wsc.SetMaxSendSize(129); // max size in bytes set
  •      適切な入力パラメータを使用してconnectメソッドを呼び出し、結果を確認します。
if(wsc.Connect(Address,Port,Timeout,usetls,true))
{
 //// 
}

接続が成功した場合は、受信したメッセージの送信または確認を開始できます。以前に準備された配列に使用されるデータ送信メソッドのいずれかを使用してデータを送信できます。

sent=wsc.SendString("string message");
// or 
// prepare and fill arbitrary array[] with data and send
sent=wsc.SendData(array);


または、文字列メッセージを送信するだけの場合は、sendstringメソッドを使用します。

sent=wsc.SendPing("optional message");


オプションでメッセージを添付できるpingをサーバに送信することもできます。サーバにpingを実行した後に応答を待機する場合、pong応答フレームはpingで送信されたものをエコーバックする必要があります。クライアントがサーバからpingを受信した場合も、クライアントは同じことを行う必要があります。

if(wsc.Readable()>0)
 {
  //read message....
  //declare frame object to receive message
  // and pass it to read method.
  CFrame msg_frames[];
  received=wsc.Read(msg_frames);
  Print(msg_frames[0].ToString());
  if(msg_frames[0].IsFinal())
   {
     Print("\n Final frame received");
   }


受信するには、読み取り可能な方法を使用してソケットから読み取り可能なデータを確認します。メソッドが読み取り可能なソケットを示している場合は、フレームタイプのオブジェクト配列を使用してクライアントの読み取りメソッドを呼び出します。次に、WebSocketは、通過したすべてのメッセージフラグメントをオブジェクト配列に書き込みます。ここでは、フレームタイプのメソッドを使用して、フレーム配列の内容をクエリできます。前述のように、受信したフレームの1つがpingフレームである場合は、できるだけ早くpongで応答することをお勧めします。この要件を支援するために、WebSocketクライアントはpingの受信時にpong応答フレームを作成します。ユーザが行う必要があるのは、引数なしでsendpingメソッドを呼び出すことだけです。


受信したフレームの1つがクローズフレームである場合、WebSocketクライアントの状態はCLOSING状態に変わります。これは、サーバがクローズリクエストを送信し、このクライアントへの接続を切断する準備をしていることを意味します。CLOSING状態の場合、送信操作は制限されます。クライアントは、pingフレームの受信と同様に、必須のクローズ応答フレームのみを送信できます。クローズフレームを受信すると、WebSocketクライアントがディスパッチの準備ができたクローズフレームを作成します。

wsc.Close(NORMAL_CLOSE,"good bye");
// can also be called with out any arguments.
// wsc.Close();
 

WebSocketクライアントを使用して接続クローズメソッドを呼び出す場合、サーバに通知する情報がない限り、通常は引数を指定せずにメソッドを呼び出すだけで十分です。その場合、短い終了メッセージとともに、コードを閉じる理由の1つを使用します。このメッセージは強制的に122文字に制限され、それを超えるものは破棄されます。

ローカルWebSocketサーバ

本稿に付属するzipファイルには、テストの目的でエコーサービスを提供するWebSocketサーバが含まれています。サーバはlibwebsocketライブラリを使用して構築されており、ソースコードはgithubからダウンロードできます。他のすべての依存関係はgithubリポジトリで利用できるため、ビルドするにはVisualStudioのみが必要です。

サーバの実行とライブラリのテスト

エコーサーバを実行するには、アプリケーション(exeファイル)をダブルクリックするだけです。サーバが動作を開始するはずです。インストールされているファイアウォールがサーバをブロックする可能性があることに注意し、必要な権限をサーバに付与してください。サーバアプリケーションディレクトリに含まれている付随する.dllファイルが必要であり、サーバがそれらなしではないことを知っておくことも重要です。

アイドルサーバ

WebSocketClientクラスを簡単にテストしましょう。これがプログラム例です。

//+------------------------------------------------------------------+
//|                                         Websocketclient_test.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict
#include<WebSocketClient.mqh>

input string Address="127.0.0.1";
input int    Port   =7681;
input bool   ExtTLS =false;
input int    MaxSize=256;
input int Timeout=5000;


//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
string _msg="For the mql5-program to operate, it must be compiled (Compile button or F7 key). Compilation should"
            "pass without errors (some warnings are possible; they should be analyzed). At this process, an"
            "executable file with the same name and with EX5 extension must be created in the corresponding"
            "directory, terminal_dir\\MQL5\\Experts, terminal_dir\\MQL5\\indicators or terminal_dir\\MQL5\\scripts."
            "This file can be run."
            "Operating features of MQL5 programs are described in the following sections:"
            "- Program running – order of calling predefined event-handlers."
            "- Testing trading strategies – operating features of MQL5 programs in the Strategy Tester."
            "- Client terminal events – description of events, which can be processed in programs."
            "- Call of imported functions – description order, allowed parameters, search details and call agreement"
            "for imported functions."
            "· Runtime errors – getting information about runtime and critical errors."
            "Expert Advisors, custom indicators and scripts are attached to one of opened charts by Drag'n'Drop"
            "method from the Navigator window."
            "For an expert Advisor to stop operating, it should be removed from a chart. To do it select 'Expert'"
            "'list' in chart context menu, then select an Expert Advisor from list and click 'Remove' button."
            "Operation of Expert Advisors is also affected by the state of the 'AutoTrading' button."
            "In order to stop a custom indicator, it should be removed from a chart."
            "Custom indicators and Expert Advisors work until they are explicitly removed from a chart;"
            "information about attached Expert Advisors and Indicators is saved between client terminal sessions."
            "Scripts are executed once and are deleted automatically upon operation completion or change of the"
            "current chart state, or upon client terminal shutdown. After the restart of the client terminal scripts"
            "are not started, because the information about them is not saved."
            "Maximum one Expert Advisor, one script and unlimited number of indicators can operate in one chart."
            "Services do not require to be bound to a chart to work and are designed to perform auxiliary functions."
            "For example, in a service, you can create a custom symbol, open its chart, receive data for it in an"
            "endless loop using the network functions and constantly update it."
            "Each script, each service and each Expert Advisor runs in its own separate thread. All indicators"
            "calculated on one symbol, even if they are attached to different charts, work in the same thread."
            "Thus, all indicators on one symbol share the resources of one thread."
            "All other actions associated with a symbol, like processing of ticks and history synchronization, are"
            "also consistently performed in the same thread with indicators. This means that if an infinite action is"
            "performed in an indicator, all other events associated with its symbol will never be performed."
            "When running an Expert Advisor, make sure that it has an actual trading environment and can access"
            "the history of the required symbol and period, and synchronize data between the terminal and the"
            "server. For all these procedures, the terminal provides a start delay of no more than 5 seconds, after"
            "which the Expert Advisor will be started with available data. Therefore, in case there is no connection"
            "to the server, this may lead to a delay in the start of an Expert Advisor.";
//---
CWebSocketClient wsc;
//---
int sent=-1;
uint received=-1;
//---
// string subject,issuer,serial,thumbprint;
//---
// datetime expiration;
//---
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(2);
//---
   wsc.SetMaxSendSize(MaxSize);
//---
   if(wsc.Connect(Address,Port,Timeout,ExtTLS,true))
     {
      sent=wsc.SendString(_msg);
      //--
      Print("sent data is "+IntegerToString(sent));
      //---
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   Print("Deinit call");
   wsc.Close();

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(wsc.Readable()>0)
     {
      CFrame msg_frames[];
      received=wsc.Read(msg_frames);
      if(received>0)
        {
         int ll=ArraySize(msg_frames);
         Print("number of received frames is "+IntegerToString(ll));
         for(int i=0; i<ll; i++)
           {
            Print(msg_frames[i].ToString());
           }

         if(msg_frames[ll-1].IsFinal())
           {
            Print("\n Final frame received");
            wsc.Close(NORMAL_CLOSE,"good bye");
            ExpertRemove();
           }
        }
     }
   else
     {
      Print("\n Nothing readable in socket");
      if(wsc.ClientState()!=CONNECTED)
        {
         Print("\n Client disconnected");
         ExpertRemove();
        }
     }
  }
//+------------------------------------------------------------------+ 


このエキスパートアドバイザーは、ローカルで実行されているエコーWebSocketサーバに接続し、すぐにかなり大きなメッセージを送信しようとします。EA入力を使用すると、TLSを有効、無効にし、送信サイズを調整して、メッセージの断片化メカニズムがどのように機能するかを確認できます。コードでは、最大メッセージサイズを256に設定しているため、各フレームはそのサイズ以下になります。

onTimer関数では、EAはサーバーから利用可能なメッセージを確認します。受信したメッセージはMT5ターミナルに出力され、WebSocket接続が切断されます。次のOntimerイベントで接続が閉じられると、EAはチャートから自分自身を削除します。 以下はMT5エキスパートタブからの出力です。

ヘッダ出力


データの解析

フレームの受信

構築中のクローズフレーム

そして、以下はWebSocketサーバからの出力です。


サーバスクリーンショット

以下は、サーバーに接続しているときに実行されているプログラムのビデオです。


終わりに

本稿はWebSocketプロトコルの概要から始まり、mql5プログラミング言語のみを使用してMetrader5にWebSocketクライアントを実装する方法について詳しく説明しました。次に、サーバを構築し、それを使用してMT5クライアントをテストしました。ここで説明するツールがお役に立てば幸いです。すべてのソースコードは以下からダウンロードできます。 

以下は添付アーカイブの内容です。

フォルダ 
 内容 説明
MT5zip\server
  echo_websocket_server.exe、websockets.dll、ssleay32.dll、libeay32.dll
 サーバアプリケーションとそれに必要な依存関係ファイル
MT5zip\Mql5\include
Frame.mqh、Socket.mqh、WebsocketClient.mqh
 CFrameクラス、CSocketクラス、CWebsocketクラスのコードをそれぞれ含むファイル
MT5zip\Mql5\Experts Websocketclient_test.mq5  CWebsocketクラスの使用を示すMetaTraderエキスパートアドバイザー



MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/8196

添付されたファイル |
MT5zip.zip (826.64 KB)
トランスダクション・アクティブ機械学習におけるスロープブースト トランスダクション・アクティブ機械学習におけるスロープブースト
本記事では、実データを活用したアクティブな機械学習手法について考察するとともに、その長所と短所について考察していきます. おそらく、いくつかの方法が有用であるとわかるでしょうし、機械学習モデルのアーセナルにインクルードするでしょう. トランスダクションは、サポートベクターマシン(SVM)の共同発明者であるVladimir Vapnik氏が紹介しています.
ニューラルネットワークが簡単に(第7回): 適応的最適化法 ニューラルネットワークが簡単に(第7回): 適応的最適化法
以前の記事では、ネットワーク内のすべてのニューロンに対して同じ学習率を用いてニューラルネットワークをトレーニングするためにストキャスティクススロープ降下法を使用しました。 本論文では、各ニューロンの学習速度を変化させることができる適応学習法に着目します。 その是非についても検討していきたいと思います。
手動チャートおよび取引ツールキット(第II部)チャートグラフィック描画ツール 手動チャートおよび取引ツールキット(第II部)チャートグラフィック描画ツール
これは連載の次の記事で、キーボードショートカットを使用してチャートグラフィックを手動で適用するための便利なライブラリを作成した方法を示します。使用されるツールには、直線とその組み合わせが含まれます。第2部では、第1部で説明した関数を使用して、描画ツールがどのように適用されるかを確認します。ライブラリは、チャート作成タスクを大幅に簡素化する任意のエキスパートアドバイザーまたはインディケーターに接続できます。このソリューションは外部DLLを使用せず、すべてのコマンドは組み込みのMQLツールを使用して実装されます。
アルゴリズム取引から100万ドルを稼ぐ方MQL5.comサービスを使用してください アルゴリズム取引から100万ドルを稼ぐ方MQL5.comサービスを使用してください
トレーダーは皆、最初の百万ドルを稼ぐことを目標に市場を訪れます。過度のリスクと初期予算なしでこれを行う方法は何でしょうか。MQL5サービスは、世界中の開発者やトレーダーにそのような機会を提供します。