Utilizando WinInet.dll para a troca de dados entre plataformas via internet
--- | 4 março, 2014
O Metatrader abre oportunidades únicas para os usuários, utilizando um número de novos elementos da interface do usuário em seu arsenal. Por essa razão, as funções que estavam indisponíveis anteriormente podem agora serem usadas ao máximo.
Nesta lição iremos aprender a:
- usar as tecnologias básicas de internet;
- trocar os dados entre as plataformas via servidor;
- criar uma biblioteca genérica para trabalhar com a internet no ambiente do MQL5.
O CodeBase do MQL5 contém um exemplo de script, que trabalha com a biblioteca wininet.dll e mostra um exemplo de solicitação da página do servidor. Mas hoje iremos muito mais longe, e faremos o servidor, não apenas nos dar a página, mas também enviar e armazenar esses dados para transferências posteriores a outras plataformas solicitantes.
Nota: para aqueles que não possuem acesso ao servidor, configurado com PHP, sugerimos baixar o kit Denwer, e usá-lo como uma plataforma de trabalho. Também, recomendamos o uso do servidor Apache e do PHP em seu localhost para teste.
Para enviar qualquer solicitação ao servidor, iremos precisar das 7 principais funções da biblioteca.
InternetAttemptConnect | Tenta localizar uma conexão com a Internet e a estabelecer. |
InternetOpen |
Inicializa a estrutura para o trabalho das funções da biblioteca WinInet. Essa função deve ser ativada antes de quaisquer outras funções da biblioteca. |
InternetConnect | Abre o recurso especificado pelos endereços HTTP URL ou FTP. Retorna o descritor para uma conexão aberta |
HttpOpenRequest | Cria um descritor para as solicitações HTTP para configurar uma conexão |
HttpSendRequest | Envia uma consulta usando o descritor criado |
InternetReadFile | Lê os dados recebidos do servidor após a consulta ter sido processada |
InternetCloseHandle | Libera o descritor transferido |
Uma descrição detalhada de todas as funções e seus parâmetros deve ser encontrada no sistema de ajuda MSDN.
A declaração das funções permaneceram as mesmas como no MQL4, com a exceção do uso das ativações do Unicode e das linhas de transferência pelo link.
#import "wininet.dll" int InternetAttemptConnect(int x); int InternetOpenW(string &sAgent,int lAccessType,string &sProxyName,string &sProxyBypass,int lFlags); int InternetConnectW(int hInternet,string &szServerName,int nServerPort,string &lpszUsername,string &lpszPassword,int dwService,int dwFlags,int dwContext); int HttpOpenRequestW(int hConnect,string &Verb,string &ObjectName,string &Version,string &Referer,string &AcceptTypes,uint dwFlags,int dwContext); int HttpSendRequestW(int hRequest,string &lpszHeaders,int dwHeadersLength,uchar &lpOptional[],int dwOptionalLength); int HttpQueryInfoW(int hRequest,int dwInfoLevel,int &lpvBuffer[],int &lpdwBufferLength,int &lpdwIndex); int InternetReadFile(int hFile,uchar &sBuffer[],int lNumBytesToRead,int &lNumberOfBytesRead); int InternetCloseHandle(int hInet); #import //To make it clear, we will use the constant names from wininet.h. #define OPEN_TYPE_PRECONFIG 0 // use the configuration by default #define FLAG_KEEP_CONNECTION 0x00400000 // do not terminate the connection #define FLAG_PRAGMA_NOCACHE 0x00000100 // no cashing of the page #define FLAG_RELOAD 0x80000000 // receive the page from the server when accessing it #define SERVICE_HTTP 3 // the required protocol
Uma descrição detalhada dos sinalizadores é localizada na mesma seção MSDN para cada uma das funções. Se você desejar ver a declaração de outras constantes e funções, então você pode baixar o arquivo original wininet.h, localizado nos anexos do artigo.
1. Guias para criar e apagar a sessão de internet
A primeira coisa que devemos fazer é criar uma sessão e abrir uma conexão com o host. Uma sessão deve ser criada preferencialmente apenas uma vez durante a inicialização do programa (por exemplo, na função OnInit). Ou isso pode ser feito no começo da inicialização do Conselheiro Especialista, mas é importante certificar-se de que a criação bem sucedida foi feita apenas uma vez antes do fechamento da sessão. E isso não deve ser invocado repetido e sem necessidade, com cada nova iteração da implementação OnStart ou OnTimer. é importante evitar ativações frequentes e a criação das estruturas necessárias para cada ativação.
Portanto, usaremos apenas a instância da classe global para descrever a sessão e as descrições da conexão.
string Host; // host name int Port; // port int Session; // session descriptor int Connect; // connection descriptor bool MqlNet::Open(string aHost,int aPort) { if(aHost=="") { Print("-Host is not specified"); return(false); } // checking the DLL resolution in the terminal if(!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) { Print("-DLL is not allowed"); return(false); } // if the session was identifies, then we close if(Session>0 || Connect>0) Close(); // record of attempting to open into the journal Print("+Open Inet..."); // if we were not able to check for the presence of an Internet connection, then we exit if(InternetAttemptConnect(0)!=0) { Print("-Err AttemptConnect"); return(false); } string UserAgent="Mozilla"; string nill=""; // open a session Session=InternetOpenW(UserAgent,OPEN_TYPE_PRECONFIG,nill,nill,0); // if we were not able to open a session, then exit if(Session<=0) { Print("-Err create Session"); Close(); return(false); } Connect=InternetConnectW(Session,aHost,aPort,nill,nill,SERVICE_HTTP,0,0); if(Connect<=0) { Print("-Err create Connect"); Close(); return(false); } Host=aHost; Port=aPort; // otherwise all attempts were successful return(true); }
Após a inicialização os descritores Session e Connect podem ser usados em todas as seguintes funções. Uma vez que todo o trabalho estiver completo e os programas MQL estiverem desinstalados, eles devem ser removidos. Isso é feito pelo uso da função InternetCloseHandle.
void MqlNet::CloseInet() { Print("-Close Inet..."); if(Session>0) InternetCloseHandle(Session); Session=-1; if(Connect>0) InternetCloseHandle(Connect); Connect=-1; }
Atenção! Ao trabalhar com as funções de internet, é necessário disponibilizar todos os descritores derivados delas, usando a InternetCloseHandle.
2. Enviando uma solicitação ao servidor e recebendo a página
Para enviar uma solicitação e receber uma página em resposta à solicitação, iremos precisar das três funções restantes HttpOpenRequest, HttpSendRequest и InternetReadFile. A essência do recebimento da página em resposta à uma solicitação é basicamente o simples processo de salvar seu conteúdo em um arquivo local.
Pela conveniência de trabalhar com solicitações e conteúdos criaremos duas funções universais.
Enviando uma solicitação:
bool MqlNet::Request(string Verb,string Object,string &Out,bool toFile=false,string addData="",bool fromFile=false) { if(toFile && Out=="") { Print("-File is not specified "); return(false); } uchar data[]; int hRequest,hSend,h; string Vers="HTTP/1.1"; string nill=""; if(fromFile) { if(FileToArray(addData,data)<0) { Print("-Err reading file "+addData); return(false); } } // read file in the array else StringToCharArray(addData,data); if(Session<=0 || Connect<=0) { Close(); if(!Open(Host,Port)) { Print("-Err Connect"); Close(); return(false); } } // create a request descriptor hRequest=HttpOpenRequestW(Connect,Verb,Object,Vers,nill,nill,FLAG_KEEP_CONNECTION|FLAG_RELOAD|FLAG_PRAGMA_NOCACHE,0); if(hRequest<=0) { Print("-Err OpenRequest"); InternetCloseHandle(Connect); return(false); } // send request // headline for request string head="Content-Type: application/x-www-form-urlencoded"; // sent file hSend=HttpSendRequestW(hRequest,head,StringLen(head),data,ArraySize(data)-1); if(hSend<=0) { Print("-Err SendRequest"); InternetCloseHandle(hRequest); Close(); } // read the page ReadPage(hRequest,Out,toFile); // close all handles InternetCloseHandle(hRequest); InternetCloseHandle(hSend); return(true); }
Os parâmetros da função MqlNet:: Solicitação:
- string Verb – tipo de solicitação “GET” ou “POST”;
- string Object – nome da página com sua transmissão nos parâmetros;
- string &Out – linha para qual a resposta é recebida;
- bool toFile – se toFile=true, então Out indica o nome do arquivo onde a resposta deve ser recebida.
- string addData - dados adicionais;
- bool fromFile - se fromFile = true, então addData é o nome do arquivo que precisa ser enviado.
Lendo os conteúdos do descritor recebido
void MqlNet::ReadPage(int hRequest,string &Out,bool toFile) { // read the page uchar ch[100]; string toStr=""; int dwBytes,h; while(InternetReadFile(hRequest,ch,100,dwBytes)) { if(dwBytes<=0) break; toStr=toStr+CharArrayToString(ch,0,dwBytes); } if(toFile) { h=FileOpen(Out,FILE_BIN|FILE_WRITE); FileWriteString(h,toStr); FileClose(h); } else Out=toStr; }
Os parâmetros da função MqlNet:: ReadPage:
- Int hRequest - descritor da solicitação a partir do qual os dados são lidos;
- string &Out – linha para qual a resposta é recebida;
- bool toFile - se oFile = true, então Out é o nome do arquivo onde a resposta será recebida.
E reunindo tudo isso em um, iremos obter uma classe de biblioteca MqlNet para trabalhar com a internet.
class MqlNet { string Host; // host name int Port; // port int Session; // session descriptor int Connect; // connection descriptor public: MqlNet(); // class constructor ~MqlNet(); // destructor bool Open(string aHost,int aPort); // create a session and open a connection void Close(); // close session and connection bool Request(string Verb,string Request,string &Out,bool toFile=false,string addData="",bool fromFile=false); // send request bool OpenURL(string URL,string &Out,bool toFile); // somply read the page into the file or the variable void ReadPage(int hRequest,string &Out,bool toFile); // read the page int FileToArray(string FileName,uchar &data[]); // copy the file into the array for sending };
Essas são basicamente todas as funções necessárias que provavelmente satisfazem as necessidades diversificadas para trabalhar com a internet. Examine os exemplos de seu uso.
Exemplo 1. Transferência automática dos programas MQL dentro das pastas das plataformas. MetaGrabber script
Para iniciar nosso teste do trabalho da classe, vamos começar com as tarefas mais fáceis - ler a página e salvar seu conteúdo em uma pasta especificada. Mas uma simples leitura das páginas é improvável ser interessante, então para ganhar algo do trabalho do script, vamos atribuir a ele uma função do grabber dos programas mql dos sites. A tarefa do MetaGrabber script será:
- a análise do URL e a separação dele dentro do host, da solicitação, e do nome do arquivo;
- enviar uma solicitação ao host, receber e salvar o arquivo dentro da pasta da plataforma \\ Files;
- movê-la da File para uma das pastas de dados solicitada:
\Experts, \Indicators, \Scripts, \Include, \Libraries, \Tester(set), \Templates.
Para resolver o segundo problema usamos a classe MqlNet. Para a terceira tarefa usamos a função MoveFileEx da Kernel32.dll
#import "Kernel32.dll" bool MoveFileExW(string &lpExistingFileName, string &lpNewFileName, int dwFlags); #import "Kernel32.dll"
Para o primeiro problema, vamos fazer uma função de serviço pequena de análise da linha URL.
Precisamos alocar três linhas do endereço: o host, o caminho para o site, e o nome do arquivo.
Por exemplo, na linha http://www.mysite.com/folder/page.html
- o host = www.mysite.com
- a solicitação = / folder / page.html
- o nome do arquivo = page.html
No caso de CodeBase no site do MQL5, os caminhos tem a mesma estrutura. Por exemplo, o caminho para a biblioteca ErrorDescription.mq5 na página https://www.mql5.com/ru/code/79 aparece como http://p.mql5.com/data/18/79/ErrorDescription.mqh. Esse caminho é facilmente obtido clicando com o botão direito no link e selecionando "Copiar Link". Assim, o URL é separado em duas partes, uma para a solicitação e outra para o nome do arquivo pela conveniência de armazenamento do arquivo.
- Host = p.mql5.com
- solicitação = / data/18/79/5/ErrorDescription.mqh
- nome do arquivo = ErrorDescription.mqh
Esse é o tipo de análise de linha que a seguinte função ParseURL estará lidando.
void ParseURL(string path,string &host,string &request,string &filename) { host=StringSubstr(URL,7); // removed int i=StringFind(host,"/"); request=StringSubstr(host,i); host=StringSubstr(host,0,i); string file=""; for(i=StringLen(URL)-1; i>=0; i--) if(StringSubstr(URL,i,1)=="/") { file=StringSubstr(URL,i+1); break; } if(file!="") filename=file; }
Nos parâmetros externos do script (roteiro) faremos apenas dois parâmetros - URL (caminho do arquivo mql5) e o tipo de pasta do posicionamento posterior - ou seja, em qual pasta da plataforma você deseja colocá-lo.
Como resultado, obtemos um script breve mas muito útil.
//+------------------------------------------------------------------+ //| MetaGrabber.mq5 | //| Copyright © 2010 www.fxmaster.de | //| Coding by Sergeev Alexey | //+------------------------------------------------------------------+ #property copyright "www.fxmaster.de © 2010" #property link "www.fxmaster.de" #property version "1.00" #property description "Download files from internet" #property script_show_inputs #include <InternetLib.mqh> #import "Kernel32.dll" bool MoveFileExW(string &lpExistingFileName,string &lpNewFileName,int dwFlags); #import #define MOVEFILE_REPLACE_EXISTING 0x1 enum _FolderType { Experts=0, Indicators=1, Scripts=2, Include=3, Libraries=4, Files=5, Templates=6, TesterSet=7 }; input string URL=""; input _FolderType FolderType=0; //------------------------------------------------------------------ OnStart int OnStart() { MqlNet INet; // variable for working in the Internet string Host,Request,FileName="Recieve_"+TimeToString(TimeCurrent())+".mq5"; // parse url ParseURL(URL,Host,Request,FileName); // open session if(!INet.Open(Host,80)) return(0); Print("+Copy "+FileName+" from http://"+Host+" to "+GetFolder(FolderType)); // obtained file if(!INet.Request("GET",Request,FileName,true)) { Print("-Err download "+URL); return(0); } Print("+Ok download "+FileName); // move to the target folder string to,from,dir; // if there is no need to move it elsewhere if(FolderType==Files) return(0); // from from=TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Files\\"+FileName; // to to=TerminalInfoString(TERMINAL_DATA_PATH)+"\\"; if(FolderType!=Templates && FolderType!=TesterSet) to+="MQL5\\"; to+=GetFolder(FolderType)+"\\"+FileName; // move file if(!MoveFileExW(from,to,MOVEFILE_REPLACE_EXISTING)) { Print("-Err move to "+to); return(0); } Print("+Ok move "+FileName+" to "+GetFolder(FolderType)); return(0); } //------------------------------------------------------------------ GetFolder string GetFolder(_FolderType foldertype) { if(foldertype==Experts) return("Experts"); if(foldertype==Indicators) return("Indicators"); if(foldertype==Scripts) return("Scripts"); if(foldertype==Include) return("Include"); if(foldertype==Libraries) return("Libraries"); if(foldertype==Files) return("Files"); if(foldertype==Templates) return("Profiles\\Templates"); if(foldertype==TesterSet) return("Tester"); return(""); } //------------------------------------------------------------------ ParseURL void ParseURL(string path,string &host,string &request,string &filename) { host=StringSubstr(URL,7); // removed int i=StringFind(host,"/"); request=StringSubstr(host,i); host=StringSubstr(host,0,i); string file=""; for(i=StringLen(URL)-1; i>=0; i--) if(StringSubstr(URL,i,1)=="/") { file=StringSubstr(URL,i+1); break; } if(file!="") filename=file; } //+------------------------------------------------------------------+
Vamos conduzir os experimentos em nossa seção favorita, https://www.mql5.com/pt/code. Os arquivos baixados irão aparecer imediatamente no navegador do editor, e eles podem ser compilados sem reiniciar a plataforma ou o editor. E eles não irão vagar através dos longos caminhos do sistema de arquivos na busca pela pasta desejada para colocar os arquivos dentro.
Atenção! Muitos sites aumentam a segurança contra a maciça transferência de conteúdos, e seu endereço de IP, no caso de transferência maciça, deve ser bloqueado por esse recurso. Portanto, preste bastante atenção ao uso da "máquina" de transferência dos arquivos a partir dos recursos, os quais você frequentemente acessa e não quer ser proibido de usar.
Aqueles que desejam ir mais longe, melhorando o serviço proposto, podem usar o script Clipboard com a interceptação dos conteúdos da área de transferência e transferência automática posterior.
Exemplo 2. Monitorando as citações de múltiplos agentes em um único gráfico.
Então aprendemos a obter arquivos da internet. Agora vamos examinar uma questão mais importante - como enviar e armazenar esses dados no servidor. Para isso, precisamos de um pequeno PHP-script adicional, que será localizado no servidor. Usando a classe escrita MqlNet, criamos um Consultor Especialista para o monitoramento - o MetaArbitrage. A tarefa do especialista juntamente com o PHP-script será:
- Envio de uma solicitação do Consultor Especialista ao servidor;
- formação da página de resposta (PHP) no servidor;
- recepção dessa página através do Consultor Especialista;
- sua análise e a entrega dos resultados para a tela.
O diagrama esquemático da interação entre o MQL-module e o PHP-script aparece a seguir:
Usaremos a classe MqlNet para resolver as seguintes tarefas.
Para evitar a duplicação dos dados, assim como para excluir as cotações desatualizadas - mandaremos 4 parâmetros principais: o nome do servidor do agente (a fonte dos preços correntes), a moeda, o preço e o tempo das cotações em UTC. Por exemplo, uma solicitação para acessar o script a partir dos recursos de nossa empresa é a seguinte:
www.fxmaster.de/metaarbitr.php?server=Metaquotes&pair=EURUSD&bid=1.4512&time=13286794
Esses parâmetros e a cotação real estão armazenados no servidor e serão baixados na página de resposta, junto com todas as outras cotações armazenadas desta moeda.
A conveniência "colateral" dessa troca é que as cotações podem ser enviadas do MT5, assim como do MT4!
A página que é formada a partir do servidor, é um arquivo CSV regular. Nesse script ela aparece assim:
ServerName1; Bid1; Time1
ServerName 2; Bid2; Time2
ServerName 3; Bid3; Time3
…
ServerName N; BidN; TimeN
Mas você pode adicionar a ela seus próprios parâmetros adicionais (por exemplo, o tipo de servidor - demo ou real). Armazenamos esse arquivo CSV e o analisamos linha por linha, com o resultado de uma tabela de valor e os preços da linha mostrados na tela.
O processamento desse arquivo pode ser realizado de várias maneiras diferentes, uma vez que a escolha seja solicitada em cada caso específico. Por exemplo, filtre as cotações recebidas do servidor demo do MetaTrader 4, etc.
As vantagens de usar o servidor de internet são óbvias - você está enviando suas cotações, as quais podem ser recebidas e vistas por um outro negociante. Da mesma forma, você receberá cotações que são enviadas por outros negociantes. Ou seja, a interação entre as plataformas é bilateral, a troca de dados é realizada como mostrada no seguinte esquema:
Esse esquema serve como base para o princípio da troca de informações entre qualquer número de plataformas. Um Conselheiro Especialista MetaArbitrage completo e o PHP-script com comentários pode ser baixado a partir do link nos anexos. Mais sobre as funções usadas em PHP pode ser lido no seguinte site php.su
Exemplo 3. Troca de mensagens (mini chat) dentro da plataforma. MetaChat Consultor Especialista
Vamos nos afastar um pouco das negociações e dos números, e criar um aplicativo que irá nos permitir conversar com várias pessoas ao mesmo tempo sem sair da plataforma. Para fazer isso iremos precisar de mais um script PHP, o qual em geral é muito similar ao anterior. Exceto pelo fato de que neste script, ao invés de analisar cotações de tempo, iremos analisar o número de linhas em um arquivo. A tarefa do Consultor Especialista será:
- Enviar uma linha de texto ao servidor;
- adicionar essa linha dentro do arquivo compartilhado, controlar o tamanho do arquivo, emitir o arquivo de resposta (php);
- receber a conversa atual e exibí-la na tela.
O trabalho do Metachat não irá se diferenciar daquele do Consultor Especialista anterior. O mesmo princípio, e o mesmo arquivo CSV simples para saída.
O MetaChat e o MetaArbitrage são mantidos no site de seus desenvolvedores. Os PHP-scripts para seu trabalho também estão localizados aqui.
Portanto, se você quiser testar um trabalho ou usar esse serviço, você pode acessá-lo através do seguinte link:
MetaСhat - www.fxmaster.de/metachat.php
MetaArbitrage - www.fxmaster.de/metaarbitr.php
Conclusão
E assim nos familiarizamos com as HTTP-requests. Adquirimos a habilidade de enviar e receber os dados através da internet, e de organizar o processo de trabalho mais confortavelmente. Mas quaisquer capacidades podem sempre ser melhoradas. As seguintes podem ser consideradas as novas orientações possíveis de seus melhoramentos:
- ler as notícias ou receber outras informações diretamente na plataforma para a análise dos Consultores Especialistas;
- gerenciamento remoto dos Consultores Especialistas;
- atualizações automáticas dos Consultores Especialistas/indicadores;
- copiadores/conversores de negociações, sinais de envio;
- baixar modelos junto com as luzes e conjuntos de arquivos para os Consultores Especialistas:
- E muito, muito mais...
Neste artigo usamos o tipo GET de solicitações. Elas cumprem adequadamente a tarefa quando você precisa obter um arquivo, ou enviar uma solicitação, com um pequeno número de parâmetros, para uma análise do servidor.
Em nossa próxima lição, iremos olhar cuidadosamente as solicitações POST - enviar arquivos para o servidor ou o compartilhamento de arquivos entre as plataformas, e iremos examinar os exemplos de seu uso.
Recursos úteis
- Conjunto Denver para instalar o servidor Apache + PHP http://www.denwer.ru/
- Um procurador para ver as manchetes enviadas http://www.charlesproxy.com/
- Tipos de solicitações de manchetes http://www.codenet.ru/webmast/php/HTTP-POST.php#part_3_2
- Tipos de solicitações http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
- Tipos de solicitações ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-types
- Descrição do WinHTTP http://msdn.microsoft.com/en-us/library/aa385331%28VS.85%29.aspx
- Descrição da sessão HTTP http://msdn.microsoft.com/en-us/library/aa384322%28VS.85%29.aspx
- Estrutura de uso da HINTERNET http://msdn.microsoft.com/en-us/library/aa383766%28VS.85%29.aspx
- Trabalhando com arquivos http://msdn.microsoft.com/en-us/library/aa364232%28VS.85%29.aspx
- Tipos de dados para transferências ao MQL http://msdn.microsoft.com/en-us/library/aa383751%28VS.85%29.aspx