WebSocket para MetaTrader 5
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.
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.
Mensagens do servidor WebSocket.
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
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso