English Русский 中文 Deutsch 日本語 Português
preview
WebSocket para MetaTrader 5

WebSocket para MetaTrader 5

MetaTrader 5Ejemplos | 2 marzo 2021, 16:19
1 070 0
Francis Dube
Francis Dube

Introducción

MetaTrader 5 ha ampliado sus prestaciones a lo largo de los años, y ahora ofrece una extensa gama de funciones para los tráders. Una característica destacada es su capacidad para integrarse con varios sistemas y plataformas a pesar del uso de un lenguaje de programación propietario. Esta capacidad resulta muy importante, ya que ofrece a los tráders una gran libertad a la hora de explorar estrategias comerciales potencialmente rentables.

La clave de esta integración reside en su capacidad para aprovechar los protocolos de red modernos más eficientes y fáciles de implementar. Precisamente en este sentido investigaremos la implementación de un cliente websocket para aplicaciones MetaTrader 5 sin utilizar una biblioteca de enlaces dinámicos.

Para comenzar, veamos una breve introducción al protocolo de red websocket.

Introducción a WebSocket

El protocolo WebSocket es un método de comunicación para intercambiar información entre un servidor y un cliente sin vernos obligados a realizar múltiples solicitudes HTTP. Los navegadores y la mayoría de las aplicaciones basadas en la web usan el protocolo WebSocket para ofrecer varios servicios como mensajería instantánea, contenido web dinámico y juegos online multijugador.

¿Para qué se necesita el protocolo WebSocket?

Antes del protocolo WebSocket, los desarrolladores tenían que usar métodos ineficientes y costosos para implementar la comunicación asincrónica entre el servidor y el cliente.

Entre ellos:

  • Sondeo (polling)— es esencialmente un método sincrónico que asume la ejecución continua de solicitudes, incluso si no hay datos para transmitir, lo cual provoca un desperdicio de recursos computacionales.
  • Sondeo largo (long polling)— el cliente realiza relativamente menos solicitudes al servidor, que responde abriendo y manteniendo una conexión hasta que se produzca un intercambio o no se aplique un tiempo de espera.
  • Streaming— este método requiere que el cliente realice una solicitud de datos, el servidor mantendrá la conexión indefinidamente. El principal inconveniente de este método es la utilización generalizada de encabezados HTTP que aumentan el volumen de los datos extraídos.
  • Ajax— es principalmente una tecnología JavaScript y xml de navegador asíncrona, pionera en el contenido web asíncrono. El usuario puede publicar un mensaje en un sitio web y su contenido se mostrará en este casi al instante, sin necesidad de actualizarlo.

Todos los métodos mencionados ofrecen diferentes niveles de intercambio de datos entre el cliente y el servidor, pero, a diferencia del protocolo WebSocket, tienen las siguientes desventajas:

  • como hemos dicho anteriormente, estos métodos ofrecen diferentes niveles de transferencia de datos asíncrona. De hecho, en el mejor de los casos, hablamos de un tipo de comunicación semidúplex. Esto significa que cada participante en el intercambio solo puede responder cuando el otro haya terminado.
  • los métodos mencionados usan ampliamente encabezados http. Combinado con la frecuencia de las solicitudes HTTP, esto provoca un uso excesivo de datos, y puede suponer una desventaja si el ancho de banda juega un papel determinante.
  • el coste asociado con el rendimiento. Mantener la conexión con los servidores durante largos periodos temporales cuando no resulta necesaria, o enviar datos a clientes que pueden haberse marchado, resulta una pérdida de recursos para las grandes empresas y aumenta el coste de ejecución de los servidores.

Particularidades de WebSocket

WebSocket es un protocolo basado en TCP que se puede ampliar para admitir otras aplicaciones o protocolos secundarios. Como está basado en TCP, puede ejecutarse en los puertos HTTP estándar 80, 443 y tiene un esquema de localizador de URL similar. Las direcciones del servidor WebSocket tienen el prefijo ws o wss en lugar de http, pero poseen la misma estructura de URL que http, por ejemplo: 

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

                   

Principios de funcionamiento del protocolo Websocket

Para comprender cómo podemos implementar un cliente WebSocket en MQL5, debemos familiarizarnos con los conceptos básicos de la organización de una red informática. El protocolo WebSocket se parece a HTTP en que los encabezados se usan en las solicitudes de cliente al servidor. El protocolo WebSocket también utiliza encabezados. Su rasgo distintivo es que estas solicitudes solo necesitan la configuración o inicialización del websocket. El cliente realiza una solicitud http, que luego cambia a WebSocket.

Este proceso se conoce como handshake, saludo inicial o establecimiento de comunicación. El cambio de protocolos tiene lugar solo si la solicitud http inicial al servidor contiene un encabezado o encabezados específicos. El servidor deberá responder en consecuencia, confirmando el deseo de establecer una conexión websocket. La información sobre la naturaleza de los encabezados especiales y sobre las posibles respuestas del servidor está documentada en RFC 6455.

Después de instalar un websocket, ya no será necesario usar solicitudes http. Ésta es la diferencia entre los protocolos en cuanto a su funcionamiento. Al intercambiar los datos mediante el protocolo WebSocket, se adopta un formato diferente. Este formato tiene un mayor nivel de optimización y usa muchos menos bits sin procesar en comparación con una solicitud http. El formato usado se llama protocolo de sincronización de frames (framing protocol). Los datos que se intercambian en una transacción entre hosts se denominan tramas o frames.

Cada frame es una secuencia de bits dispuestos de una forma determinada que se ajusta al protocolo de frames estipulado en RFC 6455. Cada frame del websocket contiene bits que definen el código de operación, el tamaño de los datos y los datos en sí. El protocolo también define cómo se organizan esos bits y se empaquetan finalmente dentro del frame. El código de operación es simplemente un valor numérico reservado que se utiliza para clasificar un frame.

Para los websockets, los códigos de operación básicos se definen de la forma siguiente:

0 - frame de continuación: datos incompletos, se esperan más frames. Esta característica posibilita la fragmentación de frames. Los datos se dividen en bloques, que se empaquetan en diferentes frames.

1 — frame de texto: los datos suponen un texto.

2 — frame binario: los datos están en formato binario.

8 — frame de cierre: cada uno de los puntos finales intenta cerrar una conexión establecida. Este tipo de frame se denomina frame de control. Tiene su propio sentido y no siempre contiene datos.

9 — frame de ping (frame de comprobación de conexión): frame de control para determinar si el punto final está conectado

10 — frame de pong: frame de respuesta, cuando el punto final recibe un frame de ping. En este caso, el destinatario deberá enviar lo antes posible el frame de pong requerido. En general, basta con mostrar los datos contenidos en el frame de ping.

Estos suponen códigos de operación básicos que cualquier websocket debería admitir. El protocolo permite un aumento en la cantidad de códigos posibles para API o protocolos secundarios basados ​​en WebSocket.

El último aspecto importante relacionado con los frames es el enmascaramiento. RFC 6455 dicta que todos los frames enviados desde el cliente al servidor deben estar enmascarados. El enmascaramiento es la principal medida de seguridad del protocolo WebSocket. Los datos se cifran con un valor (clave) de 4 bytes generado aleatoriamente utilizando un algoritmo predefinido. El algoritmo se describe en RFC 6455. Todas los frames enviados por el cliente (incluidos los frames fragmentados) deben usar una clave aleatoria generada especialmente para ellos.

Para obtener más información, consulte la documentación del estándar RFC6455. Con estos conocimientos le resultará mucho más fácil entender la implementación del código.


Cliente WebSocket en MQL5: descripción general de la biblioteca

Para empezar, dividiremos el código en tres clases.

CSocket encapsula las funciones de red de la API de MQL5.

CFrame es un frame de conexión web que se usa principalmente para descodificar los frames recibidos de los servidores.

CWebSocketClient : cliente 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);
     }
//---
  }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

La clase de socket define una estructura CERT que encapsula los datos sobre el certificado del servidor.

  •  cert_subject es el nombre del propietario del certificado
  •  cert_issuer es el nombre del emisor del certificado
  •  cert_serial es el número de serie del certificado
  •  cert_thumbprint es el hash SHA-1 del certificado
  •  cert_expiry es la fecha de caducidad del certificado

