
DLLs을 사용하지 않고 명명된 파이프를 사용하여 MetaTrader 5와 통신하기
소개
많은 개발자가 동일한 문제에 직면하고 있습니다 - 안전하지 않은 DLL을 사용하지 않고 거래 터미널 샌드박스로 이동하는 방법.
가장 쉽고 안전한 방법 중 하나는 정상적인 파일 작업으로 작동하는 표준 명명된 파이프를 사용하는 것입니다. 프로그램 간의 프로세서 간 클라이언트-서버 통신을 구성할 수 있습니다. DLL에 대한 액세스를 사용하는 방법을 설명하는 이 항목에 대해 MetaTrader 5 터미널 간에 통신할 수 있는 DLL 없는 문서 명명된 파이프를 사용하여 MetaTrader 5 터미널 간에 통신할 수 있는 DLL 없는 솔루션에 이미 게시되어 있지만, 클라이언트 터미널의 표준적이고 안전한 기능을 사용합니다.
명명된 파이프에 대한 자세한 내용은 MSDN 라이브러리에서 확인할 수 있지만, C++ 및 MQL5의 실제 예제에 대해 알아보겠습니다. 우리는 서버, 클라이언트, 데이터 교환을 구현한 후 성능을 벤치마킹할 것입니다.
서버 구현
C++에 있는 간단한 서버를 코딩해 보겠습니다. 터미널의 스크립트가 이 서버에 연결되어 데이터를 교환합니다. 서버 코어에는 다음과 같은 WinAPI 함수 집합이 있습니다:
- CreateNamedPipe - 명명된 파이프를 만듭니다.
- ConnectNamedPipe - 서버가 클라이언트 연결을 대기할 수 있도록 합니다.
- WriteFile - 파이프에 데이터를 씁니다.
- ReadFile - 파이프에서 데이터를 읽습니다.
- FlushFileBuffers - 누적된 버퍼를 플러시합니다.
- DisconnectNamedPipe - 서버 연결을 끊습니다.
- CloseHandle - 핸들을 닫습니다.
명명된 파이프가 열리면 정기적인 파일 읽기/쓰기 작업에 사용할 수 있는 파일 핸들을 반환합니다. 그 결과 네트워크 작업에 대한 특별한 지식이 필요하지 않은 매우 간단한 메커니즘을 얻을 수 있습니다.
명명된 파이프에는 로컬 및 네트워크라는 한 가지 고유한 특징이 있습니다. 즉, 클라이언트 터미널에서 네트워크 연결을 허용하는 원격 서버를 쉽게 구현할 수 있습니다.
다음은 로컬 서버를 바이트 교환 모드에서 작동하는 전이중 채널로 만드는 간단한 예입니다:
//--- open CPipeManager manager; if(!manager.Create(L"\\\\.\\pipe\\MQL5.Pipe.Server")) return(-1); //+------------------------------------------------------------------+ //| Create named pipe | //+------------------------------------------------------------------+ bool CPipeManager::Create(LPCWSTR pipename) { //--- check parameters if(!pipename || *pipename==0) return(false); //--- close old Close(); //--- create named pipe m_handle=CreateNamedPipe(pipename,PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES,256*1024,256*1024,1000,NULL); if(m_handle==INVALID_HANDLE_VALUE) { wprintf(L"Creating pipe '%s' failed\n",pipename); return(false); } //--- ok wprintf(L"Pipe '%s' created\n",pipename); return(true); }
클라이언트 연결을 얻으려면 ConnectNamedPipe 함수를 사용해야 합니다:
//+------------------------------------------------------------------+ //| Connect client | //+------------------------------------------------------------------+ bool CPipeManager::ConnectClient(void) { //--- pipe exists? if(m_handle==INVALID_HANDLE_VALUE) return(false); //--- connected? if(!m_connected) { //--- connect if(ConnectNamedPipe(m_handle,NULL)==0) { //--- client already connected before ConnectNamedPipe? if(GetLastError()!=ERROR_PIPE_CONNECTED) return(false); //--- ok } m_connected=true; } //--- return(true); }
데이터 교환은 다음과 같은 4가지 간단한 기능을 사용하여 구성됩니다.
- CPipeManager::Send(void *data,size_t data_size)
- CPipeManager::Read(void *data,size_t data_size)
- CPipeManager::SendString(LPCSTR command)
- CPipeManager::ReadString(LPSTR answer,size_t answer_maxlen)
MQL5 호환 모드에서 데이터를 이진 데이터 또는 ANSI 텍스트 문자열로 송수신할 수 있습니다. 또한 MQL5의 CFilePipe는 기본적으로 파일을 ANSI 모드로 열기 때문에 문자열이 수신 및 전송 시 유니코드로 자동 변환됩니다. MQL5 프로그램이 유니코드 모드(FILE_UNICODE)로 파일을 여는 경우 유니코드 문자열을 교환할 수 있습니다(BOM 시작 서명 포함).
클라이언트 구현
우리는 MQL5로 클라이언트를 작성할 것입니다. 표준 라이브러리의 CFilePipe 클래스를 사용하여 정기적인 파일 작업을 수행할 수 있습니다. 이 클래스는 CFileBin과 거의 동일하지만, 이 데이터를 읽기 전에 가상 파일에서 데이터 가용성에 대한 중요한 검증 내용이 포함되어 있습니다.
//+------------------------------------------------------------------+ //| Wait for incoming data | //+------------------------------------------------------------------+ bool CFilePipe::WaitForRead(const ulong size) { //--- check handle and stop flag while(m_handle!=INVALID_HANDLE && !IsStopped()) { //--- enough data? if(FileSize(m_handle)>=size) return(true); //--- wait a little Sleep(1); } //--- failure return(false); } //+------------------------------------------------------------------+ //| Read an array of variables of double type | //+------------------------------------------------------------------+ uint CFilePipe::ReadDoubleArray(double &array[],const int start_item,const int items_count) { //--- calculate size uint size=ArraySize(array); if(items_count!=WHOLE_ARRAY) size=items_count; //--- check for data if(WaitForRead(size*sizeof(double))) return FileReadArray(m_handle,array,start_item,items_count); //--- failure return(0); }
명명된 파이프는 로컬 및 네트워크 모드 구현에서 상당한 차이가 있습니다. 이러한 확인이 없으면 네트워크 모드 작업은 대량의 데이터(64K 이상)를 전송할 때 항상 읽기 오류를 반환합니다.
'RemoteServerName'이라는 이름의 원격 컴퓨터 또는 로컬 컴퓨터 중 두 가지 검사를 통해 서버에 연결하겠습니다.
void OnStart() { //--- wait for pipe server while(!IsStopped()) { if(ExtPipe.Open("\\\\RemoteServerName\\pipe\\MQL5.Pipe.Server",FILE_READ|FILE_WRITE|FILE_BIN)!=INVALID_HANDLE) break; if(ExtPipe.Open("\\\\.\\pipe\\MQL5.Pipe.Server",FILE_READ|FILE_WRITE|FILE_BIN)!=INVALID_HANDLE) break; Sleep(250); } Print("Client: pipe opened");
데이터 교환
연결에 성공하면 식별 정보가 포함된 문자열이 서버로 전송됩니다. 파일이 ANSI 모드에서 열리기 때문에 유니코드 문자열이 ANSI로 자동 변환됩니다.
//--- send welcome message if(!ExtPipe.WriteString(__FILE__+" on MQL5 build "+IntegerToString(__MQ5BUILD__))) { Print("Client: sending welcome message failed"); return; }
이에 대해 서버는 "Hello from pipe server" 문자열과 정수 1234567890을 전송합니다. 클라이언트가 "Test string" 문자열과 정수 1234567890을 다시 보냅니다.
//--- read data from server string str; int value=0; if(!ExtPipe.ReadString(str)) { Print("Client: reading string failed"); return; } Print("Server: ",str," received"); if(!ExtPipe.ReadInteger(value)) { Print("Client: reading integer failed"); return; } Print("Server: ",value," received"); //--- send data to server if(!ExtPipe.WriteString("Test string")) { Print("Client: sending string failed"); return; } if(!ExtPipe.WriteInteger(value)) { Print("Client: sending integer failed"); return; }
네, 간단한 데이터 교환은 끝났습니다. 이제 성능 벤치마크가 필요합니다.
성능 벤치마크
테스트로 1기가바이트의 데이터를 서버에서 클라이언트로 8메가바이트 블록 단위로 배열하여 전송한 후 블록의 정확성을 확인하고 전송 속도를 측정합니다.
다음은 C++ 서버의 코드입니다:
//--- benchmark double volume=0.0; double *buffer=new double[1024*1024]; // 8 Mb wprintf(L"Server: start benchmark\n"); if(buffer) { //--- fill the buffer for(size_t j=0;j<1024*1024;j++) buffer[j]=j; //--- send 8 Mb * 128 = 1024 Mb to client DWORD ticks=GetTickCount(); for(size_t i=0;i<128;i++) { //--- setup guard signatures buffer[0]=i; buffer[1024*1024-1]=i+1024*1024-1; //--- if(!manager.Send(buffer,sizeof(double)*1024*1024)) { wprintf(L"Server: benchmark failed, %d\n",GetLastError()); break; } volume+=sizeof(double)*1024*1024; wprintf(L"."); } wprintf(L"\n"); //--- read confirmation if(!manager.Read(&value,sizeof(value)) || value!=12345) wprintf(L"Server: benchmark confirmation failed\n"); //--- show statistics ticks=GetTickCount()-ticks; if(ticks>0) wprintf(L"Server: %.0lf Mb sent at %.0lf Mb per second\n",volume/1024/1024,volume/1024/ticks); //--- delete[] buffer; }
MQL5 클라이언트에서 다음을 수행합니다:
//--- benchmark double buffer[]; double volume=0.0; if(ArrayResize(buffer,1024*1024,0)==1024*1024) { uint ticks=GetTickCount(); //--- read 8 Mb * 128 = 1024 Mb from server for(int i=0;i<128;i++) { uint items=ExtPipe.ReadDoubleArray(buffer); if(items!=1024*1024) { Print("Client: benchmark failed after ",volume/1024," Kb, ",items," items received"); break; } //--- check the data if(buffer[0]!=i || buffer[1024*1024-1]!=i+1024*1024-1) { Print("Client: benchmark invalid content"); break; } //--- volume+=sizeof(double)*1024*1024; } //--- send confirmation value=12345; if(!ExtPipe.WriteInteger(value)) Print("Client: benchmark confirmation failed "); //--- show statistics ticks=GetTickCount()-ticks; if(ticks>0) printf("Client: %.0lf Mb received at %.0lf Mb per second\n",volume/1024/1024,volume/1024/ticks); //--- ArrayFree(buffer); }
전송 중에 오류가 없는지 확인하기 위해 전송된 블록의 첫 번째 요소와 마지막 요소를 확인합니다. 또한 전송이 완료되면 클라이언트는 성공적인 데이터 수신에 대한 확인 신호를 서버로 보냅니다. 최종 확인을 사용하지 않을 경우, 당사자 중 한 명이 너무 일찍 연결을 끊으면 데이터가 손실되기 쉽습니다.
PipeServer.exe 서버를 로컬로 실행하고 PipeClient.mq5 스크립트를 차트에 연결합니다.
PipeServer.exe | PipeClient.mq5 |
---|---|
MQL5 Pipe Server Copyright 2012, MetaQuotes Software Corp. Pipe '\\.\pipe\MQL5.Pipe.Server' created Client: waiting for connection... Client: connected as 'PipeClient.mq5 on MQL5 build 705' Server: send string Server: send integer Server: read string Server: 'Test string' received Server: read integer Server: 1234567890 received Server: start benchmark ...................................................... ........ Server: 1024 Mb sent at 2921 Mb per second | PipeClient (EURUSD,H1) Client: pipe opened PipeClient (EURUSD,H1) Server: Hello from pipe server received PipeClient (EURUSD,H1) Server: 1234567890 received PipeClient (EURUSD,H1) Client: 1024 Mb received at 2921 Mb per second |
로컬 교환의 경우 전송 속도가 초당 3기가바이트에 육박할 정도로 놀라울 정도입니다. 즉, 명명된 파이프를 사용하여 거의 모든 양의 데이터를 MQL5 프로그램으로 전송할 수 있습니다.
이제 일반적인 1기가비트 LAN의 데이터 전송 성능을 벤치마킹해 보겠습니다.
PipeServer.exe | PipeClient.mq5 |
---|---|
MQL5 Pipe Server Copyright 2012, MetaQuotes Software Corp. Pipe '\\.\pipe\MQL5.Pipe.Server' created Client: waiting for connection... Client: connected as 'PipeClient.mq5 on MQL5 build 705' Server: send string Server: send integer Server: read string Server: 'Test string' received Server: read integer Server: 1234567890 received Server: start benchmark ...................................................... ........ Server: 1024 Mb sent at 63 Mb per second | PipeClient (EURUSD,H1) Client: pipe opened PipeClient (EURUSD,H1) Server: Hello from pipe server received PipeClient (EURUSD,H1) Server: 1234567890 received PipeClient (EURUSD,H1) Client: 1024 Mb received at 63 Mb per second |
로컬 네트워크에서 1기가바이트의 데이터가 초당 63메가바이트의 속도로 전송되어 매우 좋습니다. 실제로 기가비트 네트워크 최대 대역폭의 63%입니다.
결론
MetaTrader 5 트레이딩 플랫폼의 보호 시스템에서는 MQL5 프로그램을 샌드박스 밖에서 실행할 수 없으므로 신뢰할 수 없는 Expert Advisor를 사용할 경우 위협으로부터 거래자를 보호할 수 있습니다. 명명된 파이프를 사용하면 타사 소프트웨어와의 통합을 쉽게 만들고 외부에서 EA를 관리할 수 있습니다. 안전하게 할 수 있습니다.
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/503



