English 中文 Español Deutsch 日本語 Português
Советник MetaTrader 4 обменивается информацией с внешним миром

Советник MetaTrader 4 обменивается информацией с внешним миром

MetaTrader 4Примеры | 22 января 2013, 09:52
15 102 17
more
[Удален]

Введение

Разработан программный инструмент, который обеспечивает советника MetaTrader 4 средствами создания как сервера, так и клиентов. Клиенты могут устанавливать соединения как с собственными серверами, так и с серверами любых других типов, обеспечивающих соединения типа точка-точка. Предлагаемый программный инструмент состоит из двух компонент:

  • NetEventsProc.exe - представляет собой Windows процесс, работающий в фоновом режиме (без консоли) и запускаемый (завершаемый) по мере необходимости по инициативе второй компоненты NetEventsProcDLL.dll. По запросу прикладных программ может создавать для них как сервер, так и клиентов: как для собственных серверов, так и для серверов любого другого типа, обеспечивающих соединения типа точка-точка. К примеру, вы можете создать клиентов, обменивающихся информацией с сайтами, разумеется, если сможете поддерживать HTTP-протокол.

  • NetEventsProcDLL.dll - представляет собой интерфейс между прикладной программой, нуждающейся в услугах процесса NetEventsProc.exe, и самим процессом NetEventsProc.exe. Благодаря DLL интерфейсу, любые программы, написанные на любом языке программирования, могут использовать этот программный инструмент для взаимного обмена информацией. Советник MetaTrader 4 является лишь примером возможного применения данного программного инструмента.

Дальнейшее изложение построено следующим образом:

  • Быстрый старт: сначала на простых, затем на более сложных примерах демонстрируется практическое применение предлагаемого программного инструмента.

  • Спецификация DLL интерфейса: приводится подробное, со всеми формальностями, описание интерфейсных DLL функций и услуг, предоставляемых процессом NetEventsProc.exe при обращении из прикладных программ к этим функциям.

  • Реализация проекта: по возможности подробно рассматриваются детали реализации данного проекта.

В прикрепленном архиве NetServerClient.zip находятся два проекта Microsoft Visual Studio 2010 Ultimate: NetEventsProc - для построения NetEventsProc.exe, и NetEventsProcDLL - для построения NetEventsProcDLL.dll. Исходные коды подробно прокомментированы, так что каждый, при необходимости, сможет разобраться в деталях реализации и, при желании, адаптировать проекты под свои специфические потребности.

NetEventsProc.exe реализует сервер и клиентов, используя асинхронные сокеты. Для перевода сокетов в асинхронный режим используется один из возможных методов работы в асинхронном режиме, а именно - привязка сокетов к сетевым событиям WSAEventSelect (h_Socket, h_Event, FD_ALL_EVENTS).

Данный проект базируется на фундаментальной работе большого Мастера Elmue.


1. Быстрый старт

1.1. Архив exe.zip

Распакуйте этот архив. В данном архиве вы найдете следующие компоненты:

  • NetEventsProcDLL.dll - необходимо поместить в "C:\Windows\system32\".

  • NetEventsProc.exe - создайте папку "C:\NetEventsProc\" и поместите в нее эту компоненту. NetEventsProcDLL.dll будет искать модуль NetEventsProc.exe именно в этой папке!

Нижеследующие компоненты этого архива обеспечивают механизм импорта DLL функций из NetEventsProcDLL.dll в прикладные программы:

  • ImportNetEventsProcDLL.mqh - прототипы функций, импортируемых в советник MetaТrader 4 из NetEventsProcDLL.dll. Этот файл необходимо поместить в папку терминала "МetaTrader 4\experts\include\".

  • cNetEventsProcDLL.h - определение С++ класса, содержащего прототипы всех DLL-функций из NetEventsProcDLL.dll. Подключение этого класса к С++ программе позволяет импортировать все DLL - функции из NetEventsProcDLL.dll. Для программ на других языках программирования необходимо либо импортировать каждую из функций этого класса по отдельности, либо соответствующим образом переписать определение этого класса.

  • NetEventsProcDLL.lib - модуль, включаемый в программы, импортирующие DLL функции из NetEventsProcDLL.dll в режиме load-time dynamic linking (compile with: /EHsc/link NetEventsProcDLL.lib).

На этом, собственно, установка завершена. Теперь можно писать советники МetaTrader 4 и прикладные программы на любых языках программирования, используя DLL функции для создания сервера и клиентов.

Чтобы в дальнейшем не отвлекаться, приведем прямо здесь исходные тексты для ImportNetEventsProcDLL.mqh и для cNetEventsProcDLL.h. Заголовочный файл ImportNetEventsProcDLL.mqh содержит прототипы импортируемых DLL-функций программы NetEventsProcDLL.dll и две вспомогательные служебные функции:

string GetErrMsg(int s32_Error);
string FormatIP(int IP);

Функция GetErrMsg конвертирует коды возврата DLL-функций в текстовый вид. Функция FormatIP конвертирует двоичное представление IP адреса в привычный текстовый формат вида "93.127.110.161". Файл ImportNetEventsProcDLL.mqh необходимо поместить в папку терминала "MetaTrader 4\experts\include\".

Вот как выглядит исходный код ImportNetEventsProcDLL.mqh (приводится только часть файла, относящаяся непосредственно к описанию прототипов импортируемых DLL функций):

// ImportNetEventsProcDLL.mqh

#import "NetEventsProcDLL.dll"
// Only for Clients:
int ConnectTo(string  ps8_ServerIP,             // in - string ps8_ServerIP = "0123456789123456"
              int     s32_Port,                 // in 
              int&    h_Client[]);              // out - int h_Client[1]
                 
int ConnectClose(int h_Client);                 // in
//
// Only for Server:
int ServerOpen(int  s32_Port);                  // in

int GetAllConnections(int& ph_Client[],         // out - int ph_Client[62]
                      int& ps32_ClientIP[],     // out - int ps32_ClientIP[62]
                      int& ps32_ClientCount[]); // out - int ps32_ClientCount[1]
                       
int DisconnectClient(int h_Client);             // in

int ServerClose();
//
// For both: Clients and Server
int SendToInt   (int  h_Client,             // in
                 int& ps32_SendBuf[],       // in
                 int  s32_SendBufLen);      // in - SendBuf[] array size in int element 
                 
int SendToDouble(int     h_Client,          // in
                 double& pd_SendBuf[],      // in
                 int     s32_SendBufLen);   // in - SendBuf[] array size in double element 
                 
int SendToString(int    h_Client,           // in
                 string ps8_SendBuf,        // in
                 int    s32_SendBufLen);    // in - SendBuf string size in char element
                 

int ReadFromInt   (int h_Client,            // in
                   int& ps32_ReadBuf[],     // in 
                   int  s32_ReadBufLen,     // in  - ReadBuf[] array size in int element
                   int& ps32_ReadLen[]);    // out - int ps32_ReadLen[1] - count of actually read data in int element
                  
int ReadFromDouble(int     h_Client,        // in
                   double& pd_ReadBuf[],    // in
                   int     s32_ReadBufLen,  // in  - ReadBuf[] array size in double element
                   int&    ps32_ReadLen[]); // out - int ps32_ReadLen[1] - count of actually read data in double element
                   
int ReadFromString(int     h_Client,        // in
                   string  ps8_ReadBuf,     // in
                   int     s32_ReadBufLen,  // in  - ReadBuf   string size in char element
                   int&    ps32_ReadLen[]); // out - int ps32_ReadLen[1] - count of actually read data in char element
//                   
#import
//***************************************************************************************
...
...
...
// Get a human readable error message for an API error code
string GetErrMsg(int s32_Error)
{
   ...
   .. 
}

// Convert DWORD IP to string IP
string FormatIP(int IP)
{
   ...
   ...
   ...
}       

Файл cNetEventsProcDLL.h содержит С++ определение класса, включающего все DLL функции, импортируемые из NetEventsProcDLL.dll. Вот исходный код этого файла:

//+---------------------------------------------------------------------------+
//|                                            cNetEventsProcDLL.h            |
//|                      Copyright © 2012, https://www.mql4.com/ ru/users/more  |

//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+

// cNetEventsProcDLL.h

#pragma once

#define EXPFUNC __declspec(dllexport)
        
class cNetEventsProcDLL
{
public:
    static BOOL MessageDLL_PROCESS_ATTACH(void);
    static BOOL MessageDLL_PROCESS_DETACH(void);
//*******************************************************************************************************************
    static EXPFUNC int __stdcall ConnectTo(char* ps8_ServerIP, //in - ps8_ServerIP = "0123456789123456"
                                           int   s32_Port,     //in 
                                           int*  ph_Client);   //out - int ph_Client[1]

    static EXPFUNC int __stdcall ConnectClose(int h_Client);   //in 

    static EXPFUNC int __stdcall ServerOpen(int s32_Port);     //in

    static EXPFUNC int __stdcall GetAllConnections(int* ph_Client,         // out - int ph_Client[62]
                                                   int* ps32_ClientIP,     // out - int ps32_ClientIP[62]
                                                   int* ps32_ClientCount); // out - int ps32_ClientCount[1]
    
    static EXPFUNC int __stdcall DisconnectClient(SOCKET h_Client);        // in 

    static EXPFUNC int __stdcall ServerClose();

    static EXPFUNC int __stdcall SendToInt(SOCKET h_Client,             // in
                                           int*   ps32_SendBuf,         // in
                                           int    s32_SendBufLen);      // in - SendBuf[] array size in int element

    static EXPFUNC int __stdcall SendToDouble(SOCKET  h_Client,         // in
                                              double* pd_SendBuf,       // in
                                              int     s32_SendBufLen);  // in - SendBuf[] array size in double element

    static EXPFUNC int __stdcall SendToString(SOCKET h_Client,          // in
                                              char*  ps8_SendBuf,       // in
                                              int    s32_SendBufLen);   // SendBuf string size in char element

