English Русский 中文 Español Deutsch 日本語
preview
WebSocket para MetaTrader 5

WebSocket para MetaTrader 5

MetaTrader 5Exemplos | 10 março 2021, 10:54
2 096 0
Francis Dube
Francis Dube

Introdução

Com o passar dos anos, a funcionalidade do MetaTrader 5 se expandiu significativamente. Uma característica distintiva da plataforma é a capacidade de integração com vários sistemas, apesar da linguagem de programação de marca. Esse recurso é muito importante, pois dá aos traders uma grande margem de manobra ao explorar estratégias de negociação potencialmente lucrativas.

Um elemento-chave da integração é a capacidade de tirar proveito dos protocolos de rede modernos que são mais eficientes e fáceis de implementar. É nesse sentido que consideraremos a implementação do cliente WebSocket para aplicativos MetaTrader 5 sem usar uma biblioteca de link dinâmico.

Primeiro, vamos descrever resumidamente os fundamentos do protocolo de rede WebSocket.

Introdução ao WebSocket

O protocolo WebSocket é um método de comunicação para trocar informações entre o servidor e o cliente sem ter que fazer várias consultas HTTP. Os navegadores e a maioria dos aplicativos baseados na web usam o protocolo WebSocket para fornecer vários serviços, como mensagens instantâneas, conteúdo dinâmico da web e jogos multijogador online.

Por que precisamos do protocolo WebSocket?

Antes do advento do protocolo WebSocket, os desenvolvedores tinham que usar métodos ineficientes e caros para a comunicação assíncrona entre o servidor e o cliente.

Entre eles estão:

  • Sondagem (polling) é, na verdade, um método síncrono, que pressupõe a execução contínua de solicitações, mesmo que os dados a serem transferidos não estejam disponíveis, o que leva a um desperdício de recursos computacionais.
  • Sondagem longa (long polling), aqui o cliente faz relativamente menos consultas ao servidor, que responde abrindo e mantendo uma conexão até que ocorra uma troca ou um tempo limite apareça.
  • Transmissão (streaming), este método requer que o cliente faça uma consulta de dados de tal modo que o servidor mantem a conexão indefinidamente. A principal desvantagem desse método é o uso generalizado de cabeçalhos HTTP, que aumentam o tamanho dos dados recuperados.
  • Ajax é principalmente a tecnologia de navegador de JavaScript assíncrono e xml, que estabeleceu a base para conteúdo da web assíncrono. Você pode postar uma mensagem num site e seu conteúdo aparecerá numa página da web quase que instantaneamente, sem a necessidade de atualizá-lo.

Todos os métodos acima permitem diferentes níveis de troca de dados entre o cliente e o servidor, mas, ao contrário do protocolo WebSocket, eles têm as seguintes desvantagens:

  • Conforme mencionado anteriormente, esses métodos fornecem diferentes níveis de transferência de dados assíncrona. Na verdade, estamos lidando, na melhor das hipóteses, com um tipo de comunicação half-duplex. Isso significa que cada participante da troca deve esperar até que o outro termine para poder responder.
  • Os métodos listados fazem uso extensivo de cabeçalhos http. Combinado com a frequência de consultas HTTP, isso leva ao uso excessivo de dados. Isso pode ser uma desvantagem se a largura de banda for crítica.
  • Custo associado ao desempenho. Manter a conectividade com servidores por longos períodos quando você não precisa, ou enviar dados para clientes que podem ter saído, é um desperdício de recursos para grandes empresas e aumenta o custo de funcionamento dos servidores.

Particularidades do WebSocket

WebSocket é um protocolo baseado em TCP que pode ser estendido para oferecer suporte a outros aplicativos ou protocolos filhos. Como é baseado em TCP, ele pode ser executado nas portas HTTP padrão 80, 443 e tem um esquema de localização de URL semelhante. Os endereços do servidor WebSocket são prefixados com ws ou wss em vez de http, mas têm a mesma estrutura de url que http, por exemplo: 

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

                   

Como funciona o protocolo Websocket

