Trabalhando com as funções de rede ou MySQL sem DLL: Parte I - Conector
Conteúdo
- Introdução
- Sockets
- Analisador de tráfego Wireshark
- Troca de dados
- Classe de transação MySQL
- Aplicação
- Documentação
- Conclusão
Introdução
Há um ano, as funções de rede em MQL5 foram reabastecidos com as funções para se trabalhar com sockets. Isso abriu grandes oportunidades para os programadores que desenvolvem produtos para o Mercado. Agora eles podem implementar coisas que antes exigiam bibliotecas dinâmicas. Vamos considerar um desses exemplos nesta série de dois artigos. No primeiro artigo, eu vou considerar os princípios do conector MySQL, enquanto no segundo, desenvolverei as aplicações mais simples usando o conector, ou seja, o serviço para coletar as propriedades dos sinais disponíveis no terminal e o programa para visualizar suas alterações ao longo do tempo (veja a Fig. 1).
Fig. 1. O programa para visualizar alterações nas propriedades do sinal dentro de um certo período de tempo
Sockets
Um socket é uma interface de software para a troca de dados entre processos. Os processos podem ser iniciados em um único PC ou em diferentes PCs conectados em rede.
A MQL5 fornece apenas sockets do tipo cliente TCP. Isso significa que somos capazes de iniciar uma conexão, mas não podemos esperar por ela do lado de fora. Portanto, se nós precisarmos fornecer uma conexão entre programas MQL5 via sockets, nós precisaremos de um servidor que atue como intermediário. O servidor aguarda uma conexão com a porta ouvida e executa determinadas funções a pedido do cliente. Para se conectar ao servidor, nós precisamos saber o seu endereço IP e porta.
Uma porta é um número que varia de 0 a 65535. Existem três intervalos de portas: do sistema (0-1023), do usuário (1024-49151) e dinâmicas (49152-65535). Algumas portas são alocadas para trabalhar com determinadas funções. A alocação é realizada pela IANA - uma organização que gerencia as zonas de endereço IP e domínios de nível superior, além de registrar os tipos de dados MIME.
A porta 3306 é alocada para o MySQL por padrão. Nós nos conectaremos a ele ao acessar o servidor. Observe que esse valor pode ser alterado. Portanto, ao desenvolver um EA, a porta deve ser configurada nas entradas junto com o endereço IP.
A seguinte abordagem é usada ao trabalhar com sockets:
- Criamos um socket (obtemos um identificador ou erro)
- Nos conectamos ao servidor
- Há a troca de dados
- Encerramos o soquete
Ao trabalhar com várias conexões, lembre-se da limitação de 128 sockets abertos simultaneamente para um único programa MQL5.
Analisador de tráfego Wireshark
O analisador de tráfego facilita a depuração do código de um programa que aplica sockets. Sem ele, todo o processo se assemelha à reparação de eletrônicos sem um osciloscópio. O analisador captura os dados da interface de rede selecionada e os exibe de forma legível. Ele rastreia o tamanho dos pacotes, o intervalo de tempo entre eles, a presença de retransmissões e quedas de conexão, além de muitos outros dados úteis. Ele também descriptografa muitos protocolos.
Pessoalmente, eu uso o Wireshark para esses fins.
Fig. 2. Analisador de tráfego Wireshark
A Figura 2 exibe a janela do analisador de tráfego com os pacotes capturados em que:
- Filtro de exibição das linhas. "tcp.port==3306" significa que apenas os pacotes com a porta TCP 3306 local ou remota são exibidos (porta do servidor MySQL padrão).
- Pacotes. Aqui nós podemos ver o processo de configuração da conexão, a saudação do servidor, a solicitação de autorização e a subsequente troca de dados.
- Conteúdo do pacote selecionado em formato hexadecimal. Nesse caso, nós podemos ver o conteúdo do pacote de saudação do servidor MySQL.
- Nível de transporte (TCP). Estamos localizados aqui ao usar as funções para trabalhar com sockets.
- Nível da aplicação (MySQL). É isso que nós devemos considerar neste artigo.
O filtro de exibição não limita a captura de pacotes. Isso é claramente visível na barra de status, indicando que 35 pacotes capturados de 2623 pacotes localizados na memória estão sendo manipulados no momento. Para reduzir a carga no PC, nós devemos definir o filtro de captura ao selecionar a interface de rede, conforme mostrado na Fig. 3. Isso deve ser feito apenas se todos os outros pacotes não forem realmente úteis.
Fig. 3. Filtro de captura de pacotes
Para nos familiarizarmos com o analisador de tráfego, vamos tentar estabelecer uma conexão com o servidor "google.com" e acompanhar o processo. Para fazer isso, escrevemos um pequeno script.
void OnStart() { //--- Get socket handle int socket=SocketCreate(); if(socket==INVALID_HANDLE) return; //--- Establish connection if(SocketConnect(socket,"google.com",80,2000)==false) { return; } Sleep(5000); //--- Close connection SocketClose(socket); }
Então, primeiro nós criamos um socket e obtemos o seu identificador usando a função SocketCreate(). A referência diz que, neste caso, você pode obter um erro em dois casos quase impossíveis:
- O erro ERR_NETSOCKET_TOO_MANY_OPENED indica que mais de 128 sockets estão abertos.
- O erro ERR_FUNCTION_NOT_ALLOWED aparece ao tentar chamar uma criação de socket a partir de um indicador, no qual esse recurso está desativado.
Depois de receber o identificador, tentamos estabelecer a conexão. Neste exemplo, nos conectamos ao servidor "google.com" (não esqueça de adicioná-lo aos endereços permitidos nas configurações da plataforma), ou seja, a porta 80 com o tempo limite de 2 000 milissegundos. Depois de estabelecer a conexão, aguardamos 5 segundos e fechamos a conexão. Agora vamos ver como fica na janela do analisador de tráfego.
Fig. 4. Estabelecendo e fechando uma conexão
Na Figura 4, nós podemos ver a troca de dados entre nosso script e o servidor "google.com" com o endereço IP "172.217.16.14". As consultas DNS não são exibidas aqui, pois a linha de filtro apresenta a expressão "tcp.port==80".
Os três pacotes superiores representam o estabelecimento de uma conexão, enquanto os três inferiores representam seu fechamento. A coluna Time exibe o tempo entre os pacotes e podemos ver 5 segundos de tempo de inatividade. Observe que os pacotes são coloridos em verde, diferente dos da Figura 2. Isso ocorre porque, no caso anterior, o analisador detectou o protocolo MySQL na troca. No caso atual, nenhum dado foi passado e o analisador destacou os pacotes com a cor TCP padrão.
Troca de dados
De acordo com o protocolo, o servidor MySQL deve enviar uma saudação depois de estabelecer uma conexão. Em resposta, o cliente envia uma solicitação de autorização. Esse funcionamento é descrito em detalhes na seção Connection Phase na página dev.mysql.com. Se a saudação não for recebida, o endereço IP é inválido ou o servidor está ouvindo outra porta. De qualquer forma, isso significa que nos conectamos a algo que definitivamente não é um servidor MySQL. Em uma situação normal, nós precisamos receber os dados (ler do socket) e analisá-los.
Recepção
Na classe CMySQLTransaction (a ser descrito em detalhes um pouco mais tarde), o recebimento de dados foi implementado da seguinte maneira:
//+------------------------------------------------------------------+ //| Data receipt | //+------------------------------------------------------------------+ bool CMySQLTransaction::ReceiveData(ushort error_code=0) { char buf[]; uint timeout_check=GetTickCount()+m_timeout; do { //--- Get the amount of data that can be read from the socket uint len=SocketIsReadable(m_socket); if(len) { //--- Read data from the socket to the buffer int rsp_len=SocketRead(m_socket,buf,len,m_timeout); m_rx_counter+= rsp_len; //--- Send the buffer for handling ENUM_TRANSACTION_STATE res = Incoming(buf,rsp_len); //--- Get the result the following actions will depend on if(res==MYSQL_TRANSACTION_COMPLETE) // server response fully accepted return true; // exit (successful) else if(res==MYSQL_TRANSACTION_ERROR) // error { if(m_packet.error.code) SetUserError(MYSQL_ERR_SERVER_ERROR); else SetUserError(MYSQL_ERR_INTERNAL_ERROR); return false; // exit (error) } //--- In case of another result, continue waiting for data in the loop } } while(GetTickCount()<timeout_check && !IsStopped()); //--- If waiting for the completion of the server response receipt took longer than m_timeout, //--- exit with the error SetUserError(error_code); return false; }Aqui o m_socket é um identificador de socket obtido anteriormente ao criá-lo, enquanto m_timeout é o tempo limite de leitura de dados usado como o SocketRead() um argumento de função para aceitar um fragmento de dados, bem como na forma do tempo limite de recebimento de todos os dados. Antes de inserir o loop, definimos um timestamp. Atingindo-o, ele é considerado o tempo limite do recebimento de dados:
uint timeout_check=GetTickCount()+m_timeout;
Em seguida, fazemos a leitura do resultado da função SocketIsReadable() em um loop e aguardamos até que ele retorne um valor diferente de zero. Depois disso, fazemos a leitura dos dados no buffer e passamos eles para processamento.
uint len=SocketIsReadable(m_socket); if(len) { //--- Read data from the socket to the buffer int rsp_len=SocketRead(m_socket,buf,len,m_timeout); m_rx_counter+= rsp_len; //--- Send the buffer for handling ENUM_TRANSACTION_STATE res = Incoming(buf,rsp_len); ... }
Nós não podemos contar com a capacidade de aceitar o pacote inteiro se houver dados no socket. Há várias situações em que os dados podem chegar em pequenas porções. Por exemplo, pode ser uma conexão ruim por meio de um modem 4G com um grande número de retransmissões. Portanto, nosso manipulador deve ser capaz de coletar os dados em alguns grupos indivisíveis com os quais é possível trabalhar. Vamos usar os pacotes MySQL para isso.
O método CMySQLTransaction::Incoming() é usado para acumular e processar os dados:
//--- Handle received data ENUM_TRANSACTION_STATE Incoming(uchar &data[], uint len);
O resultado retornado nos permite saber o que fazer em seguida — continuar, concluir ou interromper o processo de recebimento dos dados:
enum ENUM_TRANSACTION_STATE { MYSQL_TRANSACTION_ERROR=-1, // Error MYSQL_TRANSACTION_IN_PROGRESS=0, // In progress MYSQL_TRANSACTION_COMPLETE, // Fully completed MYSQL_TRANSACTION_SUBQUERY_COMPLETE // Partially completed };
No caso de um erro interno, bem como ao obter um erro no servidor ou ao concluir o recebimento dos dados, a leitura dos dados do socket deve ser interrompida. Em todos os outros casos, ele deve continuar. O valor MYSQL_TRANSACTION_SUBQUERY_COMPLETE indica que uma das respostas do servidor as várias consultas de um cliente foram aceitas. É equivalente a MYSQL_TRANSACTION_IN_PROGRESS para o algoritmo de leitura.
Fig. 5. O pacote MySQL
O formato do pacote MySQL é exibido na Fig. 5. Os três primeiros bytes definem o tamanho de uma carga útil no pacote, enquanto o próximo byte significa o número de série do pacote na sequência, sendo seguido pelos dados. O número de série é definido como zero no início de cada transmissão. Por exemplo, o pacote de saudação é 0, solicitação de autorização do cliente — 1, resposta do servidor — 2 (final da fase de conexão). Em seguida, ao enviar uma consulta do cliente, o valor do número de sequência deve ser definido como zero novamente e aumentado em cada pacote de resposta do servidor. Se o número de pacotes exceder 255, o valor numérico passa para zero.
O pacote mais simples (ping do MySQL) tem a seguinte aparência no analisador de tráfego:
Fig. 6. Pacote de ping no analisador de tráfego
O pacote Ping contém um byte de dados com o valor 14 (ou 0x0E na forma hexadecimal).
Vamos considerar o método CMySQLTransaction::Incoming() que reúne os dados para os pacotes e os passa para os manipuladores. Seu código fonte resumido é fornecido abaixo.
ENUM_TRANSACTION_STATE CMySQLTransaction::Incoming(uchar &data[], uint len) { int ptr=0; // index of the current byte in the 'data' buffer ENUM_TRANSACTION_STATE result=MYSQL_TRANSACTION_IN_PROGRESS; // result of handling accepted data while(len>0) { if(m_packet.total_length==0) { //--- If the amount of data in the packet is unknown while(m_rcv_len<4 && len>0) { m_hdr[m_rcv_len] = data[ptr]; m_rcv_len++; ptr++; len--; } //--- Received the amount of data in the packet if(m_rcv_len==4) { //--- Reset error codes etc. m_packet.Reset(); m_packet.total_length = reader.TotalLength(m_hdr); m_packet.number = m_hdr[3]; //--- Length received, reset the counter of length bytes m_rcv_len = 0; //--- Highlight the buffer of a specified size if(ArrayResize(m_packet.data,m_packet.total_length)!=m_packet.total_length) return MYSQL_TRANSACTION_ERROR; // internal error } else // if the amount of data is still not accepted return MYSQL_TRANSACTION_IN_PROGRESS; } //--- Collect packet data while(len>0 && m_rcv_len<m_packet.total_length) { m_packet.data[m_rcv_len] = data[ptr]; m_rcv_len++; ptr++; len--; } //--- Make sure the package has been collected already if(m_rcv_len<m_packet.total_length) return MYSQL_TRANSACTION_IN_PROGRESS; //--- Handle received MySQL packet //... //--- m_rcv_len = 0; m_packet.total_length = 0; } return result; }
O primeiro passo é coletar o cabeçalho do pacote — os primeiros 4 bytes contendo o comprimento dos dados e o número de série na sequência. Para acumular o cabeçalho, usamos o buffer m_hdr e o contador de bytes m_rcv_len. Quando 4 bytes são coletados, obtemos o seu comprimento e alteramos o buffer m_packet.data com base nele. Os dados do pacote recebido são copiados para ele. Quando o pacote estiver pronto, passamos para o manipulador.
E se o comprimento dos dados recebidos len ainda não for zero após o recebimento do pacote, isso significa que recebemos vários pacotes. Nós podemos lidar com vários pacotes inteiros ou vários parciais em uma única chamada do método Incoming().
Os tipos de pacotes são fornecidos abaixo:
enum ENUM_PACKET_TYPE { MYSQL_PACKET_NONE=0, // None MYSQL_PACKET_DATA, // Data MYSQL_PACKET_EOF, // End of file MYSQL_PACKET_OK, // Ok MYSQL_PACKET_GREETING, // Greeting MYSQL_PACKET_ERROR // Error };
Cada um deles tem seu próprio manipulador, que analisa sua sequência e seu conteúdo de acordo com o protocolo. Os valores recebidos durante a análise são atribuídos aos membros das classes correspondentes. Na implementação atual do conector, todos os dados recebidos nos pacotes são analisados. Isso pode parecer um pouco redundante, pois as propriedades dos campos "Table" e "Original table" geralmente coincidem. Além disso, os valores de algumas flags raramente são necessários (veja a Fig. 7). No entanto, a disponibilidade dessas propriedades permite construir de maneira flexível a lógica de interação com o servidor MySQL na camada de aplicação do programa.
Fig. 7. Pacote de descrição do campo
Transmissão
O envio de dados é um pouco mais fácil aqui.
//+------------------------------------------------------------------+ //| Form and send ping | //+------------------------------------------------------------------+ bool CMySQLTransaction::ping(void) { if(reset_rbuf()==false) { SetUserError(MYSQL_ERR_INTERNAL_ERROR); return false; } //--- Prepare the output buffer m_tx_buf.Reset(); //--- Reserve a place for the packet header m_tx_buf.Add(0x00,4); //--- Place the command code m_tx_buf+=uchar(0x0E); //--- Form a header m_tx_buf.AddHeader(0); uint len = m_tx_buf.Size(); //--- Send a packet if(SocketSend(m_socket,m_tx_buf.Buf,len)!=len) return false; m_tx_counter+= len; return true; }
O código fonte do método de envio de ping é fornecido acima. Copiamos os dados para o buffer preparado. No caso do ping, este é o código do comando 0x0E. Em seguida, formamos o cabeçalho considerando a quantidade de dados e o número de série do pacote. Para um ping, o número de série é sempre igual a zero. Depois disso, tentamos enviar o pacote montado usando a função SocketSend().
O método de envio de uma consulta (Query) é semelhante ao envio de um ping:
//+------------------------------------------------------------------+ //| Form and send a query | //+------------------------------------------------------------------+ bool CMySQLTransaction::query(string s) { if(reset_rbuf()==false) { SetUserError(MYSQL_ERR_INTERNAL_ERROR); return false; } //--- Prepare the output buffer m_tx_buf.Reset(); //--- Reserve a place for the packet header m_tx_buf.Add(0x00,4); //--- Place the command code m_tx_buf+=uchar(0x03); //--- Add the query string m_tx_buf+=s; //--- Form a header m_tx_buf.AddHeader(0); uint len = m_tx_buf.Size(); //--- Send a packet if(SocketSend(m_socket,m_tx_buf.Buf,len)!=len) return false; m_tx_counter+= len; return true; }
A única diferença é que a carga útil consiste no código de comando (0x03) e a string de consulta.
O envio de dados é sempre seguido pelo método de recebimento CMySQLTransaction::ReceiveData() que consideramos anteriormente. Se não retornar nenhum erro, a transação será considerada bem-sucedida.
Classe de transação MySQL
Agora é hora de considerar a classe CMySQLTransaction classe com mais detalhes.
//+------------------------------------------------------------------+ //| MySQL transaction class | //+------------------------------------------------------------------+ class CMySQLTransaction { private: //--- Authorization data string m_host; // MySQL server IP address uint m_port; // TCP port string m_user; // User name string m_password; // Password //--- Timeouts uint m_timeout; // timeout of waiting for TCP data (ms) uint m_timeout_conn; // timeout of establishing a server connection //--- Keep Alive uint m_keep_alive_tout; // time(ms), after which the connection is closed; the value of 0 - Keep Alive is not used uint m_ping_period; // period of sending ping (in ms) in the Keep Alive mode bool m_ping_before_query; // send 'ping' before 'query' (this is reasonable in case of large ping sending periods) //--- Network int m_socket; // socket handle ulong m_rx_counter; // counter of bytes received ulong m_tx_counter; // counter of bytes passed //--- Timestamps ulong m_dT; // last query time uint m_last_resp_timestamp; // last response time uint m_last_ping_timestamp; // last ping time //--- Server response CMySQLPacket m_packet; // accepted packet uchar m_hdr[4]; // packet header uint m_rcv_len; // counter of packet header bytes //--- Transfer buffer CData m_tx_buf; //--- Authorization request class CMySQLLoginRequest m_auth; //--- Server response buffer and its size CMySQLResponse m_rbuf[]; uint m_responses; //--- Waiting and accepting data from the socket bool ReceiveData(ushort error_code); //--- Handle received data ENUM_TRANSACTION_STATE Incoming(uchar &data[], uint len); //--- Packet handlers for each type ENUM_TRANSACTION_STATE PacketOkHandler(CMySQLPacket *p); ENUM_TRANSACTION_STATE PacketGreetingHandler(CMySQLPacket *p); ENUM_TRANSACTION_STATE PacketDataHandler(CMySQLPacket *p); ENUM_TRANSACTION_STATE PacketEOFHandler(CMySQLPacket *p); ENUM_TRANSACTION_STATE PacketErrorHandler(CMySQLPacket *p); //--- Miscellaneous bool ping(void); // send ping bool query(string s); // send a query bool reset_rbuf(void); // initialize the server response buffer uint tick_diff(uint prev_ts); // get the timestamp difference //--- Parser class CMySQLPacketReader reader; public: CMySQLTransaction(); ~CMySQLTransaction(); //--- Set connection parameters bool Config(string host,uint port,string user,string password,uint keep_alive_tout); //--- Keep Alive mode void KeepAliveTimeout(uint tout); // set timeout void PingPeriod(uint period) {m_ping_period=period;} // set ping period in seconds void PingBeforeQuery(bool st) {m_ping_before_query=st;} // enable/disable ping before a query //--- Handle timer events (relevant when using Keep Alive) void OnTimer(void); //--- Get the pointer to the class for working with authorization CMySQLLoginRequest *Handshake(void) {return &m_auth;} //--- Send a request bool Query(string q); //--- Get the number of server responses uint Responses(void) {return m_responses;} //--- Get the pointer to the server response by index CMySQLResponse *Response(uint idx); CMySQLResponse *Response(void) {return Response(0);} //--- Get the server error structure MySQLServerError GetServerError(void) {return m_packet.error;} //--- Options ulong RequestDuration(void) {return m_dT;} // get the last transaction duration ulong RxBytesTotal(void) {return m_rx_counter;} // get the number of received bytes ulong TxBytesTotal(void) {return m_tx_counter;} // get the number of passed bytes void ResetBytesCounters(void) {m_rx_counter=0; m_tx_counter=0;} // reset the counters of received and passed bytes };
Vamos dar uma olhada nos seguintes membros privados:
- m_packet do tipo CMySQLPacket — classe do pacote MySQL processado no momento atual (código fonte com comentários no arquivo MySQLPacket.mqh)
- m_tx_buf do tipo CData — classe do buffer de transferência criado para a conveniência de gerar uma consulta (arquivo Data.mqh)
- m_auth do tipo CMySQLLoginRequest — classe para trabalhar com autorizações (codificação de senhas, armazenamento de parâmetros de servidor obtidos e parâmetros de cliente especificados, o código fonte está em MySQLLoginRequest.mqh)
- m_rbuf do tipo CMySQLResponse — buffer de resposta do servidor; a resposta aqui é o pacote do tipo "Ok" ou "Data" (MySQLResponse.mqh)
- readerdo tipo CMySQLPacketReader — classe de analisador de pacotes MySQL
Os métodos públicos são descritos em detalhes na documentação.
Para a camada de aplicação, a classe de transação é exibida como mostra a Figura 8.
Fig. 8. Estrutura da classe CMySQLTransaction
onde:
- CMySQLLoginRequest — deve ser configurada antes de estabelecer uma conexão ao especificar os parâmetros do cliente cujos valores são diferentes dos predefinidos (opcional);
- CMySQLResponse — resposta do servidor se uma transação for concluída sem erros
- CMySQLField — descrição do campo;
- CMySQLRow — linha (buffer de valores do campo em forma de texto);
- MySQLServerError — estrutura de descrição do erro no caso de falha de uma transação.
Não há métodos públicos responsáveis por estabelecer e fechar uma conexão. Isso é feito automaticamente ao chamar o método CMySQLTransaction::Query(). Ao usar o modo de conexão constante, ele é estabelecido durante a primeira chamada de CMySQLTransaction::Query () e fechado após o tempo limite definido.
Importante: No modo de conexão constante, o manipulador de eventos OnTimer deve receber a chamada do método CMySQLTransaction::OnTimer(). Nesse caso, o período do timer deve ser menor que os períodos de ping e tempo limite.
Os parâmetros de conexão, conta do usuário e valores especiais de parâmetros do cliente devem ser definidos antes de chamar o método CMySQLTransaction::Query().
Em geral, a interação com a classe de transação é realizada de acordo com o seguinte princípio:
Fig. 9. Trabalhando com a classe CMySQLTransaction
Aplicação
Vamos considerar o exemplo mais simples da aplicação do conector. Para fazer isso, escrevemos um script enviando a consulta SELECT para o banco de dados de teste world.
//--- input parameters input string inp_server = "127.0.0.1"; // MySQL server address input uint inp_port = 3306; // TCP port input string inp_login = "admin"; // Login input string inp_password = "12345"; // Password input string inp_db = "world"; // Database name //--- Connect MySQL transaction class #include <MySQL\MySQLTransaction.mqh> CMySQLTransaction mysqlt; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Configure MySQL transaction class mysqlt.Config(inp_server,inp_port,inp_login,inp_password); //--- Make a query string q = "select `Name`,`SurfaceArea` "+ "from `"+inp_db+"`.`country` "+ "where `Continent`='Oceania' "+ "order by `SurfaceArea` desc limit 10"; if(mysqlt.Query(q)==true) { if(mysqlt.Responses()!=1) return; CMySQLResponse *r = mysqlt.Response(); if(r==NULL) return; Print("Name: ","Surface Area"); uint rows = r.Rows(); for(uint i=0; i<rows; i++) { double area; if(r.Row(i).Double("SurfaceArea",area)==false) break; PrintFormat("%s: %.2f",r.Row(i)["Name"],area); } } else if(GetLastError()==(ERR_USER_ERROR_FIRST+MYSQL_ERR_SERVER_ERROR)) { // in case of a server error Print("MySQL Server Error: ",mysqlt.GetServerError().code," (",mysqlt.GetServerError().message,")"); } else { if(GetLastError()>=ERR_USER_ERROR_FIRST) Print("Transaction Error: ",EnumToString(ENUM_TRANSACTION_ERROR(GetLastError()-ERR_USER_ERROR_FIRST))); else Print("Error: ",GetLastError()); } }
Suponha que nossa tarefa seja obter uma lista de países com o valor do continente "Oceania" classificados por área, do maior para o menor, com o máximo de 10 itens na lista. Vamos executar as seguintes ações:
- Declaramos uma instância da classe de transação mysqlt
- Definimos os parâmetros de conexão
- Criamos a consulta apropriada
- Se a transação for bem sucedida, verificamos se o número de respostas é igual ao valor esperado
- Obtemos o ponteiro para a classe de resposta do servidor
- Obtemos o número de linhas na resposta
- Exibimos os valores das linhas
A transação pode falhar devido a um dos três motivos:
- Erro no servidor - obtemos sua descrição usando o método CMySQLTransaction::GetServerError()
- Um erro interno - usamos o EnumToString() para obter uma descrição
- Caso contrário, obtemos o código de erro usando GetLastError()
Se as entradas forem especificadas corretamente, o resultado da operação do script é o seguinte:
Fig. 10. Resultado da operação do script de teste
Exemplos mais complexos que aplicam várias consultas e o modo de conexão constante serão descritos na segunda parte.
Documentação
Conteúdo
- Classe de gerenciamento de autorização CMySQLLoginRequest
- Classe de resposta do servidor CMySQLResponse
- Estrutura de erro do servidor MySQLServerError
- Classe de campo CMySQLField
- Classe de linha CMySQLRow
Classe de transação CMySQLTransaction
Lista de métodos da classe CMySQLTransaction
Método |
Ação |
---|---|
Config |
Define os parâmetros de conexão |
KeepAliveTimeout |
Define o tempo limite para o modo Keep Alive em segundos |
PingPeriod |
Define o período de ping para o modo Keep Alive em segundos |
PingBeforeQuery |
Ativa/desativa o ping antes de uma consulta |
OnTimer |
Processa os eventos do timer (relevante ao usar o Keep Alive) |
Handshake |
Obtém o ponteiro da classe para trabalhar com a autorização |
Query |
Envia uma consulta |
Responses |
Obtém o número de respostas do servidor |
Response |
Obtém o ponteiro para a classe de resposta do servidor |
GetServerError |
Obtém a estrutura de erros do servidor |
RequestDuration |
Duração da transação em microssegundos |
RxBytesTotal |
O contador de bytes aceitos desde o início do programa |
TxBytesTotal |
O contador de bytes enviados desde o início do programa |
ResetBytesCounters |
Redefine os contadores de bytes aceitos e enviados |
Abaixo está uma breve descrição de cada método.
Config
Define os parâmetros de conexão.bool Config( string host, // server name uint port, // port string user, // user name string password, // password string base, // database name uint keep_alive_tout // constant connection timeout (0 - not used) );
Valor de retorno: true se for bem sucedido; caso contrário - false (símbolos inválidos nos argumentos do tipo string).
KeepAliveTimeout
Ativa o modo de conexão constante e define o tempo limite. O valor do tempo limite é um tempo em segundos a partir do momento do envio da última consulta, após o qual a conexão é fechada. Se as consultas forem repetidas com mais frequência do que o valor de tempo limite definido, a conexão não será fechada.
void KeepAliveTimeout( uint tout // set the constant connection timeout in seconds (0 - disable) );
PingPeriod
Define o período de envio de pacotes 'ping' no modo de conexão constante. Isso impede que o servidor feche a conexão. O ping é enviado após o tempo especificado na última consulta ou no ping anterior.
void PingPeriod( uint period // set the ping period in seconds (for the constant connection mode) );
Valor de retorno: nenhum.
PingBeforeQuery
Permite enviar o pacote 'ping' antes de uma consulta. A conexão pode ser fechada ou encerrada por algum motivo no modo de conexão constante em intervalos de tempo entre as consultas. Nesse caso, é possível enviar o ping ao servidor MySQL para garantir que a conexão esteja ativa antes de enviar uma consulta.
void PingBeforeQuery( bool st // enable (true)/disable (false) ping before a query );
Valor de retorno: nenhum.
OnTimer
Utilizado no modo de conexão constante. O método deve ser chamado a partir do manipulador de eventos OnTimer. O período do timer não deve exceder o valor mínimo dos períodos KeepAliveTimeout e PingPeriod.
void OnTimer(void);
Valor de retorno: nenhum.
Handshake
Obtém o ponteiro para a classe de trabalho com a autorização. Ele pode ser usado para definir as flags dos recursos do cliente e o tamanho máximo do pacote antes de estabelecer uma conexão com o servidor. Após a autorização, permite receber a versão e as flags dos recursos do servidor.
CMySQLLoginRequest *Handshake(void);
Valor de retorno: ponteiro para a classe CMySQLLoginRequest de trabalho com autorização.
Query
Envia uma consulta.
bool Query( string q // query body );
Valor de retorno: resultado da execução; bem sucedido - true, erro - false.
Responses
Obtém o número de respostas.
uint Responses(void);
Valor de retorno: número de respostas do servidor.
Pacotes dos tipos "Ok" ou "Data" são considerados respostas. Se a consulta for executada com sucesso, uma ou mais respostas (para várias consultas) serão aceitas.
Response
Obtém o ponteiro para a classe de resposta do servidor MySQL
CMySQLResponse *Response( uint idx // server response index );
Valor de retorno: ponteiro para a classe de resposta do servidor CMySQLResponse. Passar um valor inválido como argumento retorna NULL.
O método sobrecarregado sem especificar um índice é equivalente a Response(0).
CMySQLResponse *Response(void);
Valor de retorno: ponteiro para a classe de resposta do servidor CMySQLResponse. Se não houver respostas, NULL será retornado.
GetServerError
Obtém a estrutura que armazena o código e a mensagem de erro do servidor. Ele pode ser chamado após a classe de transação retornar o erro MYSQL_ERR_SERVER_ERROR.
MySQLServerError GetServerError(void);
Valor de retorno: estrutura de erro MySQLServerError
RequestDuration
Obtém a duração da execução da solicitação.
ulong RequestDuration(void);
Valor de retorno: duração da consulta em microssegundos desde o momento do envio até o final do processamento
RxBytesTotal
Obtém o número de bytes aceitos.
ulong RxBytesTotal(void);
Valor de retorno: número de bytes aceitos (nível TCP) desde o início do programa. O método ResetBytesCounters é usado para uma redefinição.
TxBytesTotal
Obtém o número de bytes enviados.
ulong TxBytesTotal(void);
Valor de retorno: número de bytes passados (nível TCP) desde o início do programa. O método ResetBytesCounters é usado para uma redefinição.
ResetBytesCounters
Redefine os contadores de bytes aceitos e enviados.
void ResetBytesCounters(void);
Classe de gerenciamento de autorização CMySQLLoginRequest
Métodos da classe CMySQLLoginRequest
Método |
Ação |
---|---|
SetClientCapabilities |
Define as flags de recursos do cliente. Valor predefinido: 0x005FA685 |
SetMaxPacketSize |
Define o tamanho máximo permitido do pacote em bytes. Valor predefinido: 16777215 |
SetCharset |
Define o conjunto de símbolos usados. Valor predefinido: 8 |
Version |
Retorna a versão do servidor MySQL. Por exemplo: "5.7.21-log". |
ThreadId |
Retorna o ID da thread de conexão atual. Corresponde ao valor CONNECTION_ID. |
ServerCapabilities |
Obtém as flags dos recursos do servidor |
ServerLanguage |
Retorna a codificação e a representação do banco de dados ID |
Classe de resposta do servidor CMySQLResponse
Um pacote do tipo "Ok" ou "Data" é considerado uma resposta do servidor. Dado que eles diferem significativamente, a classe possui um conjunto separado de métodos para trabalhar com cada tipo de pacote.
Métodos gerais da classe CMySQLResponse:
Método |
Valor de Retorno |
---|---|
Type |
Tipo de resposta do servidor: MYSQL_RESPONSE_DATA ou MYSQL_RESPONSE_OK |
Métodos para os pacotes do tipo "Data":
Método |
Valor de Retorno |
---|---|
Fields |
Número de campos |
Field |
Ponteiro para a classe de campo por índice (método sobrecarregado - obtendo o índice do campo pelo nome) |
Field | Índice do campo por nome |
Rows |
Número de linhas em uma resposta do servidor |
Row |
O ponteiro para uma classe de linha por índice |
Value |
Valor de string por índices de linha e campo |
Value | Valor de string por índice de linha e nome do campo |
ColumnToArray | O resultado da leitura de uma coluna do array do tipo string |
ColumnToArray |
O resultado da leitura de uma coluna no array do tipo int com verificação de tipo |
ColumnToArray |
O resultado da leitura de uma coluna no array do tipo long com verificação de tipo |
ColumnToArray |
O resultado da leitura de uma coluna no array do tipo double com verificação de tipo |
Método |
Valor de Retorno |
---|---|
AffectedRows |
Número de linhas afetadas pela última operação |
LastId |
Valor LAST_INSERT_ID |
ServerStatus |
Flags do estado do servidor |
Warnings |
Número de avisos |
Message |
Mensagem de texto do servidor |
Estrutura de erro do servidor MySQLServerError
Elementos da estrutura MySQLServerError
Elemento |
Tipo |
Propósito |
---|---|---|
code |
ushort | Código de erro |
sqlstate |
uint | Estado |
message | string | Mensagem de texto do servidor |
Classe de campo CMySQLField
Métodos da classe CMySQLField
Método |
Valor de Retorno |
---|---|
Catalog |
Nome de um diretório ao qual pertence a tabela |
Database |
Nome de um banco de dados ao qual pertence a tabela |
Table |
Pseudônimo de uma tabela à qual pertence o campo |
OriginalTable |
Nome original de uma tabela à qual pertence o campo |
Name |
Pseudônimo de campo |
OriginalName |
Nome do campo original |
Charset |
Número de codificação aplicado |
Length |
Comprimento do valor |
Type |
Tipo de valor |
Flags |
Flags que definem atributos de valor |
Decimals |
Casas decimais permitidas |
MQLType |
Tipo de campo na forma de valor ENUM_DATABASE_FIELD_TYPE (exceto para DATABASE_FIELD_TYPE_NULL) |
Classe de linha CMySQLRow
Métodos da classe CMySQLRow
Método |
Ação |
---|---|
Value |
Retorna o valor do campo pelo número como uma string |
operator[] |
Retorna o valor do campo pelo nome como uma string |
MQLType |
Retorna o tipo de campo por número como o valor ENUM_DATABASE_FIELD_TYPE |
MQLType |
Retorna o tipo de campo pelo nome como o valor ENUM_DATABASE_FIELD_TYPE |
Text |
Obtém o valor do campo pelo número como uma string com verificação de tipo |
Text |
Obtém o valor do campo pelo nome como uma string com verificação de tipo |
Integer |
Obtém o valot do tipo int pelo número do campo com verificação de tipo |
Integer |
Obtém o valor do tipo int pelo nome do campo com verificação de tipo |
Long |
Obtém o valor do tipo long pelo número do campo com verificação de tipo |
Long |
Obtém o valor do tipo long pelo nome do campo com verificação de tipo |
Double |
Obtém o valor do tipo double pelo número do campo com verificação de tipo |
Double |
Obtém o valor do tipo double pelo nome do campo com verificação de tipo |
Blob |
Obtém o valor na forma de array uchar pelo número do campo com verificação de tipo |
Blob |
Obtém o valor na forma de array uchar pelo nome do campo com verificação de tipo |
Observação. A verificação de tipo significa que o campo legível do método que trabalha com o tipo int deve ser igual a DATABASE_FIELD_TYPE_INTEGER. No caso de uma incompatibilidade, nenhum valor é recebido e o método retorna 'false'. Convertendo IDs de tipo de campo MySQL para ENUM_DATABASE_FIELD_TYPE, o valor do tipo é implementado no método CMySQLField::MQLType() cujo código-fonte é fornecido abaixo.
//+------------------------------------------------------------------+ //| Return the field type as the ENUM_DATABASE_FIELD_TYPE value | //+------------------------------------------------------------------+ ENUM_DATABASE_FIELD_TYPE CMySQLField::MQLType(void) { switch(m_type) { case 0x00: // decimal case 0x04: // float case 0x05: // double case 0xf6: // newdecimal return DATABASE_FIELD_TYPE_FLOAT; case 0x01: // tiny case 0x02: // short case 0x03: // long case 0x08: // longlong case 0x09: // int24 case 0x10: // bit case 0x07: // timestamp case 0x0c: // datetime return DATABASE_FIELD_TYPE_INTEGER; case 0x0f: // varchar case 0xfd: // varstring case 0xfe: // string return DATABASE_FIELD_TYPE_TEXT; case 0xfb: // blob return DATABASE_FIELD_TYPE_BLOB; default: return DATABASE_FIELD_TYPE_INVALID; } }
Conclusão
Neste artigo, nós examinamos o uso de funções para trabalhar com sockets usando a implementação do conector MySQL como exemplo. Esta foi a parte teórica. A segunda parte do artigo é de natureza mais prática: desenvolveremos um serviço para coletar propriedades do sinal e um programa para visualizar as alterações nelas.
O arquivo anexado contém os seguintes arquivos:
- Caminho Include\MySQL\: códigos-fonte do conector
- Arquivo Scripts\test_mysql.mq5: o exemplo do uso do conector considerado na seção Aplicação.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/7117
- 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