    static EXPFUNC int __stdcall ReadFromInt(SOCKET h_Client,           // in
                                             int*   ps32_ReadBuf,       // in
                                             int    s32_ReadBufLen,     // ReadBuf[] array size in int element
                                             int*   ps32_ReadLen);      // out - int ps32_ReadLen[1] - actual count of read data in int element

    static EXPFUNC int __stdcall ReadFromDouble(SOCKET  h_Client,       // in
                                                double* pd_ReadBuf,     // in
                                                int     s32_ReadBufLen, // ReadBuf[] array size in double element
                                                int*    ps32_ReadLen);  // out - int ps32_ReadLen[1] - actual count of read data in double element

    static EXPFUNC int __stdcall ReadFromString(SOCKET h_Client,        // in
                                                char*  ps8_ReadBuf,     // in
                                                int    s32_ReadBufLen,  // ReadBuf[] array size in char element
                                                int*   ps32_ReadLen);   // out - int ps32_ReadLen[1] - actual count of read data in char element
//*******************************************************************************************************************
protected:
    static DWORD SendTo(SOCKET h_Client,   char* ps8_SendBuf, INT s32_SendBufLen);
    static DWORD ReadFrom(SOCKET h_Client, char* ps8_ReadBuf, INT s32_ReadBufLen, INT& s32_ReadLen);
};


1.2. Архив FastStart.zip

В данном архиве содержатся исходные коды всех программ, используемых в демонстрационных примерах. С++ программы представлены как проекты Microsoft Visual Studio 2010 Ultimate - Client и EchoServer. Исходные коды MQL4 программ также присутствуют в этом архиве вместе с файлом ImportNetEventsProcDLL.mqh, с помощью которого выполняется импорт DLL функций в MQL4 программы. Это файл необходимо поместить в папку "MetaTrader 4\experts\include\".

При дальнейшем обсуждении исходные коды всех этих программ приводятся прямо по тексту. Всего рассмотрено 3 примера, в совокупности охватывающих работу со всеми DLL функциями на языках MQL4 и C++:

  • В разделе 1.2.1. демонстрируется обмен информацией между МetaТrader 4 Советником-сервером и С++ Клиентом.

  • В разделе 1.2.2. демонстрируется обмен информацией между С++ Сервером и МetaТrader 4 Советником-клиентом.

  • В разделе 1.2.3. демонстрируется обмен информацией между МetaTrader 4 Советниками, один из которых выполняет роль сервера, обеспечивающего другие МetaТrader 4 Советники-индикаторы (Советники-клиенты) значениями индикатора. Иными словами, реализована раздача значений "засекреченного" индикатора клиентам.


1.2.1. МetaТrader 4 Советник-сервер & С++ Программа-клиент

Рассмотрим такую традиционную задачу по обмену информацией между МetaТrader 4 советником и программой на C++:

  • EchoServer.mq4 - советник выполняет роль эхо-сервера.

  • Client.cpp - С++ программа, выполняет роль клиента для данного советника сервера.

С++ клиент считывает сообщения, вводимые пользователем с консоли, и посылает их советнику. Советник получает эти сообщения, выводит их в окно терминала и посылает назад адресату. Приведем для наглядности рисунок:


Вот как выглядит исходный код МetaTrader 4 советника EchoServer.mq4, выполняющего роль эхо-сервера:

//+---------------------------------------------------------------------------+
//|                                            EchoServer.mq4                 |
//|                      Copyright © 2012, https://www.mql4.com/ ru/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+
#property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more"
#property link      "https://www.mql4.com/ ru/users/more"
#include <ImportNetEventsProcDLL.mqh>
/*int ServerOpen(int  s32_Port); // in
*/
/*int ServerClose();
*/
/*int GetAllConnections(int& ph_Client[],         // out - int ph_Client[62]
                        int& ps32_ClientIP[],     // out - int ps32_ClientIP[62]
                        int& ps32_ClientCount[]); // out - int ps32_ClientCount[1]
*/                      
/*int SendToString(int    h_Client,               // in
                   string ps8_SendBuf,            // in
                   int    s32_SendBufLen);        // in - SendBuf string size in char element
*/ 
/*int ReadFromString(int     h_Client,            // in
                   string    ps8_ReadBuf,         // in
                   int       s32_ReadBufLen,      // in  - ReadBuf   string size in char element
                   int&      ps32_ReadLen[]);     // out - int ps32_ReadLen[1] - count of actually read data in char element     
*/                              
// Globals variables
int s32_Error;
int i;

int s32_Port = 2000;
bool b_ServerOpened = false;

// for GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount)
int ph_Client       [62];
int ps32_ClientIP   [62];
int ps32_ClientCount[1 ];


// for int ReadFromString(h_Client, ps8_Buf, s32_BufLen, ps32_ReadLen)
// for int SendToString  (h_Client, ps8_Buf, s32_BufLen)
string ps8_Buf = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
int    s32_BufLen;
int    ps32_ReadLen[1];
//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
{
   //----
   s32_BufLen = StringLen(ps8_Buf);
   
   if (!b_ServerOpened)
   {
      s32_Error = ServerOpen(s32_Port);
      Print("ServerOpen() return is: ",GetErrMsg(s32_Error));
      
      if (s32_Error == OK)
      {
         b_ServerOpened = true;
         Print("Server is Opened and Waiting fo Client connection requests...");
      }
   }
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
//----
   if (b_ServerOpened)
   {
      s32_Error = ServerClose();
      Print("ServerClose() return is: ",GetErrMsg(s32_Error));
      
      if (s32_Error == OK)
         b_ServerOpened = false;
   }
//----
   return(0);
  }  

int start()
{
//----
   if (!b_ServerOpened)
      return(0);
      
   s32_Error = GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount);
   
   if (s32_Error != 0)
   {
      Print("GetAllConnections(...) return is: ",GetErrMsg(s32_Error));
      return(1);
   }
   

   Print("ClientCount = ", ps32_ClientCount[0]);

   
   for (i = 0; i<ps32_ClientCount[0]; i++)
   {
      Print("h_Client = ", ph_Client[i], "      Client IP =  ", FormatIP(ps32_ClientIP[i])); 
       
      s32_Error = ReadFromString(ph_Client[i], ps8_Buf, s32_BufLen, ps32_ReadLen);
      
      Print("ReadFromString(",ph_Client[i],",...) return is: ", GetErrMsg(s32_Error));
      
      if (s32_Error == 0)
      {
         Print("ReadFromString(",ph_Client[i],",...) ps32_ReadLen = ",ps32_ReadLen[0]);
         
         if (ps32_ReadLen[0] > 0)
         {
            Print("ReadFromString(",ph_Client[i],",...) Read data is: ", StringSubstr(ps8_Buf,0,ps32_ReadLen[0]));
            
            s32_Error = SendToString(ph_Client[i], ps8_Buf, StringLen(StringSubstr(ps8_Buf,0,ps32_ReadLen[0])));
            
            Print("SendToString(", ph_Client[i],",...) return is: ",GetErrMsg(s32_Error));
         }
      }
   }
//----
   return(0);
  }
//+------------------------------------------------------------------+

А вот так выглядит исходный текст Client.cpp - С++ программы, выполняющей роль клиента для советника-сервера:

//+---------------------------------------------------------------------------+
//|                                            Client.cpp                     |
//|                      Copyright © 2012, https://www.mql4.com/ ru/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+

// Client.cpp

#include <winsock2.h>
#pragma comment(lib, "ws2_32")
#include <iostream>
#pragma comment(lib, "NetEventsProcDLL") // NetEventsProcDLL.lib placed in ...\FastStart\Client\
#include  "cNetEventsProcDLL.h"

// LocalIp = 0x6401a8c0 -> 192.168.1.100
// Returns a list of all local IP's on this computer (multiple IP's if multiple network adapters)
DWORD GetLocalIPs(char s8_IpList[][20], int &s32_IpCount);

/* This is the simple Client.
   It connects to Echo Server, send your input to Echo Server and read Echo from Server.

   ATTENTION !!! If your want to connect to local Server do not use  "127.0.0.1" !!!ATTENTION !!! 
   You may get all your local IP's by means of GetLocalIPs(...) function, here is call example:
   // Local IP's list
        char s8_IpList[10][20];

        int s32_IpCount;
        DWORD u32_Err = GetLocalIPs(s8_IpList, s32_IpCount);

        if (u32_Err)
        {
                printf("Get local IP's error !, no local IP means no network available...");
                return 1;
        }

        printf("\nLocal IP's list:\n");

        for (int i = 0; i<s32_IpCount; i++)
                printf("\n%s\n",s8_IpList[i]);
        //
*/

// This is the Server we want to connect to...
#define SERVER_IP   "192.168.1.5"  //this is mine local IP's get by means of GetLocalIPs(...) function,
                                   // do not use so called loopback  "127.0.0.1" address !!! 
#define PORT         2000

int main()
{
        // Local IP's list
        char s8_IpList[10][20];

        int s32_IpCount;
        DWORD u32_Err = GetLocalIPs(s8_IpList, s32_IpCount);

        if (u32_Err)
        {
                printf("Get local IP's error !, no local IP means no network available...");
                return 1;
        }

        printf("\nLocal IP's list:\n");

        for (int i = 0; i<s32_IpCount; i++)
                printf("\n%s\n",s8_IpList[i]);
        //

        char s8_ServerIP[] = SERVER_IP;
        int  s32_Port      = PORT;
        int  ph_Client[1];

        int  h_Client;

        DWORD u32_Error = cNetEventsProcDLL::ConnectTo(SERVER_IP, PORT, ph_Client);

        if (u32_Error)
        {
                printf("\nConnectTo(...) failed with error: %d\n", u32_Error);
                return 1;
        }
        else
                printf("\nConnectTo(...) OK, ph_Client[0] = : %d\n", ph_Client[0]);

        h_Client = ph_Client[0];

        // Connection is established successfully ! Let's  have some SendTo(...) data on this connection...
        char  ps8_SendData[200];
        int   s32_SendDataLen;;

        char   ps8_ReadBuf[1025];
        DWORD  s32_ReadBufLen = 1025;
        int    ps32_ReadLen[1];

        while(true)
        {
                std::cout << "\nEnter something to send to Server or Exit 'q':\n" << std::endl;

                std::cin.getline(ps8_SendData, 200);

                s32_SendDataLen = strlen(ps8_SendData);

                OemToCharBuff(ps8_SendData, ps8_SendData, s32_SendDataLen);

                if (ps8_SendData[0] == 'q')
                {
                        u32_Error = cNetEventsProcDLL::ConnectClose(h_Client);
                        if (u32_Error)
                        {
                                printf("\nConnectClose(...) failed with error: %d\n", u32_Error);
                                break;
                        }
                        else
                        {
                                printf("\nConnectClose(...) OK.\n");
                                break;
                        }
                }

                u32_Error = cNetEventsProcDLL::SendToString(h_Client, ps8_SendData, s32_SendDataLen);   

                switch (u32_Error)
                {
                case 0:
                        printf("\nSendTo(...) OK");
                        printf("\nSendTo(%d...) sent %d bytes\n", h_Client, s32_SendDataLen);
                        CharToOemBuff(ps8_SendData, ps8_SendData, s32_SendDataLen);
                        printf("\nSendTo(%d...) Sent Data: %s\n",h_Client, ps8_SendData);
                        printf("Waiting now for Echo....");
                        break;
                case ERROR_INVALID_PARAMETER:
                        printf("\nSendTo(%d...) return is: ERROR_INVALID_PARAMETER(%d)\n",h_Client, u32_Error);
                        printf("\nERROR_INVALID_PARAMETER -> One of this parms or more: h_Client, ps8_SendData, u32_SendDataLen is invalid...\n");
                        break;
                case WSAEWOULDBLOCK:
                        printf("\nSendTo(%d...) return is: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error);
                        printf("\nWSAEWOULDBLOCK -> The data will be send after the next FD_WRITE event, do nouthing\n");
                        break;

                case WSA_IO_PENDING:
                        printf("\nSendTo(%d...) return is: WSA_IO_PENDING(%d)\n",h_Client, u32_Error);
                        printf("\nWSA_IO_PENDING -> Error: A previous Send operation is still pending. This data will not be sent, try latter\n");
                        break;

                default:
                        printf("\nSendTo(%d...)failed with severe error: %d\n",h_Client, u32_Error);
                        // Severe error -> abort event loop
                        printf("\nConnection was closed !\n");
                        break;
                };
                
                if (u32_Error == 0 || u32_Error == WSAEWOULDBLOCK)
                {
                        int ReadLen = 0;

                        while(!ReadLen)
                        {
                                u32_Error = cNetEventsProcDLL::ReadFromString(h_Client, ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen);

                                if (u32_Error)
                                {
                                        printf("\nReadFromString(%d...) failed with error: %d\n", h_Client, u32_Error);
                                        break;
                                }

                                ReadLen = ps32_ReadLen[0];
                        }
                        if (u32_Error)
                        {
                                printf("\nReadFromString(%d...) failed with error: %d\n", h_Client, u32_Error);
                        }
                        else
                        {
                                printf("\nReadFromString(%d...) OK, read %d  bytes\n", h_Client, ReadLen);
                        }
                                
                        if (ReadLen > 0)
                        {
                                CharToOemBuff(ps8_ReadBuf, ps8_ReadBuf, s32_SendDataLen);
                                ps8_ReadBuf[ReadLen] = 0;
                                printf("\nReadFromString(%d...) Read Data: %s\n", h_Client, ps8_ReadBuf);
                        }

                }

        }

        return 0;
}

// LocalIp = 0x6401a8c0 -> 192.168.1.100
// Returns a list of all local IP's on this computer (multiple IP's if multiple network adapters)
DWORD GetLocalIPs(char s8_IpList[][20], int &s32_IpCount)
{
        // Winsock version 2.0 is available on ALL Windows operating systems 
        // except Windows 95 which comes with Winsock 1.1
        WSADATA k_Data;
        DWORD u32_Error = WSAStartup(MAKEWORD(2,0), &k_Data);
        if (u32_Error)
                return u32_Error;

        int ps32_IpList[20];

        char s8_Host[500];
        if (gethostname(s8_Host, sizeof(s8_Host)) == SOCKET_ERROR)
                return WSAGetLastError();
        
        struct hostent* pk_Host = gethostbyname(s8_Host);
        if (!pk_Host)
                return WSAGetLastError();

        s32_IpCount = 0;

        for (DWORD i=0; TRUE; i++)
        {
                if (!pk_Host->h_addr_list[i])
                        break; // The IP list is zero terminated

                ps32_IpList[i] = *((DWORD*)pk_Host->h_addr_list[i]);

                s32_IpCount++;
        }

        if (!s32_IpCount)
                return WSAENETDOWN; // no local IP means no network available

        for (int i = 0; i<s32_IpCount; i++)
        {
                BYTE* pu8_Addr = (BYTE*)&ps32_IpList[i];
        
                sprintf(s8_IpList[i],"%d.%d.%d.%d",pu8_Addr[0], pu8_Addr[1], pu8_Addr[2], pu8_Addr[3]);
        }
        return 0;
}

Для запуска данного демо-примера необходимо:

  1. Поместите EchoServer.mq4 в папку терминала "МetaТrader 4\experts\" и выполните компиляцию.

  2. Откройте проект Microsoft Visual Studio 2010 Ultimate Client и выполните его построение для режима Release. При построении проекта в другой IDE не забудьте подать модуль NetEventsProcDLL.lib как дополнительный вход для редактора (compile with: /EHsc/link NetEventsProcDLL.lib).

  3. Запустите на выполнение полученный исполняемый модуль Client.exe. Вы получите ошибку с кодом 10057 и список локальных IP вашего компьютера. Откорректируйте в исходном коде Client.cpp следующую строку

    #define SERVER_IP   "192.168.1.5"

    заменив "192.168.1.5" на первый по порядку (или единственный) локальный IP, и произведите повторное построение проекта.

  4. Запустите EchoServer советник на любом графике в терминале MetaTrader 4. Если все сделано правильно, то в окне терминала появятся сообщения: "ServerOpen() return is: OK", "Server is Opened and Waiting for Client connection requests...". Далее на каждом тике советник проверяет наличие запросов на подключение, производит подключения, читает пришедшие по каждому подключению сообщения и посылает копию сообщения назад адресату. Всю текущую информацию советник выводит в окне терминала.

  5. Теперь можно запустить на выполнение программу Client.exe, построенную нами в пункте 3.

  6. Вы можете запустить сразу несколько копий этой программы и наблюдать как советник сервер обеспечивает обмен информацией со всеми копиями.

  7. Если в пункте 3 вы вместо локального IP компьютера поставите его глобальный IP, на котором запущен советник-сервер, то в таком случае вы можете выполнять Client.exe на любом другом компьютере, связь с советником будет выполняться через интернет. Разумеется, не забудьте в этом случае убрать все существующие в вашем брандмауэре защиты.


1.2.2. С++ Программа-сервер & MetaTrader 4 Советник-клиент

А теперь рассмотрим также традиционную задачу по обмену информацией между МetaТrader 4 Советником и программой на C++:

  • EchoServer.cpp - C++ программа выполняет роль эхо-сервера.

  • Client.mq4 - советник выполняет роль клиента для данного С++ сервера.

Клиент-советник считывает котировки инструмента, на котором он запущен, и передает эти котировки С++ серверу. С++ сервер возвращает котировки назад адресату, который их принимает и выводит в окне терминала. Приведем для наглядности рисунок:

Вот как выглядит исходный код МetaТrader 4 Советника Client.mq4, выполняющего роль клиента:

//+---------------------------------------------------------------------------+
//|                                            Client.mq4                     |
//|                      Copyright © 2012, https://www.mql4.com/ ru/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+
#property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more"
#property link      "https://www.mql4.com/ ru/users/more"
#include <ImportNetEventsProcDLL.mqh>
/*int ConnectTo(string  ps8_ServerIP,         // in - string ps8_ServerIP = "0123456789123456"
                int     s32_Port,             // in 
                int&    ph_Client[]);         // out - int ph_Client[1]
*/
/*int SendToDouble(int     h_Client,          // in
                   double& pd_SendBuf[],      // in
                   int     s32_SendBufLen);   // in - SendBuf[] array size in double element 
*/
/*int ReadFromDouble(int     h_Client,        // in
                     double& pd_ReadBuf[],    // in
                     int     s32_ReadBufLen,  // in  - ReadBuf[] array size in double element
                     int&    ps32_ReadLen[]); // out - int ps32_ReadLen[1] - count of actually read data in double element
*/                      
/*int ConnectClose(int h_Client);             // in
*/ 
       
// Globals variables
int s32_Error;
int i;
// for int ConnectTo(ps8_ServerIP, s32_Port, ph_Client); // out - int h_Client[1]
string ps8_ServerIP = "192.168.1.5";                     // mine local IP
int    s32_Port = 2000;
int    ph_Client[1];

bool b_ConnectTo = false;

// for int SendToDouble(ph_Client[0], pd_Buf, s32_BufLen);  
// for int ReadFromDouble(ph_Client[0], pd_Buf, s32_BufLen, ps32_ReadLen);
double pd_Buf[1];
int    s32_BufLen = 1;
int    ps32_ReadLen[1];
//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
{
//----
   if (!b_ConnectTo)
   {
      s32_Error = ConnectTo(ps8_ServerIP, s32_Port, ph_Client);
      Print("ConnectTo(...) return is: ",GetErrMsg(s32_Error));
      Print("ConnectTo(...) handle is: ",ph_Client[0]);
      
      if (s32_Error == OK)
      {
         b_ConnectTo = true;
         Print("Client now is ConnectTo the Server: ",ps8_ServerIP);
      }
   }
//----
   return(0);
}
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
{
//----
   if (b_ConnectTo)
   {
      s32_Error = ConnectClose(ph_Client[0]);
      Print("ConnectClose(...) return is: ",GetErrMsg(s32_Error));
      
      if (s32_Error == OK)
         b_ConnectTo = false;
   }
//----
   return(0);
}  

int start()
{
//----
   if (!b_ConnectTo)
      return(0);
   
   RefreshRates();
   
   double pd_Value[1];
   
   pd_Value[0] = NormalizeDouble(Bid,Digits);
      
   s32_Error = SendToDouble(ph_Client[0], pd_Value, s32_BufLen);
   
   
   if (s32_Error != 0)
   {
      Print("SendToDouble(",ph_Client[0],"...) return is: ",GetErrMsg(s32_Error));
      return(1);
   }
   else
      Print("SendToDouble(",ph_Client[0],"...) return is: OK");
      
   s32_Error = ReadFromDouble(ph_Client[0], pd_Buf, s32_BufLen, ps32_ReadLen);      
   
   if (s32_Error != 0)
   {
      Print("ReadFromDouble(",ph_Client[0],"...) return is: ", GetErrMsg(s32_Error));
      return(1);
   }
   else
      Print("ReadFromDouble(",ph_Client[0],"...) return is: OK"); 
   
   pd_Buf[0] = NormalizeDouble(pd_Buf[0],Digits);
   
   if (ps32_ReadLen[0] > 0)
      Print("Read doble value is: ", pd_Buf[0]);
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

А вот так выглядит исходный текст EchoServer.cpp С++ программы, выполняющей роль эхо-сервера для советника-клиента:

//+---------------------------------------------------------------------------+
//|                                            EchoServer.cpp                 |
//|                      Copyright © 2012, https://www.mql4.com/ ru/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+

// EchoServer.cpp

#include <winsock2.h>
#pragma comment(lib, "NetEventsProcDLL") // NetEventsProcDLL.lib placed in ...\FastStart\EchoServer\ 
#include <iostream>
#include <conio.h>

#include  "cNetEventsProcDLL.h"

BOOL FormatIP(DWORD u32_IP, char* s8_IP);

int main()
{
        int s32_Port = 2000;
        
        // Try to create server listening on port 2000
        // You may change port.
        DWORD u32_Error = cNetEventsProcDLL::ServerOpen(s32_Port);

        if (u32_Error)
        {
                printf("\nServerOpen() failed with error: %d\n", u32_Error);
                return 1;
        }
        else
                printf("\nServerOpen() fine, we now are waiting for connections...\n");
        
        DWORD u32_Count = 0;
        DWORD u32_CountOld = 0;
        
        double pd_Buf[1025];
        DWORD  u32_BufLen = 1025;
        int    ps32_ReadLen[1];

        pd_Buf[0] = 0;

        int ph_Client[62];
        int ps32_ClientIP[62];
        int ps32_ClientCount[1];

        while(!kbhit())
        {
                u32_Error = cNetEventsProcDLL::GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount);
                
                if (u32_Error)
                {
                        printf("\nGetAllConnections(...) failed with error: %d\n", u32_Error);
                        break;
                }
                else
                        u32_Count = ps32_ClientCount[0];

                if (u32_Count != u32_CountOld)
                {
                        u32_CountOld = u32_Count;

                        printf("\nNumber of connections now = %d\n", u32_Count);
                        printf("#     h_Connect   (peer IP)\n");
                        
                        for (DWORD i = 0; i<u32_Count; i++)
                        {
                                char  s8_IP[20];
                                sprintf(s8_IP, "%s","123456789012345");

                                FormatIP(ps32_ClientIP[i], s8_IP);

                                printf("%d      %d       (%s)\n", i, ph_Client[i], s8_IP);
                        }
                }

                for (DWORD i = 0; i<u32_Count; i++)
                {
                        u32_Error = cNetEventsProcDLL::ReadFromDouble(ph_Client[i], pd_Buf, u32_BufLen, ps32_ReadLen);

                        if (u32_Error)
                        {
                                printf("ReadFromDouble(%d...) failed with error: %d\n", ph_Client[i], u32_Error);
                        }

                        if (ps32_ReadLen[0])
                        {
                                printf("ReadFromDouble(%d...) read %d double values\n", ph_Client[i], ps32_ReadLen[0]);
                                printf("\nReadFromDouble(%d...) Read Data: %9.5f\n", ph_Client[i], pd_Buf[0]);
                        }
                        
                        if (ps32_ReadLen[0])
                        {
                                u32_Error = cNetEventsProcDLL::SendToDouble(ph_Client[i], pd_Buf, ps32_ReadLen[0]);

                                if (u32_Error)
                                {
                                        printf("SendToDouble(%d...) failed with error: %d\n", ph_Client[i], u32_Error);
                                }
                                else
                                {
                                        printf("SendToDouble(%d...) sent %d double values\n", ph_Client[i], ps32_ReadLen[0]);
                                        printf("SendToDouble(%d...) sent Data: %9.5f\n",ph_Client[i], pd_Buf[0]);
                                }
                        }

                }
                
        }

        u32_Error = cNetEventsProcDLL::ServerClose();

        if (u32_Error)
        {
                printf("\nServerClose() failed with error: %d\n", u32_Error);
                return 1;
        }
        else
                printf("\nServerClose() fine...\n");

        Sleep(10000);
        return 0;
}

BOOL FormatIP(DWORD u32_IP, char* s8_IP)
{
        DWORD u32_Len = strlen(s8_IP);

        if ( u32_Len < 15)
                return FALSE;
        
        BYTE* pu8_Addr = (BYTE*)&u32_IP;
        sprintf(s8_IP,"%d.%d.%d.%d",pu8_Addr[0], pu8_Addr[1], pu8_Addr[2], pu8_Addr[3]);

        return TRUE;
}

Для запуска данного демо-примера необходимо:

  1. Поместите Client.mq4 в папку терминала "MetaTrader 4\experts\", вставьте локальный IP (полученный в предыдущем примере 1.2.1.) в строку

    string ps8_ServerIP = "192.168.1.5";

    и выполните компиляцию. Если С++ сервер будет запускаться на другом компьютере, то вставьте сюда глобальный IP этого компьютера, при этом уберите все существующие в вашем брандмауэре защиты.

  2. Откройте проект Microsoft Visual Studio 2010 Ultimate EchoServer и выполните его построение для режима Release. При построении проекта в другой IDE не забудьте подать модуль NetEventsProcDLL.lib как дополнительный вход для редактора (compile with: /EHsc/link NetEventsProcDLL.lib).

  3. Запустите C++ сервер EchoServer.exe. Если все сделано правильно, то в консоль будет выведено сообщение "ServerOpen() fine, we now are waiting for connections.....". Нажатие любой клавиши приведет к закрытию сервера и завершению работы программы.

  4. На любом графике терминала МetaТrader 4 запустите клиент Client.mq4.

  5. Вы можете запустить клиент одновременно на нескольких графиках, в одном и том же или на разных терминалах, на одном и том же или на разных компьютерах.

  6. Понаблюдайте за работой С++ сервера и МetaТrader 4 клиентов. Для корректного завершения работы нажмите любую клавишу в консоли С++ сервера, и тогда С++ программа закроет сервер и завершит работу.


1.2.3. МetaТrader 4 Советник-индикатор (Советник-сервер) & МetaТrader 4 Клиент-индикатор

Советник-индикатор (Советник-сервер) обеспечивает Клиенты-индикаторы показаниями индикатора. В данном случае - это показания штатного индикатора iEnvelops(...). Данный пример может иметь практическое значение для раздачи показаний "засекреченного" индикатора всем подписчикам-клиентам.

Приведем для наглядности рисунок:

Вот как выглядит исходный код МetaТrader 4 Советника ServerSendInd.mq4, выполняющего роль поставщика значений индикатора iEnvelops(...):

//+---------------------------------------------------------------------------+
//|                                            ServerSendInd.mq4              |
//|                      Copyright © 2012, https://www.mql4.com/ ru/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+
#property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more"
#property link      "https://www.mql4.com/ ru/users/more"
#include <ImportNetEventsProcDLL.mqh>
/*int ServerOpen(int  s32_Port);                  // in
*/
/*int ServerClose();
*/
/*int GetAllConnections(int& ph_Client[],         // out - int ph_Client[62]
                        int& ps32_ClientIP[],     // out - int ps32_ClientIP[62]
                        int& ps32_ClientCount[]); // out - int ps32_ClientCount[1]
*/                      
/*int ReadFromString(int     h_Client,            // in
                   string    ps8_ReadBuf,         // in
                   int       s32_ReadBufLen,      // in  - ReadBuf   string size in char element
                   int&      ps32_ReadLen[]);     // out - int ps32_ReadLen[1] - count of actually read data in char element     
*/ 
/*int SendToDouble(int     h_Client,              // in
                   double& pd_SendBuf[],          // in
                   int     s32_SendBufLen);       // in - SendBuf[] array size in double element   
*/                                            
// Globals variables
int s32_Error;
int i;

int s32_Port = 2000;
bool b_ServerOpened = false;

// for GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount)
int ph_Client       [62];
int ps32_ClientIP   [62];
int ps32_ClientCount[1 ];

// for int ReadFromString(h_Client, ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen)
string ps8_ReadBuf = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
int    s32_ReadBufLen;
int    ps32_ReadLen[1];

// for int SendToDouble(ph_Client[0], pd_SendBuf, s32_SendBufLen);  
#define  BARS_COUNT  200
double pd_SendBuf      [BARS_COUNT];  //BARS_COUNT/2 Bars for each of 2 line 
int    s32_SendBufLen = BARS_COUNT;

//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
{
   //----
   s32_ReadBufLen = StringLen(ps8_ReadBuf);
   
   if (!b_ServerOpened)
   {
      s32_Error = ServerOpen(s32_Port);
      Print("ServerOpen() return is: ",GetErrMsg(s32_Error));
      
      if (s32_Error == OK)
      {
         b_ServerOpened = true;
         Print("Server is Opened and Waiting for Clients connection requests...");
      }
   }
//----
   return(0);
}
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
{
//----
   if (b_ServerOpened)
   {
      s32_Error = ServerClose();
      Print("ServerClose() return is: ",GetErrMsg(s32_Error));
      
      if (s32_Error == OK)
         b_ServerOpened = false;
   }
//----
   return(0);
}  

int start()
{
//----
   if (!b_ServerOpened)
      return(0);
      
   s32_Error = GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount);
   
   if (s32_Error != 0)
   {
      Print("GetAllConnections(...) failed with error: ",GetErrMsg(s32_Error));
      return(1);
   }
   
   Print("ClientCount = ", ps32_ClientCount[0]);
   
   for (i = 0; i<ps32_ClientCount[0]; i++)
   {
      Print("h_Client = ", ph_Client[i], "      Client IP =  ", FormatIP(ps32_ClientIP[i]));
      
      s32_Error = ReadFromString(ph_Client[i], ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen); 
      
      if (s32_Error != 0)
      {
         Print("ReadFromString(",ph_Client[i],") failed with error: ", GetErrMsg(s32_Error));
         continue;
      }

      if (ps32_ReadLen[0] > 0)
      {
         // ps8_ReadBuf = "EURUSDMinuts"   i.e. "Symbol+Timeframe"
         string Sym       = StringSubstr(ps8_ReadBuf,0,6);
         int    TimeFrame = StrToInteger(StringSubstr(ps8_ReadBuf,6,ps32_ReadLen[0]-6));
      

         int k;                
         for (k = 0; k<BARS_COUNT/2; k++)
         {
            while(true)
            {
               double UpperLine_k = iEnvelopes(Sym, TimeFrame, 14, MODE_SMA, 0, PRICE_CLOSE, 0.1, MODE_UPPER, k);
               if (GetLastError() != 0)
                  continue;
               else
                  break;   
            }    
            while(true)
            {           
               double LowerLine_k = iEnvelopes(Sym, TimeFrame, 14, MODE_SMA, 0, PRICE_CLOSE, 0.1, MODE_LOWER, k);
               if (GetLastError() != 0)
                  continue;
               else
                  break;
            }
             
            pd_SendBuf[k]              = UpperLine_k;
            pd_SendBuf[k+BARS_COUNT/2] = LowerLine_k;
         }
         
         s32_Error = SendToDouble(ph_Client[i], pd_SendBuf, s32_SendBufLen); 
         if (s32_Error != 0)
         {
            Print("SendToDouble(",ph_Client[i],") failed with error: ", GetErrMsg(s32_Error));

            continue;
         }
      }  
   }
//----
   return(0);
}
//+------------------------------------------------------------------+