Para entender como podemos implementar um cliente WebSocket em MQL5, precisamos nos familiarizar com os princípios básicos da criação de uma rede de computação. O protocolo WebSocket é semelhante ao HTTP no sentido de que os cabeçalhos são usados nas consultas do cliente ao servidor. O protocolo WebSocket também usa cabeçalhos. Sua característica distintiva é que tais consultas são necessárias apenas para configurar ou inicializar um WebSocket. O cliente faz uma consulta http, que muda para WebSocket.

Esse processo é chamado de aperto de mão (handshake). A substituição de protocolo ocorre apenas se a consulta http original contiver um cabeçalho ou cabeçalhos específicos. O servidor deve então responder apropriadamente, confirmando que deseja estabelecer uma conexão WebSocket. As informações sobre cabeçalhos especiais e possíveis respostas do servidor estão documentadas em RFC 6455.

Após instalar o WebSocket, você não precisa mais usar consultas http. Essa é a diferença entre os protocolos a nível de operação. Ao trocar dados usando o protocolo WebSocket, um formato diferente é adotado. Esse formato é mais otimizado e usa muito menos bits brutos em comparação com a consulta http. O formato usado é chamado de protocolo de enquadramento (framing protocol). Os dados trocados numa transação entre hosts são chamados de quadro ou frame.

Cada quadro é uma sequência de bits, ordenados de acordo com RFC 6455. Cada quadro do WebSocket contém bits que definem o opcode, o tamanho dos dados e os próprios dados. O protocolo também define como esses bits são localizados e empacotados no quadro. Um opcode é simplesmente um valor numérico reservado para classificar um quadro.

Os opcodes básicos para websockets são definidos da seguinte forma:

0 — quadro de continuação: dados incompletos, sãos esperados os quadros seguintes. Esta função fornece fragmentação de quadro. Os dados são divididos em blocos, que são compactados em quadros diferentes.

1 — quadro de texto: dados como texto.

2 — quadro binário: dados em forma binária.

8 — quadro de fechamento: cada um dos terminais pretende fechar a conexão estabelecida. Este tipo de quadro é denominado quadro de controle. É significativo por si só e nem sempre contém dados.

9 — quadro de ping: quadro de controle para determinar se o ponto final está conectado.

10 — quadro de pong: quadro de resposta quando o endpoint recebe um quadro de ping. Nesse caso, o destinatário deve enviar o quadro de pong necessário o mais rápido possível. Normalmente, basta exibir os dados contidos no quadro de ping.

Estes são opcodes básicos que qualquer WebSocket deve suportar. O protocolo permite aumentar o número de códigos possíveis para APIs ou protocolos filhos baseados em WebSocket.

O último aspecto importante em relação aos quadros é o mascaramento. O RFC 6455 determina que todos os quadros enviados desde o cliente para o servidor devem ser mascarados. O mascaramento é a principal medida de segurança para o protocolo WebSocket. Os dados são criptografados com um valor de 4 bytes gerado aleatoriamente (chave) usando um algoritmo predefinido. O algoritmo é descrito em RFC 6455. Todos os quadros enviados pelo cliente (incluindo quadros fragmentados) devem usar uma chave aleatória gerada especialmente para eles.

Para obter mais informações, consulte a documentação RFC6455. Acho que com esse conhecimento será muito mais fácil entender a implementação do código.


Cliente WebSocket em MQL5 — visão geral da biblioteca

Para começar, o código será dividido em três classes.

CSocket inicializa as funções de rede da API MQL5.

CFrame é um quadro de WebSocket que é usado principalmente para descriptografar quadros recebidos de servidores.

CWebSocketClient é o 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);
     }
//---
  }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

A classe de soquete define uma estrutura CERT que encapsula os dados do certificado do servidor.

  •  cert_subject — nome do proprietário do certificado
  •  cert_issuer — nome do emissor do certificado
  •  cert_serial — número de série do certificado
  •  cert_thumbprint — hash SHA-1 do certificado
  •  cert_expiry — data de expiração do certificado

Métodos que obtêm propriedades privadas:

