English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
DLLs을 사용하지 않고 명명된 파이프를 사용하여 MetaTrader 5와 통신하기

DLLs을 사용하지 않고 명명된 파이프를 사용하여 MetaTrader 5와 통신하기

MetaTrader 5 | 11 10월 2021, 16:35
68 0
MetaQuotes
MetaQuotes

소개

많은 개발자가 동일한 문제에 직면하고 있습니다 - 안전하지 않은 DLL을 사용하지 않고 거래 터미널 샌드박스로 이동하는 방법.

가장 쉽고 안전한 방법 중 하나는 정상적인 파일 작업으로 작동하는 표준 명명된 파이프를 사용하는 것입니다. 프로그램 간의 프로세서 간 클라이언트-서버 통신을 구성할 수 있습니다. DLL에 대한 액세스를 사용하는 방법을 설명하는 이 항목에 대해 MetaTrader 5 터미널 간에 통신할 수 있는 DLL 없는 문서 명명된 파이프를 사용하여 MetaTrader 5 터미널 간에 통신할 수 있는 DLL 없는 솔루션에 이미 게시되어 있지만, 클라이언트 터미널의 표준적이고 안전한 기능을 사용합니다.

명명된 파이프에 대한 자세한 내용은 MSDN 라이브러리에서 확인할 수 있지만, C++ 및 MQL5의 실제 예제에 대해 알아보겠습니다. 우리는 서버, 클라이언트, 데이터 교환을 구현한 후 성능을 벤치마킹할 것입니다.


서버 구현

C++에 있는 간단한 서버를 코딩해 보겠습니다. 터미널의 스크립트가 이 서버에 연결되어 데이터를 교환합니다. 서버 코어에는 다음과 같은 WinAPI 함수 집합이 있습니다:

명명된 파이프가 열리면 정기적인 파일 읽기/쓰기 작업에 사용할 수 있는 파일 핸들을 반환합니다. 그 결과 네트워크 작업에 대한 특별한 지식이 필요하지 않은 매우 간단한 메커니즘을 얻을 수 있습니다.

명명된 파이프에는 로컬 및 네트워크라는 한 가지 고유한 특징이 있습니다. 즉, 클라이언트 터미널에서 네트워크 연결을 허용하는 원격 서버를 쉽게 구현할 수 있습니다.

다음은 로컬 서버를 바이트 교환 모드에서 작동하는 전이중 채널로 만드는 간단한 예입니다:

//--- 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

파일 첨부됨 |
pipeclient.mq5 (3.15 KB)
pipeserver.zip (43.59 KB)
거래신호에 가입하는 방법 거래신호에 가입하는 방법
시그널(신호) 서비스는 MetaTrader 4 및 MetaTrader 5와의 소셜 트레이딩을 소개합니다. 이 서비스는 거래 플랫폼에 통합되어 있으며 누구나 전문 거래자의 거래를 쉽게 복사할 수 있습니다. 수천 개의 신호 공급자 중 하나를 선택하고 몇 번의 클릭으로 가입하면 해당 공급자의 거래가 귀하의 계정에 복사됩니다.
다른 애플리케이션에 대한 MetaTrader 5 Quotes을 준비하는 방법 다른 애플리케이션에 대한 MetaTrader 5 Quotes을 준비하는 방법
이 기사에서는 디렉터리 만들기, 데이터 복사, 파일 작성, 마켓워치 또는 공통 목록의 기호 사용 예와 오류 처리 예 등에 대해 설명합니다. 이러한 모든 요소는 사용자 정의 형식으로 데이터를 파일링하기 위한 단일 스크립트로 수집될 수 있습니다.
시장 제품에 대한 올바른 설명을 작성하는 방법 시장 제품에 대한 올바른 설명을 작성하는 방법
MQL5 시장에는 판매할 제품이 많지만 일부 제품 설명에는 미흡한 점이 많습니다. 많은 텍스트는 일반 거래자들이 이해할 수 없기 때문에 개선이 필요한 것이 분명합니다. 이 글은 당신의 제품을 좋게 보는 데 도움이 될 것입니다. 저희의 권장사항을 사용하여 고객에게 귀사가 정확히 무엇을 판매하는지 쉽게 보여줄 수 있는 눈길을 끄는 설명을 작성하십시오.
MetaTrader Market에서 거래용 로봇을 구입하여 설치하는 방법은 무엇입니까? MetaTrader Market에서 거래용 로봇을 구입하여 설치하는 방법은 무엇입니까?
MetaTrader 마켓의 제품은 MQL5.com 웹 사이트에서 구입하거나 MetaTrader 4 및 MetaTrader 5 트레이딩 플랫폼에서 직접 구입할 수 있습니다. 거래 스타일에 맞는 상품을 선택하고 선호하는 결제 방법으로 결제한 후 제품을 활성화하세요.