А вот как выглядит исходный код Клиента-индикатора ClientIndicator.mq4, получающего значения индикатора iEnvelops(...) от Советника ServerSendInd.mq4:

//+---------------------------------------------------------------------------+
//|                                            ClientIndicator.mq4            |
//|                      Copyright © 2012, https://www.mql4.com/ ru/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+
#property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more"
#property link      "https://www.mql4.com/ ru/users/more"
#include <ImportNetEventsProcDLL.mqh>

/*int ConnectTo(string  ps8_ServerIP,         // in - string ps8_ServerIP = "0123456789123456"
                int     s32_Port,             // in 
                int&    ph_Client[]);         // out - int ph_Client[1]
*/
/*                      
/*int ConnectClose(int h_Client);             // in
*/
/*int SendToString(int    h_Client,           // in
                   string ps8_SendBuf,        // in
                   int    s32_SendBufLen);    // in - SendBuf string size in char element
*/
/*int ReadFromDouble(int     h_Client,        // in
                     double& pd_ReadBuf[],    // in
                     int     s32_ReadBufLen,  // in  - ReadBuf[] array size in double element
                     int&    ps32_ReadLen[]); // out - int ps32_ReadLen[1] - count of actually read data in double element
*/
// Globals variables
int s32_Error;
int i;
// for int ConnectTo(ps8_ServerIP, s32_Port, ph_Client);  // out - int h_Client[1]
string ps8_ServerIP = "192.168.1.5";                      // mine local IP
int    s32_Port = 2000;
int    ph_Client[1]; 