SocketID — retorna um identificador de soquete para um soquete criado com sucesso.
Address — retorna o endereço remoto ao qual o soquete está conectado, como uma string.
Port — retorna a porta remota à qual o soquete ativo está conectado.
IsSecure — retorna true ou false dependendo de se o protocolo TLS está habilitado no soquete ou não.
RxTimeout — retorna o tempo limite definido em milissegundos para leitura desde um soquete.
TxTimeout — retorna o tempo limite especificado em milissegundos para gravar no soquete.
ServerCertificate — retorna informações sobre o certificado do servidor ao qual o soquete está conectado.
SocketsInUse — retorna o número total de soquetes atualmente em uso num programa.

Métodos para definir propriedades privadas:

SetTimeouts — define o tempo limite para leitura e gravação num soquete em milissegundos.
Métodos comuns de soquetes de trabalho
Log — método de serviço para registrar a atividade do soquete. Para enviar mensagens para o log do terminal, precisamos definir o log ao inicializar um soquete usando o método Open.
Open — método para estabelecer uma conexão com um servidor remoto, com o qual um novo soquete é criado.
Close — método para se desconectar de um servidor remoto e desinicializar um soquete.
Readable — retorna o número de bytes disponíveis para leitura no soquete
Writable — pergunta se o soquete está disponível para qualquer operação de envio.
IsConnected — verifica se a conexão de soquete está ativa.
Read — lê dados de um socket.
Send — método para executar operações de envio num soquete ativo.

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

A classe de quadro define a enumeração ENUM_FRAME_TYPE, que descreve os vários tipos de quadro documentados pelo protocolo WebSocket.

As instâncias da classe CFrame representam um quadro recebido do servidor. Isso significa que uma mensagem completa pode consistir num conjunto de quadros. A classe permite consultar várias características de cada quadro, incluindo os valores de byte individuais que compõem o quadro. 

O método Size retorna o tamanho do quadro em bytes. Como a classe usa uma matriz sem sinal como contêiner para o quadro, o método simplesmente retorna o tamanho da matriz subjacente. 

O método MessageType retorna o tipo de quadro como tipo ENUM_FRAME_TYPE.

O método IsFinal verifica se o quadro é o último, o que significa que todos os dados recebidos devem ser considerados como um todo. Isso permite fazer a distinção entre uma mensagem fragmentada e, portanto, incompleta, de uma mensagem completa.

operador[] é a sobrecarga de operador subscript permite que você recupere qualquer elemento do quadro no formato de matriz.

A classe CFrame será usada no cliente WebSocket ao ler desde um objeto CSocket. Para preencher o quadro, são usados os métodos Add e Fill, que permitem preencher o quadro com um elemento separado ou com uma matriz correspondente.

O método de serviço Reset pode ser usado para limpar um quadro e redefinir suas propriedades, enquanto o método ToString é uma ferramenta útil para converter o conteúdo de um quadro num valor de string.

CWebSocketClient

A classe contém constantes implementadas como #defines. Os caracteres prefixados com HEADER são associados aos campos de cabeçalho http necessários para criar o handshake de abertura. Um GUID é um identificador exclusivo internacionalmente usado pelo protocolo WebSocket do lado do servidor ao gerar uma parte dos cabeçalhos de resposta. A classe o utiliza para validar e demonstrar que o processo de comunicação está certo, mas, na verdade, isso não é necessário. O cliente só precisa verificar a presença do campo de cabeçalho |Sec-WebSocket-Accept| para confirmar um handshake bem-sucedido.

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


O tipo de enumeração ENUM_STATUS_CLOSE_CODE enumera os códigos de fechamento que podem ser enviados ou recebidos junto com o quadro de fechamento. A enumeração ENUM_WEBSOCKET_CLIENT_STATE exibe os vários estados que um WebSocket pode adotar.

Closed é o estado inicial antes de qualquer soquete ser alocado ao cliente ou depois que o cliente interrompe a conexão e o soquete subjacente é fechado.

Quando a conexão inicial é estabelecida antes de enviar o handshake de abertura (cabeçalho), o cliente está num estado de conexão. O cliente se conecta após enviar o handshake de abertura e receber uma resposta permitindo o uso do protocolo WebSocket.

Um estado de fechamento ocorre quando o cliente recebe um quadro de fechamento pela primeira vez desde a inicialização do cliente ou o cliente envia o primeiro quadro de fechamento para notificar o servidor de que está interrompendo a conexão. No estado de fechamento, o cliente só pode enviar quadros de desligamento ao servidor. Lembre-se de que num estado de fechamento, o servidor pode não responder, pois não é necessário continuar a manutenção após enviar ou receber uma notificação de desligamento.

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


O método ClientState recupera uma propriedade que determina o estado da conexão de qualquer 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() é usado para configurar a fragmentação do quadro do cliente WebSocket. O método define o tamanho máximo em bytes para um quadro enviado do cliente ao servidor. Isso fornece ao cliente a flexibilidade de usar com qualquer API que imponha limites de tamanho de quadro.

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


O método Connect é usado para estabelecer uma conexão WebSocket. O parâmetro secure é um valor booleano para configurar um WebSocket com ou sem TLS. O método primeiro chama o método open da classe CSocket para estabelecer uma conexão TCP inicial. Se for bem-sucedida, o estado do WebSocket será alterado para "conectando", após o qual o método auxiliar de atualização entra em ação. Ele é encarregado, entre outras coisas, da criação do cabeçalho http necessário para alternar para o protocolo WebSocket. O estado do WebSocket é verificado quando a função é encerrada.

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

O método ClientClose é usado para fechar ou encerrar a conexão. Possui dois parâmetros padrão: o código de fechamento e o corpo da mensagem que será enviada ao servidor como um quadro de fechamento. O corpo da mensagem será truncado se exceder o limite de 122 caracteres. De acordo com a especificação WebSocket, se um terminal (servidor ou cliente) receber primeiro um quadro de fechamento, o receptor deverá responder e o remetente deverá aguardar uma resposta como uma confirmação da solicitação de fechamento. Como você pode ver no código ClientClose, após enviar o quadro de fechamento, o soquete TCP subjacente é fechado sem esperar por uma resposta, mesmo que o fechamento tenha sido iniciado pelo cliente. Esperar por uma resposta neste estágio do ciclo de vida do cliente parece um desperdício de recursos, por isso não foi implementado.

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


Ao enviar dados arbitrários para o servidor, você pode escolher um dos dois métodos. Como dados de entrada, SendData recebe uma matriz, enquanto SendString, uma string.

SendPing e SendPong são métodos especiais para enviar pings e pongs. Ambos permitem um corpo de mensagem opcional ao qual se aplica o limite de 122 caracteres.


Todos os métodos de envio público empacotam a entrada correspondente na matriz m_txbuff. O método de envio privado define o tipo de quadro e usa filltxbuffer() para habilitar a fragmentação da mensagem com base no valor da propriedade m_maxsendsize. FillTxbuffer() prepara um único quadro empacotando-o na matriz m_send. Assim que o m_send estiver pronto, ele é enviado ao servidor. Tudo isso é feito num loop até que todo o conteúdo de m_txbuffer tenha sido enviado.

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

Todos os dados enviados ao cliente são armazenados em buffer na matriz m_rxbuff pelo método privado fillrxbuffer() sempre que o método público Readable() é chamado. Ele retorna o tamanho da matriz m_rxbuff indicando a disponibilidade dos dados recuperados, com ajuda do 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());}


O método Read() aceita uma matriz do tipo CFrame como entrada, na qual serão gravados todos os quadros. O método usa uma função parse() privada para decodificar os dados de byte para que possam ser organizados adequadamente para facilitar a leitura. O método parse() retira a carga dos bytes do cabeçalho, que codificam informações descritivas sobre os quadros recém-recebidos.

//+------------------------------------------------------------------+
//| 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 a classe

Agora que nossa classe WebSocket está definida, vamos ver como ela pode ser usada em programas MetaTrader 5. Antes de começarmos a desenvolver um aplicativo que implemente esta classe, primeiro adicionaremos o endereço do servidor remoto que desejamos visitar à lista de pontos de extremidade permitidos nas configurações do terminal.



Lembre-se de habilitar WebsocketClient.mqh e siga estas etapas:

CWebSocketClient wsc;
  • declarar instância ou instâncias do WebsocketClient

Se você deseja especificar um tamanho máximo de upload para todos os uploads relacionados à conexão, agora é a hora de fazer isso. Quando a instância é inicializada, m_maxsendsize é 0, o que indica que não há limite de tamanho de quadro.

   

wsc.SetMaxSendSize(129); // max size in bytes set
  •      Chame o método de conexão com os parâmetros de entrada apropriados e verifique o resultado.
if(wsc.Connect(Address,Port,Timeout,usetls,true))
{
 //// 
}

Se a conexão for bem-sucedida, você pode iniciar o upload ou revisão das as mensagens recebidas. Você pode enviar dados usando qualquer método de upload que se aplique a matrizes preparadas anteriormente.

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


Se você deseja apenas enviar uma mensagem de string, use o método sendstring.

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


Você também pode enviar uma solicitação de eco ao servidor, que, se desejar, pode ser acompanhada por uma mensagem. Ao esperar por uma resposta após fazer o ping do servidor, o quadro de resposta do pong deve refletir o que foi enviado com o ping. O cliente deve fazer o mesmo se receber uma solicitação de eco do 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 obtê-la, verifique a presença de dados legíveis do soquete por um método legível. Se o método apontar para um soquete legível, chame o método de leitura do cliente com uma matriz de objetos do tipo Frame. O WebSocket irá então gravar todos os fragmentos de mensagens recebidos numa matriz de objetos. Aqui você pode usar métodos do tipo Frame para consultar o conteúdo de uma matriz de quadros. Conforme mencionado anteriormente, se um dos quadros recebidos for um quadro ping, é recomendável responder com um quadro pong o mais rápido possível. Para cumprir esse requisito, o cliente WebSocket criará um quadro de resposta pong ao receber qualquer ping. Tudo o que o usuário precisa fazer é chamar o método de envio de ping sem nenhum argumento.


Se um dos quadros recebidos for um quadro fechado, o estado do cliente WebSocket mudará para um estado fechado. Isso significa que o servidor enviou uma solicitação de fechamento e está se preparando para encerrar a conexão com este cliente. No estado fechado, as operações de envio são limitadas. O cliente só pode enviar um quadro de resposta de fechamento obrigatório. Assim como acontece com o recebimento de um quadro de ping, o recebimento de um quadro de fechamento significa que o cliente WebSocket cria um quadro de fechamento, pronto para upload.

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

Quando terminar de usar um cliente WebSocket, chame o método para fechar a conexão. Normalmente, basta chamar o método sem especificar nenhum argumento, a menos que haja algo sobre o qual você deseja notificar o servidor. Nesse caso, use uma das razões para o código de fechamento junto com uma mensagem curta de fechamento. Esta mensagem será forçosamente limitada a 122 caracteres. Os caracteres que excedem o limite são descartados.

Servidor WebSocket local

Para fins de teste, o arquivo zip anexado inclui um servidor WebSocket que fornece a capacidade de enviar solicitações de eco. O servidor é construído usando a biblioteca libwebsocket. O código fonte está disponível para download no github. Para criação é necessário apenas o Visual Studio. Todo o resto está no repositório github.

Iniciando o servidor e testando a biblioteca

Para iniciar o servidor de eco, clique duas vezes no arquivo exe do aplicativo. O servidor deve arrancar. O firewall instalado pode bloquear o servidor, portanto, dê a ele as permissões necessárias. Os arquivos .dll complementares contidos no diretório do aplicativo do servidor são necessários. O servidor não funcionará sem eles.

Servidor inativo

Vamos testar rapidamente a classe WebSocketClient. Aqui está um programa de amostra.

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


O EA se conecta ao servidor de eco WebSocket local e imediatamente tenta enviar uma mensagem bastante grande. Os dados de entrada do Expert Advisor permitem ativar ou desativar o TLS, bem como ajustar o tamanho de envio para ver como funciona o mecanismo de fragmentação da mensagem. No código, eu defini o tamanho máximo da mensagem para 256, de modo que cada quadro terá esse tamanho ou menos.