Métodos que obtienen las propiedades privadas:

SocketID — retorna un identificador de socket para un socket creado correctamente.
Address — retorna en forma de cadena la dirección remota a la que está conectado el socket.
Port — retorna el puerto remoto al que está conectado el socket activo.
IsSecure — retorna true o false dependiendo de si la TLS está activada o no en el socket
RxTimeouto — retorna el tiempo de espera establecido en milisegundos para leer desde un socket.
TxTimeout — retorna el tiempo de espera establecido en milisegundos para escribir en el socket.
ServerCertificate — retorna la información sobre el certificado del servidor al que está conectado el socket.
SocketsInUse — retorna el número total de sockets actualmente utilizados en un programa.

Métodos para establecer las propiedades privadas:

SetTimeouts — establece los tiempos de espera para leer y escribir en un socket en milisegundos.
Métodos generales de los sockets de trabajo
Log — es un método de servicio para registrar la actividad del socket. Para enviar mensajes al registro del terminal, debemos configurar el registro al inicializar un socket usando el método Open.
Open — es un método para establecer conexión con el servidor remoto en el que se crea un nuevo socket.
Close — es un método que sirve para desconectarse de un servidor remoto y desinicializar un socket.
Readable — retorna el número de bytes disponibles para leer en el socket
Writable — consulta si el socket está disponible para cualquier operación de envío.
IsConnected — comprueba si hay una conexión de socket activa.
Read — lee los datos de un socket.
Send — es un método que sirve para realizar operaciones de envío en un socket activo.

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);
  }

La clase de frame define la enumeración ENUM_FRAME_TYPE, que describe los distintos tipos de frames documentados por el protocolo WebSocket.

Las instancias de la clase CFrame suponen un frame recibido del servidor. Esto significa que un mensaje completo puede constar de un conjunto de frames. La clase nos permite consultar varias características de cada frame, incluidos los valores de bytes individuales que componen el frame. 

El método Size retorna el tamaño del frame en bytes. Como la clase utiliza una matriz sin firmar como contenedor para el frame, el método simplemente retorna el tamaño de la matriz básica. 

El método MessageType retorna el tipo de frame como tipo ENUM_FRAME_TYPE.

El método IsFinal comprueba si el frame es el último, lo cual indica que todos los datos recibidos deben considerarse como un todo. Esto nos permite distinguir un mensaje fragmentado (y por consiguiente incompleto) de uno completo.

operator[] — la sobrecarga del operador subscript nos permite recuperar cualquier elemento del frame en formato de matriz.

La clase CFrame se usará en el cliente WebSocket al leer desde un objeto CSocket. Para llenar el frame, se usan los métodos Add y Fill, que nos permiten rellenar un frame con un elemento aparte o con la matriz correspondiente.

Podemos utilizar el método de servicio Reset para borrar un frame y restablecer sus propiedades, mientras que el método ToString supone una herramienta útil para convertir el contenido de un frame en un valor de cadena conocido.

CWebSocketClient

La clase contiene constantes implementadas como #defines. Los símbolos con el prefijo HEADER se relacionan con los campos de encabezado http necesarios para crear el saludo inicial de apertura. Un GUID es un identificador internacional único usado por el protocolo de websocket del lado del servidor al generar una parte de los encabezados de respuesta. La clase lo usa para confirmar y demostrar la corrección del proceso de comunicación, pero, en esencia, no es necesario. El cliente solo necesita verificar la presencia del campo de encabezado |Sec-WebSocket-Accept| para confirmar el saludo inicial exitoso.

#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"


El tipo de enumeración ENUM_STATUS_CLOSE_CODE enumera los códigos de cierre que se pueden enviar o recibir junto con el frame de cierre. La enumeración ENUM_WEBSOCKET_CLIENT_STATE representa los diferentes estados que puede adoptar un websocket.

Closed — es el estado inicial antes de que se asigne un socket al cliente o después de que el cliente haya detenido la conexión y se haya cerrado el socket básico.

Cuando se establece la primera conexión antes de enviar el saludo inicial de apertura (encabezado), el cliente se encuentra en un estado conectado. El cliente se conecta después de enviar el saludo inicial y recibir una respuesta que permita la utilización del protocolo webSocket.