bool b_ConnectTo = false;

// for int SendToString  (h_Client, ps8_SendBuf, s32_SendBufLen)
string ps8_SendBuf = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
int    s32_SendBufLen;  
 
// for int ReadFromDouble(ph_Client[0], pd_ReadBuf, s32_ReadBufLen, ps32_ReadLen);
#define BARS_COUNT  200
double  pd_ReadBuf      [BARS_COUNT];
int     s32_ReadBufLen = BARS_COUNT;
int     ps32_ReadLen[1]; 
               
string Indicator_Name = "Envelopes: ";
//----
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1 Blue
#property indicator_color2 Red

double UpperLine[];
double LowerLine[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
{
//---- indicators
   SetIndexStyle (0,DRAW_LINE);
   SetIndexBuffer(0,UpperLine);
   
   SetIndexStyle (1,DRAW_LINE);
   SetIndexBuffer(1,LowerLine);
   
   s32_SendBufLen = StringLen(ps8_SendBuf);
   
   if (!b_ConnectTo)
   {
      s32_Error = ConnectTo(ps8_ServerIP, s32_Port, ph_Client);
      Print("ConnectTo(...) return is: ",GetErrMsg(s32_Error));
      Print("ConnectTo(...) handle is: ",ph_Client[0]);
      
      if (s32_Error == OK)
      {
         b_ConnectTo = true;
         Print("Client now is ConnectTo the Server: ",ps8_ServerIP);
      }
   }
//----   
   return(0);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
int deinit()
{
    //----
   if (b_ConnectTo)
   {
      s32_Error = ConnectClose(ph_Client[0]);
      Print("ConnectClose(...) return is: ",GetErrMsg(s32_Error));
      
      if (s32_Error == OK)
         b_ConnectTo = false;
   }
//----
   return(0);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()                          
{
//----
   if (!b_ConnectTo)
      return(0);
      
    string Sym       = Symbol();
    int    TimeFrame = Period();
    ps8_SendBuf = Symbol() + DoubleToStr(Period(),0);
    
    s32_Error = SendToString(ph_Client[0], ps8_SendBuf, StringLen(ps8_SendBuf));
    
    if (s32_Error != 0) 
    {       
      Print("SendToString(", ph_Client[0],",...) failed with error: ",GetErrMsg(s32_Error));
      return (1);
    }
    
    s32_Error = ReadFromDouble(ph_Client[0], pd_ReadBuf, s32_ReadBufLen, ps32_ReadLen);      
   
   if (s32_Error != 0)
   {
      Print("ReadFromDouble(",ph_Client[0],"...) return is: ", GetErrMsg(s32_Error));
      return(1);
   }
   
   if (ps32_ReadLen[0] == 0)
      return (0);
 
//--------------------------------------------------------------------
    int Counted_bars = IndicatorCounted();       // Количество просчитанных баров 
    i = Bars - Counted_bars - 1;                 // Индекс первого непосчитанного
    if (i > BARS_COUNT/2-1)  i = BARS_COUNT/2-1; // Если много баров то рассчитывать заданное количество
//-----------------------------------------------------------------------  
    for (i = BARS_COUNT/2-1; i >= 0; i--)
    {
         UpperLine  [i] = pd_ReadBuf[i];
         LowerLine  [i] = pd_ReadBuf[i+BARS_COUNT/2];
    }  
    
    return;                          
} // end of int start()
//--------------------------------------------------------------------

Для запуска данного демо-примера необходимо:

  1. Поместите ServerSendInd.mq4 в папку терминала "МetaТrader 4\experts\", выполните компиляцию.

  2. Поместите ClientIndicator.mq4 в папку терминала "МetaТrader 4\experts\indicators\", вставьте локальный IP (полученный в примере 1.2.1.) в строку

    string ps8_ServerIP = "192.168.1.5";

    Если ServerSendInd будет запускаться на другом компьютере, вставьте сюда глобальный IP этого компьютера, при этом уберите все существующие в вашем брандмауэре защиты. Выполните компиляцию.

  3. Запустите ServerSendInd. Если все сделано правильно, то в консоль будет выведено сообщение "ServerOpen() fine, we now are waiting for connections.....".

  4. На любом графике терминала МetaТrader 4 запустите индикатор ClientIndicator.mq4. На графике появятся две индикаторные линии. Если вы запустите на том же графике стандартный индикатор Envelops, то убедитесь, что обе линии нашего индикатора совпадают с линиями стандартного индикатора.

  5. Вы можете запустить индикатор ClientIndicator одновременно на нескольких графиках, в одном и том же или на разных терминалах, на одном и том же или на разных компьютерах.

  6. Понаблюдайте за работой ServerSendInd сервера и ClientIndicator. Попробуйте поменять таймфрейм графика с индикатором ClientIndicator. ServerSendInd сервер немедленно настраивается на посылку значений индикатора для данного таймфрейма.


2. Спецификация DLL интерфейса

В этом разделе мы подробно опишем все DLL функции и необходимые для их вызова параметры. Все функции в случае их успешного завершения возвращают ноль. В противном случае функции возвращают коды ошибок winsock2 API. Все экспортированные DLL функции приведены в определении С++ класса cNetEventsProcDLL.h, поэтому приведем исходный код этого файла:

//+---------------------------------------------------------------------------+
//|                                            cNetEventsProcDLL.h            |
//|                      Copyright © 2012, https://www.mql4.com/ ru/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+
//--- cNetEventsProcDLL.h
#pragma once
#define EXPFUNC __declspec(dllexport)
//---   
class cNetEventsProcDLL
  {
public:
   static BOOL MessageDLL_PROCESS_ATTACH(void);
   static BOOL MessageDLL_PROCESS_DETACH(void);
//---
   static EXPFUNC int __stdcall ConnectTo(char *ps8_ServerIP,             // in - ps8_ServerIP = "0123456789123456"
                                          int   s32_Port,                 // in 
                                          int*  ph_Client);               // out - int ph_Client[1]
//---
   static EXPFUNC int __stdcall ConnectClose(int h_Client);               // in 
//---
   static EXPFUNC int __stdcall ServerOpen(int s32_Port);                 // in
//---
   static EXPFUNC int __stdcall GetAllConnections(int* ph_Client,         // out - int ph_Client[62]
                                                  int* ps32_ClientIP,     // out - int ps32_ClientIP[62]
                                                  int* ps32_ClientCount); // out - int ps32_ClientCount[1]
//---
   static EXPFUNC int __stdcall DisconnectClient(SOCKET h_Client);        // in 
//---
   static EXPFUNC int __stdcall ServerClose();
//---
   static EXPFUNC int __stdcall SendToInt(SOCKET h_Client,             // in
                                          int   *ps32_SendBuf,         // in
                                          int    s32_SendBufLen);      // in -  SendBuf[] array size in int element
//---
   static EXPFUNC int __stdcall SendToDouble(SOCKET  h_Client,         // in
                                             double* pd_SendBuf,       // in
                                             int     s32_SendBufLen);  // in -  SendBuf[] array size in double element
//---
   static EXPFUNC int __stdcall SendToString(SOCKET h_Client,          // in
                                             char*  ps8_SendBuf,       // in
                                             INT   s32_SendBufLen);    // SendBuf string size in char element
//---
   static EXPFUNC int __stdcall ReadFromInt(SOCKET h_Client,           // in
                                            int   *ps32_ReadBuf,       // in
                                            int    s32_ReadBufLen,     // ReadBuf[] array size in int element
                                            int   *ps32_ReadLen);      // out - int ps32_ReadLen[1] - actual count of read data in int element
//---
   static EXPFUNC int __stdcall ReadFromDouble(SOCKET  h_Client,       // in
                                               double *pd_ReadBuf,     // in
                                               int     s32_ReadBufLen, // ReadBuf[] array size in double element
                                               int    *ps32_ReadLen);  // out - int ps32_ReadLen[1] - actual count of read data in double element
//---
   static EXPFUNC int __stdcall ReadFromString(SOCKET h_Client,        // in
                                               char  *ps8_ReadBuf,     // in
                                               int    s32_ReadBufLen,  // ReadBuf[] array size in char element
                                               int*   ps32_ReadLen);   // out - int ps32_ReadLen[1] - actual count of read data in char element
//---
protected:
   static DWORD SendTo(SOCKET h_Client,char *ps8_SendBuf,INT s32_SendBufLen);
   static DWORD ReadFrom(SOCKET h_Client,char *ps8_ReadBuf,INT s32_ReadBufLen,INT &s32_ReadLen);
  };

Теперь рассмотрим все DLL функции в порядке их появления в этом файле:

  1. ConnectTo - запрос к серверу на создание соединения:

    static EXPFUNC int __stdcall ConnectTo(char* ps8_ServerIP, // in - ps8_ServerIP = "0123456789123456"
                                           int   s32_Port,     // in 
                                           int*  ph_Client);   // out - int ph_Client[1]

    Параметры функции:

    • char* ps8_ServerIP - IP-адрес сервера, с которыми необходимо установить соединение (например, "93.127.110.162"). Если сервер является локальным, то следует задавать локальный IP, но не "127.0.0.1", а полученный, как описано в примере 1.2.1.

    • int s32_Port - номер порта, который "слушает" сервер.

    • int* ph_Client - в эту переменную помещается хэндл соединения в случае успешного завершения работы функции. Этот хэндл должен использоваться для всех последующих операций по этому соединению.

    При успешном выполнении функция возвращает ноль, в противном случае - код ошибки winsock2 API. Пример C++:

    DWORD u32_Error = cNetEventsProcDLL::ConnectTo(SERVER_IP, PORT, ph_Client);
    
    if (u32_Error)
    {
            printf("\nConnectTo(...) failed with error: %d\n", u32_Error);
            return 1;
    }
    else
            printf("\nConnectTo(...) OK, ph_Client[0] = : %d\n", ph_Client[0]);
    
    int h_Client = ph_Client[0];
  2. ConnectClose - запрос к серверу на закрытие соединения.

    static EXPFUNC int __stdcall ConnectClose(int h_Client); // in 

    Параметры функции:

    • int h_Client - хэндл соединения, которое необходимо ликвидировать. При успешном выполнении функция возвращает ноль, в противном случае - код ошибки winsock2 API.

    Пример C++:

    int u32_Error = cNetEventsProcDLL::ConnectClose(h_Client);
    
        if (u32_Error)
                printf("\nConnectClose(...) failed with error: %d\n", u32_Error);
        else
                printf("\nConnectClose(...) OK.\n");
  3. ServerOpen - запрос на создание сервера.

    static EXPFUNC int __stdcall ServerOpen(int s32_Port); //in

    Параметры функции:

    • int s32_Port - номер порта, который сервер будет "слушать" в ожидании запросов от клиентов. При успешном выполнении функция возвращает ноль, в противном случае - код ошибки winsock2 API.

    Пример C++:

    int u32_Error = cNetEventsProcDLL::ServerOpen(s32_Port);
    
    if (u32_Error)
    {
            printf("\nServerOpen() failed with error: %d\n", u32_Error);
            return 1;
    }
    else
            printf("\nServerOpen() fine, we now are waiting for connections...\n");
  4. GetAllConnections - запрос к серверу предоставить информацию обо всех существующих на настоящий момент соединениях.

    static EXPFUNC int __stdcall GetAllConnections(int* ph_Client,         // out - int ph_Client[62]
                                                   int* ps32_ClientIP,     // out - int ps32_ClientIP[62]
                                                   int* ps32_ClientCount); // out - int ps32_ClientCount[1]

    Параметры функции:

    • int ph_Client[62] - выходной массив, в который сервер помещает хэндлы для всех существующих на настоящий момент соединений.

    • int ps32_ClientIP[62] - выходной массив, в который сервер помещает IP адреса для всех существующих в настоящий момент соединений. Для конвертирования этих адресов в привычный символьный формат типа "92.127.110.161" используйте функцию string FormatIP(int IP) для МetaТrader 4 Советника или аналогичную, приведенную в примерах С++ программ. Число 62 в размерности массивов указано не случайно, оно обозначает ограничение на допустимое количество соединений (клиентов) для одного сервера.

    • int* ps32_ClientCount - в эту переменную сервер помещает количество существующих на настоящий момент соединений, т.е. количество элементов в упомянутых массивах.

    При успешном выполнении функция возвращает ноль, в противном случае - код ошибки winsock2 API. Пример C++:

    int ph_Client[62];
    int ps32_ClientIP[62];
    int ps32_ClientCount[1];
    
    int u32_Error = cNetEventsProcDLL::GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount);
                    
    if (u32_Error)
    {
        printf("\nGetAllConnections(...) failed with error: %d\n", u32_Error);
    }
    else
       int u32_Count = ps32_ClientCount[0];
  5. DisconnectClient - запрос к серверу на закрытие соединения c одним из его клиентов.

    static EXPFUNC int __stdcall DisconnectClient(int h_Client); // in 

    Параметры функции:

    • int h_Client - хэндл соединения, которое необходимо ликвидировать. При успешном выполнении функция возвращает ноль, в противном случае - код ошибки winsock2 API.

    Пример C++:

    int u32_Error = cNetEventsProcDLL::DisconnectClient(h_Client);
    
    if (u32_Error)
            printf("\nDisconnectClient(...) failed with error: %d\n", u32_Error);
    else
            printf("\nDisconnectClient(...) OK.\n");
  6. ServerClose - запрос на закрытие сервера.

    static EXPFUNC int __stdcall ServerClose();  

    При закрытии сервера производится ликвидация всех существующих клиентских соединений, так что каждый из клиентов в ответ на любую операцию будет получать код возврата - "соединение отсутствует". При успешном выполнении функция возвращает ноль, в противном случае - код ошибки winsock2 API. Пример C++:

    int u32_Error = cNetEventsProcDLL::ServerClose();
    
    if (u32_Error)
            printf("\nServerClose() failed with error: %d\n", u32_Error);
    else
            printf("\nServerClose() OK.\n");
  7. Следующая группа функций относится непосредственно к обмену данными по существующему соединению. Прием данных для каждого из существующих соединений производится в асинхронном режиме, т.е. без участия самого адресата-назначения.

    Все данные отправляются и принимаются в виде независимой единицы обмена - блока. Все блоки для каждого адресата накапливаются в стеке FIFO. Адресат может извлекать эти блоки из стека в любой удобный для него момент времени. Каждая функция обмена имеет дело с одним единственным блоком.

    Для операций отправки данных возможна ситуация, когда операция завершена успешно, но при этом код возврата отличен от нуля и может принимать следующие значения:

    • WSAEWOULDBLOCK - операция завершена успешно, данные еще не отправлены адресату, но будут отправлены в подходящий для этого момент времени. Никаких действий от пользователя не требуется.

    • WSA_IO_PENDING - предыдущая отправка данных еще не завершена, пользователь должен повторить попытку отправить данные позже. Ситуация штатная, поэтому считается, что функция выполнена успешно.

    Любые другие коды возврата свидетельствуют об ошибке пользователя.

  8. SendToInt - запрос на отправку блока данных, представляющего собой массив чисел типа int, по существующему соединению.

    static EXPFUNC int __stdcall SendToInt(SOCKET h_Client,        // in
                                           int*   ps32_SendBuf,    // in
                                           int    s32_SendBufLen); // in - SendBuf[] array size in int element

    Параметры функции:

    • SOCKET h_Client - хэндл существующего соединения.

    • int ps32_SendBuf[s32_SendBufLen] - массив чисел типа int, представляющий собой единый блок, который требуется передать клиенту.

    • int s32_SendBufLen - размерность массива.

    При успешном выполнении функция возвращает ноль, в противном случае - код ошибки winsock2 API. Пример C++:

    int ps32_SendBuf[200];
    int s32_SendBufLen=200;
    
    int u32_Error = cNetEventsProcDLL::SendToInt(h_Client, ps32_SendBuf, s32_SendBufLen);   
    
    switch (u32_Error)
    {
       case 0:
           printf("\nSendTo(...) OK");
           break;
       case WSAEWOULDBLOCK:
           printf("\nSendTo(%d...) return is: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error);
           printf("\nWSAEWOULDBLOCK -> The data will be send after the next FD_WRITE event, do nouthing\n");
           break;
       case WSA_IO_PENDING:
           printf("\nSendTo(%d...) return is: WSA_IO_PENDING(%d)\n",h_Client, u32_Error);
           printf("\nWSA_IO_PENDING -> Error: A previous Send operation is still pending. This data will not be sent, try latter\n");
           break;
    
       default:
           printf("\nSendTo(%d...)failed with severe error: %d\n",h_Client, u32_Error);
           break;
    };
  9. SendToDouble - запрос на отправку блока данных, представляющего собой массив чисел типа double, по существующему соединению.

    static EXPFUNC int __stdcall SendToDouble(SOCKET h_Client,          // in
                                              double*  pd_SendBuf,      // in
                                              int      s32_SendBufLen); // in - SendBuf[] array size in int element

    Параметры функции:

    • SOCKET h_Client - хэндл существующего соединения.

    • double pd_SendBuf[s32_SendBufLen] - массив чисел типа double, представляющий собой единый блок, который требуется передать клиенту.

    • int s32_SendBufLen - размерность массива.

    При успешном выполнении функция возвращает ноль, в противном случае - код ошибки winsock2 API. Пример C++:

    double pd_SendBuf[200];
    int    s32_SendBufLen=200;
    
    int u32_Error = cNetEventsProcDLL::SendToDouble(h_Client, pd_SendBuf, s32_SendBufLen);   
    
    switch (u32_Error)
    {
       case 0:
           printf("\nSendTo(...) OK");
           break;
       case WSAEWOULDBLOCK:
           printf("\nSendTo(%d...) return is: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error);
           printf("\nWSAEWOULDBLOCK -> The data will be send after the next FD_WRITE event, do nouthing\n");
           break;
       case WSA_IO_PENDING:
           printf("\nSendTo(%d...) return is: WSA_IO_PENDING(%d)\n",h_Client, u32_Error);
           printf("\nWSA_IO_PENDING -> Error: A previous Send operation is still pending. This data will not be sent, try latter\n");
           break;
    
       default:
           printf("\nSendTo(%d...)failed with severe error: %d\n",h_Client, u32_Error);
           break;
    };
  10. SendToString - запрос на отправку блока данных, представляющего собой массив типа char, по существующему соединению.

    static EXPFUNC int __stdcall SendToString(SOCKET h_Client,        // in
                                              char*  ps8_SendBuf,     // in
                                              int    s32_SendBufLen); // in -  SendBuf[] array size in int element

    Параметры функции:

    • SOCKET h_Client - хэндл существующего соединения.

    • char ps8_SendBuf[s32_SendBufLen] - массив char, представляющий собой единый блок, который требуется передать клиенту.

    • int s32_SendBufLen - размерность массива.

    При успешном выполнении функция возвращает ноль, в противном случае - код ошибки winsock2 API. Пример C++:

    char ps8_SendBuf[200];
    int  s32_SendBufLen=200;
    
    int u32_Error = cNetEventsProcDLL::SendToString(h_Client, ps8_SendBuf, s32_SendBufLen);   
    
    switch (u32_Error)
    {
       case 0:
           printf("\nSendTo(...) OK");
           break;
       case WSAEWOULDBLOCK:
           printf("\nSendTo(%d...) return is: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error);
           printf("\nWSAEWOULDBLOCK -> The data will be send after the next FD_WRITE event, do nouthing\n");
           break;
       case WSA_IO_PENDING:
           printf("\nSendTo(%d...) return is: WSA_IO_PENDING(%d)\n",h_Client, u32_Error);
           printf("\nWSA_IO_PENDING -> Error: A previous Send operation is still pending. This data will not be sent, try latter\n");
           break;
    
       default:
           printf("\nSendTo(%d...)failed with severe error: %d\n",h_Client, u32_Error);
           break;
    };
  11. ReadFromInt - запрос на прием блока данных, представляющего собой массив чисел типа int, для существующего соединения.

    static EXPFUNC int __stdcall ReadFromInt(SOCKET h_Client,       // in
                                             int*   ps32_ReadBuf,   // in
                                             int    s32_ReadBufLen, // ReadBuf[] array size in int element
                                             int*   ps32_ReadLen);  // out - int ps32_ReadLen[1] - actual count of read data in int element

    Параметры функции:

    • SOCKET h_Client - хэндл существующего соединения.

    • int ps32_ReadBuf[s32_ReadBufLen] - массив чисел типа int, предназначенный для приема блока данных.

    • int s32_ReadBufLen - размерность принимающего массива.

    • int* ps32ReadLen - в эту переменную помещается реальный размер принятого и помещенного в массив ps32_ReadBuf[] блока данных в int единицах. Если размер принимающего массива недостаточен для приема блока данных, то в эту переменную помещается значение размера, необходимого для приема блока данных, со знаком минус. Сам блок остается в стеке, при этом код возврата будет равен нулю. Если в стеке клиента с указанным хэндлом отсутствуют данные, то эта переменная будет равна нулю, при этом код возврата будет так же равен нулю.

    При успешном выполнении функция возвращает ноль, в противном случае - код ошибки winsock2 API. Пример C++:

    int ps32_ReadBuf[2000];
    int s32_ReadBufLen=2000;
    int ps32_ReadLen[1];
    int u32_Error = cNetEventsProcDLL::ReadFromInt(h_Client, ps32_ReadBuf, s32_ReadBufLen, ps32_ReadLen);   
            
    if(u32_Error)
        printf("ReadFromInt(%d...) failed with error: %d", h_Client, u32_Error);
    else
        if(ps32_ReadLen[0] >= 0) 
            printf("ReadFromInt(%d...) fine, %d int number was read", h_Client, ps32_ReadLen[0]);
        else 
            printf("ReadFromInt(%d...) fine, but ReadBuf must be at least %d int number size", h_Client, -ps32_ReadLen[0]);
  12. ReadFromDouble - запрос на прием блока данных, представляющего собой массив чисел типа double, для существующего соединения.

    static EXPFUNC int __stdcall ReadFromDouble(SOCKET h_Client,        // in
                                                double* pd_ReadBuf,     // in
                                                int     s32_ReadBufLen, // ReadBuf[] array size in double element
                                                int*    ps32_ReadLen);  // out - int ps32_ReadLen[1] - actual count of read data in double element

    Параметры функции:

    • SOCKET h_Client - хэндл существующего соединения.

    • double pd_ReadBuf[s32_ReadBufLen] - массив чисел типа double, предназначенный для приема блока данных.

    • int s32_ReadBufLen - размерность принимающего массива.

    • int* ps32ReadLen - в эту переменную помещается реальный размер принятого и помещенного в массив ps32_ReadBuf[] блока данных в double единицах. Если размер принимающего массива недостаточен для приема блока данных, то в эту переменную помещается значение размера, необходимого для приема блока данных, со знаком минус. Сам блок остается в стеке, при этом код возврата будет равен нулю. Если в стеке клиента с указанным хэндлом отсутствуют данные, то эта переменная будет равна нулю, при этом код возврата будет так же равен нулю.

    При успешном выполнении функция возвращает ноль, в противном случае - код ошибки winsock2 API. Пример C++:

    double ps32_ReadBuf[2000];
    int    s32_ReadBufLen = 2000;
    int    ps32_ReadLen[1];
    int    u32_Error = cNetEventsProcDLL::ReadFromDouble(h_Client, pd_ReadBuf, s32_ReadBufLen, ps32_ReadLen);   
            
    if(u32_Error)
        printf("ReadFromDouble(%d...) failed with error: %d", h_Client, u32_Error);
    else
        if(ps32_ReadLen[0] >= 0) 
            printf("ReadFromDouble(%d...) fine, %d double number was read", h_Client, ps32_ReadLen[0]);
        else 
            printf("ReadFromDouble(%d...) fine, but ReadBuf must be at least %d double number size", h_Client, -ps32_ReadLen[0]);
  13. ReadFromString - запрос на прием блока данных, представляющего собой массив типа char, для существующего соединения.

    static EXPFUNC int __stdcall ReadFromString(SOCKET h_Client,       // in
                                                char*  ps8_ReadBuf,    // in
                                                int    s32_ReadBufLen, // ReadBuf[] array size in char element
                                                int*   ps32_ReadLen);  // out - int ps32_ReadLen[1] - actual count of read data in char element

    Параметры функции:

    • SOCKET h_Client - хэндл существующего соединения.

    • char ps8_ReadBuf[s32_ReadBufLen] - массив типа char, предназначенный для приема блока данных.

    • int s32_ReadBufLen - размерность принимающего массива.

    • int* ps32ReadLen - в эту переменную помещается реальный размер принятого и помещенного в массив ps32_ReadBuf[] блока данных в char единицах. Если размер принимающего массива недостаточен для приема блока данных, то в эту переменную помещается значение размера, необходимого для приема блока данных, со знаком минус. Сам блок остается в стеке, при этом код возврата будет равен нулю. Если в стеке клиента с указанным хэндлом отсутствуют данные, то эта переменная будет равна нулю, при этом код возврата будет так же равен нулю.

    При успешном выполнении функция возвращает ноль, в противном случае - код ошибки winsock2 API. Пример C++:

    char ps8_ReadBuf[2000];
    int  s32_ReadBufLen = 2000;
    int  ps32_ReadLen[1];
    int  u32_Error = cNetEventsProcDLL::ReadFromString(h_Client, ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen);   
            
    if(u32_Error)
        printf("ReadFromStrung(%d...) failed with error: %d", h_Client, u32_Error);
    else
        if(ps32_ReadLen[0] >= 0)
            printf("ReadFromString(%d...) fine, %d char was read", h_Client, ps32_ReadLen[0]);
        else 
            printf("ReadFromString(%d...) fine, but ReadBuf must be at least %d char size", h_Client, -ps32_ReadLen[0]);


3. Реализация проекта

В прикрепленном архиве NetServerClient.zip находятся два проекта Microsoft Visual Studio 2010 Ultimate в исходных кодах:

  • NetEventsProc - для построения NetEventsProc.exe
  • NetEventsProcDLL - для построения NetEventsProcDLL.dll

Исходные коды подробно прокомментированы, так что каждый, при необходимости, может разобраться в деталях реализации и, при желании, адаптировать проекты под свои специфические потребности.

NetEventsProc.exe реализует сервер и клиентов, используя асинхронные сокеты. Для перевода сокетов в асинхронный режим используется один из возможных методов работы в асинхронном режиме: привязка сокетов к сетевым событиям WSAEventSelect (h_Socket, h_Event, FD_ALL_EVENTS).

Если данная статья вызовет интерес у читателей, то в следующей версии статьи мы обсудим детали реализации во всех подробностях. А пока что остановимся на этом, заметим только еще раз, что данный проект базируется на фундаментальной работе большого Мастера Elmue.


4. Заключение

Надеюсь, что данная статья поможет решить проблему обмена информацией между терминалом МetaТrader 4 и сторонними приложениями, независимо от их местонахождения: на локальном, по отношению к терминалу, компьютере или на удаленном компьютере. Надеюсь также, что в приведенных в статье кодах не очень много "Спагетти", а механизм использования DLL функций получился достаточно простым и прозрачным. По крайней мере, я к этому стремился.

Необходимо отметить еще один весьма важный момент:

Почти все компьютеры являются участниками локальной сети (LAN). Даже если вы имеете единственный компьютер, то и он, скорее всего, является участником LAN, состоящей из одного компьютера.

Подключение к глобальной интернет-сети (WAN) выполняется через дополнительное аппаратное устройство, которое может называться маршрутизатором, роутером, модемом или еще каким-то техническим термином, который мы будем называть маршрутизатор. Именно для этого маршрутизатора провайдеры выделяют глобальный IP адрес.

Маршрутизаторы решают определенные проблемы безопасности при работе с WAN, позволяют экономить глобальные IP адреса, помогают упорядочить выход в WAN из локальной сети. Но одновременно, по существу, искажают первоначальный смысл глобальной интернет-сети WAN, который предполагал возможность прямой связи любых двух компьютеров с помощью WAN (связь Peer-To-Peer).

Такой эффект вызван тем, что практически любой маршрутизатор производит так называемую трансляцию сетевых адресов (NAT - Network Address Translation). Сетевой адрес представлен кортежем <протокол, IP, порт>. Любые элементы этого кортежа могут быть изменены маршрутизатором. Какие именно - зависит от модели маршрутизатора.

В связи с этим компьютер, выходящий в WAN из LAN, не обладает всеми теми возможностями, которые предоставляет WAN в чистом виде. Подавляющее большинство маршрутизаторов обладает OUTBOUND - свойством, которое заключается в том, что маршрутизатор запоминает сетевой адрес клиента локальной сети, обратившегося из LAN в глобальную сеть WAN с каким-либо запросом.

Благодаря этому, маршрутизатор способен передать этому клиенту всю информацию, пришедшую на маршрутизатор как ответ на запрос клиента. Таким образом, клиент LAN сети может соединяться с серверами WAN сети, однако не всегда, так как в целях безопасности и рабочей дисциплины некоторые сетевые адреса могут быть аппаратно заблокированы.

Поэтому, для организации сервера на компьютере, находящегося в LAN сети, в любом случае требуется произвести так называемый Port Forwarding или в общепринятом переводе - "проброску портов". Для примеров данной статьи, в простейшем случае одного компьютера в LAN, необходимо произвести "проброску порта" с номером 2000. Это можно сделать самостоятельно, подключившись из браузера к мини-сайту, расположенному в маршрутизаторе, или обратиться к специалисту. Чаще всего этот мини-сайт доступен по адресу 192.168.1.1.

Все это необходимо учитывать, если вы хотите иметь возможность обмениваться информацией через WAN сеть.

В следующей версии статьи будет представлен вариант связи Peer-To-Peer (p2p).

Прикрепленные файлы |
EXE.zip (21.35 KB)
FastStart.zip (28.26 KB)
NetServerClient.zip (56.32 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (17)
Mikhail Rudyk
Mikhail Rudyk | 26 мая 2015 в 19:24
Здраствуйте Я все сделал по инструкции чтоб сервер передавал значения индикатора клиенту Далее клиент конектится к серверу а потом пишет ошибку     ClientIndicator EURUSD,H1: SendToString(0,...) failed with error: WSA_INVALID_PARAMETER  Помогите пожалуйсто ато уже много чего перепробовал но ничего не получается я в С++ не силен
Михаил
Михаил | 15 окт. 2015 в 16:40
Mikhail Rudyk:
Здраствуйте Я все сделал по инструкции чтоб сервер передавал значения индикатора клиенту Далее клиент конектится к серверу а потом пишет ошибку     ClientIndicator EURUSD,H1: SendToString(0,...) failed with error: WSA_INVALID_PARAMETER  Помогите пожалуйсто ато уже много чего перепробовал но ничего не получается я в С++ не силен

У меня такая же проблема, если запускать ClientIndicator c ServerSendInd. Причина скорее всего в том, что хэндл подключения равен 0, поэтому попытка передать что-то заканчиваеться ошибкой WSA_INVALID_PARAMETER. До того как попробовать пример из статьи я сам написал сервер и клиент на mql. Клиент не может подключится к серверу, выдает ошибку 10051 WSAENETUNREACH A socket operation was attempted to an unreachable network. Правда иногда подключение типо происходит, ConnectTo возвращает 0, но хэндл тоже 0 и сервер не видит никаких подключений через GetAllConnections. При этом сам сервер успешно создаеться, это можно проверить если в командной строке прописать netstat -a. в списке появляеться подключение к указаному порту с состоянием LISENING. 

Кто-нибудь знает в чем дело?

Михаил
Михаил | 16 окт. 2015 в 09:48
Михаил:

У меня такая же проблема, если запускать ClientIndicator c ServerSendInd. Причина скорее всего в том, что хэндл подключения равен 0, поэтому попытка передать что-то заканчиваеться ошибкой WSA_INVALID_PARAMETER. До того как попробовать пример из статьи я сам написал сервер и клиент на mql. Клиент не может подключится к серверу, выдает ошибку 10051 WSAENETUNREACH A socket operation was attempted to an unreachable network. Правда иногда подключение типо происходит, ConnectTo возвращает 0, но хэндл тоже 0 и сервер не видит никаких подключений через GetAllConnections. При этом сам сервер успешно создаеться, это можно проверить если в командной строке прописать netstat -a. в списке появляеться подключение к указаному порту с состоянием LISENING. 

Кто-нибудь знает в чем дело?

Решение этой проблемы подано в англоязычном аналоге данной темы на форуме. 
Ekaterina Iginova
Ekaterina Iginova | 14 дек. 2015 в 14:18

Доброго времени суток! Для начала хочу выразить благодарность Автору за данную статью! Вы поистине гениальный человек!

Но я столкнулась со следующей проблемой:

Все примеры выполнила успешно.

Кроме последнего где передаются данные индикатора. Через локальный IP все работает безупречно! Но когда пытаюсь использовать глобальный IP то метатрейдер начинает жутко тормозить и в итоге выскакивает окошко, что  NetEventsProc.exe завершена, и метатрейдер тоже падает. Пробовала через 2000 порт, который у меня закрыт. Пробовала через 88 который открыт. Так же пробовала закидывать clientindicator на другой комп и с него коннектиться к этому компу. Там так же вылетает та же ошибка. Хэлп!

 

Благодарю 

[Удален] | 4 июл. 2017 в 10:04

Вы также связаться с авторами делать? Мне нужны некоторые настройки,

Дело не нужно начинать МТ4 клиент, подключиться к серверу, и считывает сделку, кто-то мне помочь?

Пожалуйста, свяжитесь со мной.

Виджеты Торговых сигналов MetaTrader 4 и MetaTrader 5 Виджеты Торговых сигналов MetaTrader 4 и MetaTrader 5
Совсем недавно у каждого пользователя MetaTrader 4 и MetaTrader 5 появилась возможность стать поставщиком торговых сигналов и получать дополнительную прибыль. Теперь же при помощи новых виджетов можно рассказать о торговых успехах на своем сайте, в блоге или на странице в социальной сети. Преимущества использования виджетов очевидны: рост популярности поставщика, его репутации как успешного трейдера и привлечение новых подписчиков. Все это получают те трейдеры, которые размещают виджеты на других сайтах в интернете.
MetaTrader 5 на Linux MetaTrader 5 на Linux
В этой статье расскажем, как легко установить MetaTrader 5 в популярных версиях Linux — Ubuntu и Debian. Эти системы широко используются не только на серверном оборудовании, но и на обычных компьютерах трейдерами.
Машинное обучение: как метод опорных векторов может быть использован в трейдинге Машинное обучение: как метод опорных векторов может быть использован в трейдинге
Метод опорных векторов уже достаточно давно применяется в таких областях науки, как биоинформатика и прикладная математика для анализа сложных наборов данных и выявления полезных паттернов, которые используются для классификации данных. Цель данной статьи - показать, что из себя представляет метод опорных векторов, как он работает, и почему он так полезен для выявления сложных паттернов.
MetaTrader 5 на Mac OS MetaTrader 5 на Mac OS
Продукты компании Apple пользуются большой популярностью. Компания MetaQuotes Software Corp. внимательно следит за развитием сферы компьютерных технологий и уже выпустила специальные мобильные приложения для iOS-устройств - MetaTrader 4 для iPhone и MetaTrader 5 для iPhone. Не раз на форуме MQL5.community поднималась тема о возможности запуска MetaTrader 5 под управлением операционной системы Mac OS. В этой статье мы расскажем, как легко использовать MetaTrader 5 в любимой многими операционной системе от Apple.