O Expert Advisor verifica se há mensagens do servidor na função onTimer. A mensagem recebida é exibida no terminal MetaTrader 5, após o qual a conexão com o soquete da web é encerrada. No próximo evento Ontimer, se a conexão for fechada, o EA será removido do gráfico. Isso é o que a guia de EAs no MetaTrader 5 exibe.

Impressão de cabeçalhos


Análise de dados

Recepção de quadros

Formação do quadro de fechamento

Mensagens do servidor WebSocket.


Captura de tela do servidor

Abaixo está um vídeo do programa que funciona quando conectado ao servidor.


Fim do artigo

Este artigo começou com uma visão geral rápida do protocolo WebSocket. Logo, descrevi em detalhes como o WebSocket pode ser implementado no MetaTrader 5 usando exclusivamente a linguagem de programação MQL5. Em seguida, criamos um servidor que usamos para testar nosso cliente MetaTrader 5. Espero que as ferramentas descritas aqui sejam úteis. Todo o código fonte está disponível para download abaixo. 

Conteúdo do arquivo anexado

Pasta 
 Sumário Descrição
MT5zip\server
echo_websocket_server.exe, websockets.dll,ssleay32.dll,libeay32.dll
 Aplicativo de servidor junto com os componentes necessários
MT5zip\Mql5\include
Frame.mqh, Socket.mqh, WebsocketClient.mqh
 Arquivos include contendo código para as classes CFrame, CSocket e CWebsocket
MT5zip\Mql5\Experts Websocketclient_test.mq5  Expert Advisor MetaTrader demonstrando o uso da classe CWebsocket



Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/8196

Arquivos anexados |
MT5zip.zip (826.64 KB)
Trabalhando com séries temporais na biblioteca DoEasy (Parte 58): séries temporais de dados de buffers de indicadores Trabalhando com séries temporais na biblioteca DoEasy (Parte 58): séries temporais de dados de buffers de indicadores
No final do tópico sobre trabalho com séries temporais, realizaremos o armazenamento, a pesquisa e a classificação dos dados armazenados em buffers de indicadores, o que nos permitirá realizar análises posteriores com base nos valores dos indicadores criados assentes na biblioteca para nossos programas. O conceito geral por trás de todas as classes-coleções da biblioteca torna mais fácil encontrar os dados necessários na coleção correspondente, assim, o mesmo será possível na classe que será criada hoje.
Reamostragem avançada e seleção de modelos CatBoost pelo método de força bruta Reamostragem avançada e seleção de modelos CatBoost pelo método de força bruta
Este artigo descreve uma das possíveis abordagens para a transformação de dados com o objetivo de melhorar a generalização do modelo, ele também discute a amostragem e seleção dos modelos CatBoost.
Redes Neurais de Maneira Fácil (Parte 8): Mecanismos de Atenção Redes Neurais de Maneira Fácil (Parte 8): Mecanismos de Atenção
Nos artigos anteriores, nós já testamos várias opções para organizar as redes neurais. Nós também estudamos as redes convolucionais emprestadas dos algoritmos de processamento de imagem. Neste artigo, eu sugiro estudarmos os Mecanismos de Atenção, cujo surgimento deu impulso ao desenvolvimento dos modelos de linguagem.
Algoritmo de aprendizado de máquina CatBoost da Yandex sem conhecimento prévio de Python ou R Algoritmo de aprendizado de máquina CatBoost da Yandex sem conhecimento prévio de Python ou R
O artigo fornece o código e a descrição das principais etapas do processo de aprendizado de máquina usando um exemplo específico. Para obter o modelo, você não precisa de conhecimento prévio em Python ou R. Além disso, um conhecimento básico de MQL5 já é suficiente — este é exatamente o meu nível. Portanto, eu espero que o artigo sirva como um bom tutorial para um público amplo, auxiliando os interessados em avaliar os recursos de aprendizado de máquina e implementá-lo em seus programas.