El estado cerrado tiene lugar cuando el cliente recibe un frame de cierre por primera vez desde que el cliente se ha inicializado, o el cliente envía el primer frame de cierre para notificar al servidor que está interrumpiendo la conexión. Cuando está cerrado, el cliente solo puede enviar al servidor frames de cierre. Recuerde que en el estado de cierre es posible que el servidor no responda, ya que no está obligado a seguir ofreciendo servicio después de haber enviado o recibido una notificación de cierre.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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
  };


El método ClientState extrae una propiedad que determina el estado de la conexión de cualquier cliente 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() se usa para configurar la fragmentación del frame de un cliente WebSocket. El método establece el tamaño máximo en bytes para un frame enviado desde el cliente al servidor. Esto ofrece al cliente flexibilidad de uso con cualquier API que imponga límites de tamaño de frame.

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


El método Connect se usa para establecer una conexión WebSocket. El parámetro secure es un valor booleano que sirve para configurar un websocket con o sin TLS. El método primero llama al método open de la clase CSocket para establecer una conexión TCP inicial. Si tiene éxito, el estado del WebSocket se cambiará a "conectando", después de lo cual entrará en juego el método auxiliar de actualización. Sus responsabilidades incluyen la creación del encabezado http necesario para cambiar al protocolo WebSocket. El estado del websocket se comprueba al salir de la función.

//+------------------------------------------------------------------+
//| 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);
  }

El método ClientClose se usa para cerrar o terminar la conexión. Tiene dos parámetros predeterminados: el código de cierre y el cuerpo del mensaje que se enviará al servidor como un frame de cierre. El cuerpo del mensaje se cortará si supera el límite de 122 caracteres. Según las especificaciones de WebSocket, si un punto final (servidor o cliente) recibe primero un frame de cierre, el receptor deberá responder y el remitente deberá esperar una respuesta como confirmación de la solicitud de cierre. Como podemos ver en el código de ClientClose, tras enviar el frame de cierre, el socket TCP básico se cierra sin esperar una respuesta, incluso si el cliente ha iniciado el cierre. La espera de una respuesta en esta etapa del ciclo de vida del cliente parece una pérdida de recursos, por eso no ha sido implementada.

//+------------------------------------------------------------------+
//| 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);
//---
  }


Al enviar datos aleatorios al servidor, podemos elegir uno de dos métodos. SendString toma una cadena como datos de entrada, mientras que SendData toma una matriz.

SendPing y SendPong son métodos especiales para enviar pings y pongs. Ambos admiten un cuerpo de mensaje opcional al que se aplica un límite de 122 caracteres.


Todos los métodos públicos de envío empaquetan los datos de entrada correspondientes en la matriz m_txbuff. El método de envío privado establece el tipo de frame y usa filltxbuffer() para activar la fragmentación del mensaje según el valor de la propiedad m_maxsendsize. FillTxbuffer() prepara un solo frame empaquetándolo en la matriz m_send. Una vez se ha preparado m_send, se envía al servidor. Todo esto se hace en un ciclo hasta que se haya enviado todo el contenido de 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);
  }

Los datos que se envían al cliente se guardan en un búfer en la matriz m_rxbuff usando el método privado fillrxbuffer() siempre que se llama al método público Readable(). Retorna el tamaño de la matriz m_rxbuff que indica la disponibilidad de los datos recuperados llamando al método Read().

//+------------------------------------------------------------------+
//| 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());}


El método Read() adopta como datos de entrada una matriz de tipo CFrame, en la que se escribirán todos los frames. El método usa una función parse() privada para decodificar los datos de bytes de forma que puedan organizarse correctamente para facilitar la lectura. El método parse() separa la carga útil de los bytes del encabezado que codifican la información descriptiva sobre los frames recién recibidos.

//+------------------------------------------------------------------+
//| 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);
  }


Usando la clase

Ahora que nuestra clase WebSocket está definida, veamos cómo se puede utilizar en los programas de MetaTrader 5. Antes de comenzar a desarrollar aplicaciones que implementen esta clase, vamos a crear la primera dirección del servidor remoto que queremos ver en la lista de puntos finales permitidos en la configuración del terminal.



No se olvide de activar WebsocketClient.mqh, siguiendo luego los siguientes pasos:

CWebSocketClient wsc;
  • declare la instancia o instancias de WebsocketClient

Si desea especificar un tamaño máximo de envío para todas las operaciones de envío relacionadas con la conexión, ahora es el momento de hacerlo. Al inicializar la instancia, m_maxsendsize es igual a 0, lo cual indica que no hay límite de tamaño de frame.

   

wsc.SetMaxSendSize(129); // max size in bytes set
  •      Llame al método de conexión con los parámetros de entrada correspondientes y verifique el resultado.
if(wsc.Connect(Address,Port,Timeout,usetls,true))
{
 //// 
}

Si la conexión ha tenido éxito, podrá comenzar a enviar o comprobar los mensajes recibidos. Puede enviar los datos usando cualquier método de envío que se aplique a las matrices previamente preparadas.

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


Si solo desea enviar un mensaje de cadena, utilice el método sendtring.

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


También podrá enviar una solicitud de eco al servidor, que, si así lo desea, podrá ir acompañada de un mensaje. Al esperar una respuesta tras comprobar la conexión con el servidor, el frame de respuesta pong deberá reflejar lo que se ha enviado con la ayuda del ping. El cliente deberá hacer lo proio si recibe una solicitud de eco del servidor.

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");
   }


Para obtenerlas, compruebe los datos disponibles para leerlos desde el socket con un método legible. Si el método indica un conector legible, llamaremos al método de lectura del cliente con una matriz de objetos de tipo Frame. El websocket luego escribirá todos los fragmentos de los mensajes recibidos en una matriz de objetos. Aquí podemos usar métodos de tipo frame para consultar el contenido de una matriz de frames. Como hemos dicho anteriormente, si una de los frames recibidos es un frame de ping, recomendamos responder con un frame de pong lo antes posible. Para cumplir con este requisito, el cliente WebSocket creará un frame pong de respuesta cuando reciba cualquier ping. Todo lo que tiene que hacer el usuario es llamar al método para enviar un ping sin ningún argumento.


Si uno de los frames recibidos es un cierre, el estado del cliente cambiará al estado de cierre de WebSocket. Esto significa que el servidor ha enviado una solicitud para cerrar y se está preparando para cortar la conexión con el cliente. En el estado cerrado, las operaciones de envío son limitadas. El cliente solo puede enviar un frame de respuesta para el cierre obligatorio. Al igual que sucede con la recepción del frame de ping, recibir un frame de cierre indica que el cliente WebSocket está creando un frame de cierre listo para enviar.

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

Cuando esto se haga usando un cliente WebSocket, llamaremos al método para cerrar la conexión. En general, basta con llamar al método sin especificar ningún argumento, a menos que haya algo sobre lo que deseemos notificar al servidor. En este caso, usaremos uno de los motivos del código de cierre junto con un breve mensaje de cierre. Este mensaje se verá forzosamente limitado a 122 caracteres. Los caracteres que superen el límite serán descartados.

Servidor WebSocket local

Con propósitos ilustrativos, el archivo zip adjunto incluye un servidor WebSocket que ofrece la capacidad de enviar solicitudes de eco. El servidor se construye usando la biblioteca libwebsocket. El código fuente se puede descargar en github. Solo se necesita Visual Studio para la creación. Todo lo demás está disponible en github.

Iniciando el servidor y poniendo a prueba la biblioteca

Para iniciar el servidor de eco, clique dos veces en el archivo exe de la aplicación. El servidor debería ponerse en funcionamiento. El firewall instalado puede bloquear el servidor, así que concédale los permisos necesarios. Serán necesarios algunos archivos .dll complementarios que se encuentran en el directorio de la aplicación del servidor. El servidor no podrá funcionar sin ellos.

Servidor inactivo

Vamos a poner rápidamente a prueba la clase WebSocketClient. Aquí tenemos un programa de muestra.

//+------------------------------------------------------------------+
//|                                         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();
        }
     }
  }
//+------------------------------------------------------------------+ 


El asesor se conecta al servidor de eco del WebSocket local e inmediatamente trata de enviar un mensaje bastante grande. Los datos de entrada del asesor nos permiten activar o desactivar la TLS, y también configurar el tamaño del envío para ver cómo funciona el mecanismo de fragmentación de mensajes. En el código, establecemos el tamaño máximo de mensaje en 256, por lo que cada frame tendrá ese tamaño o menos.

El asesor busca los mensajes del servidor en la función onTimer. El mensaje recibido se muestra en el terminal MetaTrader 5; después de ello, se interrumpe la conexión con el websocket. Con el próximo evento de Ontimer, si se cierra la conexión, el asesor será eliminado del gráfico. Esto es lo que muestra la pestaña de expertos en MetaTrader 5.

Muestra de encabezados


Análisis de datos

Recibiendo frames

Formación del frame de cierre

Mensajes del servidor WebSocket.


Captura de pantalla del servidor

A continuación, le mostramos un vídeo de un programa que funciona al conectarse al servidor.


Conclusión

Este artículo comenzó con una descripción general rápida del protocolo WebSocket. Luego describimos con detalle cómo podemos implementar WebSocket en MetaTrader 5 usando exclusivamente el lenguaje de programación MQL5. A conitnuación, creamos un servidor que utilizamos para poner a prueba nuestro cliente MetaTrader 5. Esperamos que las herramientas descritas aquí le resulten útiles. El código fuente al completo está disponible para descargar un poco más abajo. 

Contenido del archivo adjunto

Carpeta 
 Contenido Descripción
MT5zip\server
echo_websocket_server.exe, websockets.dll,ssleay32.dll,libeay32.dll
 Aplicación de servidor junto con los componentes necesarios
MT5zip\Mql5\include
Frame.mqh, Socket.mqh, WebsocketClient.mqh
 Archivos include que contienen el código para las clases CFrame, CSocket y CWebsocket
MT5zip\Mql5\Experts Websocketclient_test.mq5  Asesor experto de MetaTrader que muestra la utilización de la clase CWebsocket



Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/8196

Archivos adjuntos |
MT5zip.zip (826.64 KB)
Perceptrón Multicapa y Algoritmo de Retropropagación Perceptrón Multicapa y Algoritmo de Retropropagación
Recientemente, al aumentar la popularidad de estos dos métodos, se han desarrollado tantas bibliotecas en Matlab, R, Python, C++, etc., que reciben el conjunto de entrenamiento como entrada y construyen automáticamente una red neuronal apropiada para el supuesto problema. Vamos a entender cómo funciona un tipo básico de red neural, (perceptrón de una sola neurona y perceptrón multicapa), y un fascinante algoritmo encargado del aprendizaje de la red, (gradiente descendente y retropropagación). Estos modelos de red servirán como base para los modelos más complejos que existen hoy en día.
Aproximación por fuerza bruta a la búsqueda de patrones (Parte II): Inmersión Aproximación por fuerza bruta a la búsqueda de patrones (Parte II): Inmersión
En el presente artículo, continuaremos con el tema de la fuerza bruta. Intentaremos destacar mejor los patrones con la ayuda de la nueva versión mejorada de nuestro programa y trataremos de encontrar la diferencia en la estabilidad usando distintos segmentos temporales y diferentes marcos temporales para las cotizaciones.
Redes neuronales: así de sencillo (Parte 8): Mecanismos de atención Redes neuronales: así de sencillo (Parte 8): Mecanismos de atención
En artículos anteriores, ya hemos puesto a prueba diferentes variantes para organizar las redes neuronales, incluyendo las redes convolucionales, adoptadas de algoritmos de procesamiento de imágenes. En el presente artículo, les proponemos analizar los mecanismos de atención, cuya aparición impulsó el desarrollo de los modelos de lenguaje.
Remuestreo avanzado y selección de modelos CatBoost con el método de fuerza bruta Remuestreo avanzado y selección de modelos CatBoost con el método de fuerza bruta
Este artículo describe uno de los posibles enfoques respecto a la transformación de datos para mejorar las capacidades generalizadoras del modelo, y también analiza la iteración sobre los modelos CatBoost y la elección del mejor de ellos.