Español Português
preview
Моделирование рынка (Часть 09): Сокеты (III)

Моделирование рынка (Часть 09): Сокеты (III)

MetaTrader 5Примеры |
368 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье, "Моделирование рынка (Часть 08): Сокеты (II)", мы начали разрабатывать практическое приложение, использующее сокеты. Наша цель - продемонстрировать, как этот инструмент используется в программировании, ориентированном на MetaTrader 5. Правда, MQL5 не позволяет создать сервер непосредственно на чистом MQL5. Но, поскольку использование сокетов не зависит ни от языка, ни даже от ОС, мы можем использовать их и в MetaTrader 5, программируя на MQL5.

Однако по внутренним причинам самой платформы MetaTrader 5 мы не можем использовать индикаторы вместе с сокетами. Другими словами, нельзя включать в код указателя вызовы процедур с сокетами. По возможности мы можем заблокировать или скомпрометировать расчеты, выполняемые в рамках индикаторов.

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

Таким образом, у нас не возникнет подобных проблем, оставляя наш мини-чат изолированным в своем собственном окне.

Однако исполняемый файл, который сгенерируется в статье выше, не сможет удовлетворить наши потребности в том, чтобы мини-чат действительно работал. Нам нужно реализовать ещё несколько деталей. На этом мы завершим создание необходимой поддержки для запуска мини-чата в MetaTrader 5. Однако, как уже говорилось в начале, на данный момент MQL5 не способен охватить всё, что нам нужно, поэтому придется прибегнуть к внешнему программированию. Но на данный момент можно кое-что сделать. Впрочем, об этом я расскажу позже в этой статье. Давайте теперь приступим к работе и закончим ту часть, которая будет разработана на MQL5.


Реализация основного класса

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. class C_Connection
05. {
06.    private   :
07.       int      m_Socket;
08.       char     m_buff[];
09.       string   m_info;
10.       bool     m_NewLine;
11.    public   :
12. //+------------------------------------------------------------------+
13.       C_Connection(string addr, ushort port, ushort timeout = 1000)
14.          :m_Socket(INVALID_HANDLE),
15.           m_info(""),
16.           m_NewLine(true)
17.       {
18.          if ((m_Socket = SocketCreate()) == INVALID_HANDLE) Print("Unable to create socket. Error: ", GetLastError());
19.          else if (SocketConnect(m_Socket, addr, port, timeout)) return;
20.          else Print("Connection with the address [", addr,"] in port: ",port, " failed. Error code: ", GetLastError());
21.          SetUserError(1);
22.       }
23. //+------------------------------------------------------------------+
24.       ~C_Connection()
25.       {
26.          SocketClose(m_Socket);
27.       }
28. //+------------------------------------------------------------------+
29.       bool ConnectionWrite(string szMsg)
30.       {
31.          int len = StringToCharArray(szMsg, m_buff) - 1;
32. 
33.          if (m_Socket != INVALID_HANDLE)
34.             if (len >= 0)
35.                if (SocketSend(m_Socket, m_buff, len) == len)
36.                   return true;
37.          Print("Connection Write: FAILED...");
38. 
39.          return false;
40.       }
41. //+------------------------------------------------------------------+
42.       const string ConnectionRead(ushort timeout = 500)
43.       {
44.          int ret;
45. 
46.          if (m_NewLine)
47.          {
48.             m_info = "";
49.             m_NewLine = false;
50.          }
51.          ret = SocketRead(m_Socket, m_buff, SocketIsReadable(m_Socket), timeout);
52.          if (ret > 0)
53.          {
54.             m_info += CharArrayToString(m_buff, 0, ret);
55.             for (ret--; (ret >= 0) && (!m_NewLine); ret--)
56.                m_NewLine = (m_buff[ret] == '\n') || (m_buff[ret] == '\r');
57.          }
58.          
59.          return (m_NewLine ? m_info : "");
60.       }
61. //+------------------------------------------------------------------+
62. };
63. //+------------------------------------------------------------------+

Заголовочный файл C_Study.mqh

Очень важно, чтобы вы обратили внимание на некоторые детали этого кода, поскольку их понимание позволит вам действительно понять, как заставить мини-чат работать. Наш класс содержит несколько частных глобальных переменных, как можно видеть между строками 07 и 10. Прошу заметить, что все эти переменные появляются после приватной части.

Поэтому доступ к ним за пределами класса невозможен. Первое, что мы делаем при использовании данного класса, - вызываем его конструктор. Таким образом, на первом месте будет выполнена инструкция в строке 13. Теперь обратите внимание, что в строках 14, 15 и 16 выполняются определенные действия. Точнее, там инициализируются глобальные переменные класса. Один важный момент: мы делаем это здесь и таким образом по привычке, но в основном для того, чтобы не забыть сделать это внутри тела конструктора. Поверьте, такая «забывчивость» встречается очень часто. Но при этом мы не забываем правильно инициализировать основные переменные.

Хорошо. Как только конструктор начинает выполняться, в строке 18 мы используем вызов стандартной библиотеки, чтобы создать сокет. В случае неудачи мы выведем сообщение в MetaTrader 5. В случае успеха в строке 19 мы попытаемся подключиться к сокету, который указали в параметрах. В отличие от предыдущей строки, здесь, в случае успеха, мы сразу же вернемся обратно. Причина в том, что соединение уже установлено, и в случае неудачи строка 20 сообщит нам о том, что произошло и что вызвало ошибку.

Однако если создать или подключиться к сокету не удастся, строка 21 позволит сообщить основному коду (который в данном случае является советником), что процесс подключения завершился неудачей. Когда мы будем обсуждать код советника, мы объясним, как можно обнаружить эту ситуацию. Но строка 21 выполнится только в том случае, если при попытке подключения произойдет ошибка.

Деструктор, расположенный в строке 24, имеет единственную цель - закрыть сокет. Поэтому на его теле всего одна строка. Это строка 26, которая освобождает используемый сокет, если он был создан.

Теперь давайте рассмотрим две основные функции нашего класса. Первая из них отвечает за отправку данных по соединению. Это как раз и есть функция записи, которая находится в строке 29. Данную функцию, как и следующую, надо реализовать с некоторой осторожностью, поскольку весь код не изменится полностью, независимо от того, что вы хотите отправить: изображение, звук, видео или текст, как это будет в данном случае. На самом деле меняются только две функции: запись и чтение. В данном случае мы делаем всё как можно проще. Поэтому мы не будем использовать шифрование или другие виды защиты при отправке информации. Причина очень простая: мы только тестируем и изучаем, как работают сокеты.

Поэтому, поскольку мы будем передавать только текст, первое, что нам нужно знать, - это количество символов, которые необходимо отправить. И это делается в строке 31. Вычитание единицы из количества символов связано с тем, что полученная строка соответствует стандарту C/C++, то есть она заканчивается нулевым символом. Этот символ не будет передан нами. Так как существует вероятность того, что конструктор может не сработать во время создания или даже соединения, мы должны проверить, действителен ли сокет, прежде чем пытаться передать через него что-либо. Данная проверка выполняется в строке 33. Если сокет недействителен по какой-либо причине, ничего не передается.

Как только это будет сделано, нам нужно будет проверить кое-что ещё. В этом случае необходимо проверить количество передаваемых символов, поскольку нет смысла пытаться отправить что-то, если отправлять нечего. Данная проверка выполняется в строке 34. Если все эти проверки пройдены успешно, строка 35 попытается отправить нужные данные через открытый сокет. В данном случае мы отправляем данные через обычное соединение, то есть мы не используем никакого шифрования или процесса аутентификации. Однако, MQL5 позволяет нам осуществлять безопасную отправку. Для лучшего понимания этой проблемы лучше обратиться к документации.

Если данные отправлены успешно, в строке 36 вызывающей стороне возвращается значение true. Если какая-либо из проверок окажется неудачной, мы выведем сообщение в MetaTrader 5 в строке 37 и вернем вызывающей стороне значение false в строке 39.

Теперь давайте посмотрим на функцию read, она находится в строке 42. Здесь стоит сделать небольшую паузу, и внести кое-какое объяснение. Каждая функция записи должна иметь соответствующую функцию чтения. Это не обязательно, но представьте на минуту следующий сценарий: можно создать несколько функций для отправки различных типов данных. В определенной степени целесообразно, чтобы данные поступали через соответствующие функции.

Иными словами, зачем пытаться прочитать фрагмент данных, представляющий собой видео, с помощью функции, предназначенной для чтения текста? Это же бессмысленно, правда? Поэтому всегда следите за тем, чтобы используемые вами данные и протокол были подходящими.

Данная часть протокола не связана с типом соединения TCP или UDP, а связана с тем, как была структурирована информация перед передачей. Всегда подумайте о том, что данные сокета похожи на файл, который вы читаете. Для правильного восприятия их содержания, необходимо использовать соответствующий протокол чтения.

Нет смысла пытаться читать растровые изображения с помощью протокола, который предназначен для чтения изображений JPEG. Хотя и то, и другое - изображения, их структура в файле совершенно разная. То же самое относится и к сокетам. Давайте теперь перейдем к процедуре чтения сокетов.

В отличие от статьи "Моделирование рынка (Часть 07): Сокеты (I)", где мы передавали эхо обратно на сервер, здесь мы не обязательно будем ждать ответа сервера. На самом деле мы ждем ответа в течение короткого промежутка времени. Но суть в том, что независимо от скорости ответа, мы вернемся к вызывающей стороне ещё до того, как все сообщения будут прочитаны из сокета.

Но каким образом? Можно ли вернуться к коду нашего советника до того, как полностью прочитаем сокет? Да, мы можем, но при этом должны соблюдать определенные меры предосторожности. Именно поэтому мы упомянули минуту назад, что следует обратить внимание на правильную обработку типа информации, ожидаемой в сокете. В нашей функции чтения по умолчанию мы ждем 500 миллисекунд перед возвратом. По истечении данного промежутка времени мы вернемся к основному коду, даже если в сокете нет никакой информации.

Однако если за это время появится какая-либо информация, мы прочитаем ее в строке 51. Теперь обратите внимание на следующий момент, поскольку это важно, если вы решите реализовать собственный сервер. В строке 54 мы добавляем содержимое сокета в возвращаемую строку. Однако данная строка возврата вернет что-то вызывающей стороне только в том случае, если в строке будет обнаружен символ новой строки или возврата каретки в строке символов. Чтобы проверить это, мы используем цикл в строке 55, который проверит полученное содержимое в поисках упомянутых символов.

Когда встречается один из этих символов, переменная m_NewLine устанавливается в true. Таким образом, в строке 59 содержимое, считанное из сокета, может быть отправлено вызывающей стороне. Аналогично, когда происходит новый вызов, проверка в строке 46 позволяет очистить содержимое строки и начать новый цикл.

Теперь обратите внимание: в коде функции записи сокета, которая находится в строке 29, мы не добавляем те символы, которые ожидает функция чтения. И объект OBJ_EDIT не добавляет такие символы, также как и коды, вызывающие функцию ConnectionWrite, не добавляют такие символы. Кто же добавляет эти символы? Это сделает сервер. Поэтому, если вы реализуете свой собственный сервер, вам придется добавить эти символы; иначе мини-чат не сможет отличить одно отправленное сообщение от другого.

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


Реализация советника

Полный код советника, реализующего мини-чат, можно посмотреть ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Mini Chat for demonstration of using sockets in MQL5."
04. #property link      "https://www.mql5.com/pt/articles/12673"
05. #property version   "1.00"
06. //+------------------------------------------------------------------+
07. #define def_IndicatorMiniChat   "Indicators\\Mini Chat\\Mini Chat.ex5"
08. #resource "\\" + def_IndicatorMiniChat
09. //+------------------------------------------------------------------+
10. #include <Market Replay\Mini Chat\C_Connection.mqh>
11. #include <Market Replay\Defines.mqh>
12. //+------------------------------------------------------------------+
13. input string   user00 = "127.0.0.1";      //Address
14. input ushort   user01 = 27015;            //Port
15. //+------------------------------------------------------------------+
16. long    gl_id;
17. int    gl_sub;
18. C_Connection *Conn;
19. //+------------------------------------------------------------------+
20. int OnInit()
21. {
22.    Conn = new C_Connection(user00, user01);
23.    if (_LastError > ERR_USER_ERROR_FIRST)
24.       return INIT_FAILED;   
25.    ChartIndicatorAdd(gl_id = ChartID(), gl_sub = (int) ChartGetInteger(gl_id, CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_IndicatorMiniChat));
26. 
27.    EventSetTimer(1);
28. 
29.    return INIT_SUCCEEDED;
30. }
31. //+------------------------------------------------------------------+
32. void OnDeinit(const int reason)
33. {
34.    EventKillTimer();
35.    delete Conn;
36.    ChartIndicatorDelete(gl_id, gl_sub, ChartIndicatorName(gl_id, gl_sub, 0));
37. }
38. //+------------------------------------------------------------------+
39. void OnTick()
40. {
41. }
42. //+------------------------------------------------------------------+
43. void OnTimer()
44. {
45.    string sz0 = (*Conn).ConnectionRead();
46.    if (sz0 != "")
47.       EventChartCustom(gl_id, evChatReadSocket, 0, 0, sz0);
48. }
49. //+------------------------------------------------------------------+
50. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
51. {
52.    if (id == CHARTEVENT_CUSTOM + evChatWriteSocket)
53.       (*Conn).ConnectionWrite(sparam);
54. }
55. //+------------------------------------------------------------------+

Исходный код советника

Как видите, код довольно прост. С нашей стороны, программирование практически не требуется. Тем не менее, есть некоторые моменты, которые не стоит упускать. Обратите внимание на строки 07 и 08, где мы добавляем в код советника индикатор, созданный в предыдущей статье. Таким образом, после компиляции кода советника исполняемый файл индикатора можно удалить, поскольку он будет встроен в советник. В предыдущей статье мы объяснили, почему были приняты такие меры. По сравнению с предыдущей статьей, код разработан без особых изменений.

Теперь добавляется вызов для получения событий от часов. Это выполняется в строке 27. Таким образом, примерно каждую секунду будет выполняться код, отвечающий за управление событиями часов, то есть будет происходить вызов OnTime. Что нас действительно интересует в коде советника, так это вызов OnTime и вызов OnChartEvent. Именно эти два вызова позволяют мини-чату функционировать в том виде, в котором он сейчас реализуется. Давайте сначала разберемся с процедурой OnTime.

Когда в MetaTrader 5 срабатывает событие OnTime, наш советник перехватывает данный вызов. В строке 45 мы попытаемся прочитать сокет, указанный в строках 13 и 14, то есть подключимся к сети. Об этом мы рассказывали две статьи назад, когда говорили о сокетах. Если у вас есть сомнения на этот счет, ознакомьтесь со статьей Сокеты (I).

Хорошо. Попытка чтения может вернуть строку, а может и не вернуть её. Если функция чтения возвращает ответ, то проверка в строке 46 прошла успешно. Затем мы запустим в строке 47 пользовательское событие. Цель этого события как раз и состоит в том, чтобы дать возможность индикатору мини-чата получить доступ к информации, опубликованной в сокете. Если бы мини-чат реализовался непосредственно в советнике, то такое пользовательское событие было бы совершенно излишним. Но поскольку мы разделили составляющие, необходимо отправить данные индикатору, чтобы он отвечал за отображение информации в текстовой панели. Прошу обратить внимание на один интересный момент: в зависимости от того, что мы хотим показать, мы должны приспособиться к наилучшему способу реализации решения.

Что касается процедуры OnTime, то здесь нет никаких дополнительных подробностей. Всё очень просто. Процедура OnChartEvent также довольно проста, только в этом случае мы будем перехватывать запрос от индикатора мини-чата, чтобы записать в сокет. Чтобы проверить, есть ли у нас такой вызов, мы будем использовать проверку в строке 52. Если событие подтверждено как пользовательское, строка 53 передает данные, предоставленные индикатором мини-чата, в функцию записи.

В данной системе связи есть одна деталь, которая может быть не совсем понятна тем, кто не знаком с событийно-ориентированным программированием. Загвоздка в том, что если какая-либо другая программа (будь то другой индикатор, скрипт или даже сервис) запустит пользовательское событие с тем же значением evChatReadSocket, индикатор мини-чата будет интерпретировать его как исходящее от советника. То же самое происходит, если у запущенного пользовательского события есть то же значение, как у evChatWriteSocket. В этом случае советник интерпретирует ситуацию так, как если бы событие произошло от индикатора мини-чата.

Не знаю, заметили ли вы, какие возможности это открывает. Тем более если мы знаем, какой адрес и порт должен принимать данные, которые мы собираемся поместить в сокет, ведь мы также сможем читать заданный адрес или порт. Такая ситуация позволяет создать маленького «шпиона», который наблюдает за происходящим в определенном месте. Поэтому мы всегда должны правильно шифровать и кодировать информацию. Но поскольку мы используем здесь всё только в демонстрационных целях, риск утечки чего-либо сведен к минимуму. Как можно увидеть в видеороликах, протестировать сетевое подключение можно даже без установленной программы.

Однако, учитывая, что всё вышеперечисленное является частью MQL5, вы, вероятно, будете удовлетворены результатами и сможете развернуть сервер. Но для тех, кто не знает, как это реализовать, мы рассмотрим эту тему в новой записи, просто из любопытства.


Реализуем сервер для мини-чата

Внедрение сервера не является сложной задачей, на самом деле всё очень просто. Однако внедрение сервера отличается от его эксплуатации. Реализация - это создание кода, в то время как запуск его в работу предполагает предотвращение утечки той информации, к которой изначально не должен быть предоставлен доступ.

Мы не будем рассматривать здесь настройку сервера. Мы ограничимся только реализацией. Но я хочу уточнить, что код, который будет показан, не должен использоваться в целях эксплуатации. Показанный код имеет сугубо образовательную цель, то есть не используйте его без понимания того, что вы делаете. Используйте его как способ понять, как на самом деле реализован сервер. Полный код можно найти ниже:

001. #define WIN32_LEAN_AND_MEAN
002. 
003. #include <winsock2.h>
004. #include <iostream>
005. #include <sstream>
006. #include <stdlib.h>
007. 
008. #pragma comment (lib, "Ws2_32.lib")
009. 
010. #define DEFAULT_BUFLEN   256
011. #define DEFAULT_PORT     27015
012. 
013. using namespace std;
014. 
015. int __cdecl main(void)
016. {
017.    WSADATA wsData;
018.    SOCKET listening, slave;
019.    sockaddr_in hint;
020.    fd_set master;
021.    bool Looping = true;
022.    int ConnectCount;
023.    string szMsg;
024. 
025.    if (WSAStartup(MAKEWORD(2, 2), &wsData) != EXIT_SUCCESS)
026.    {
027.       cout << "Can't Initialize WinSock! Quitting..." << endl;
028.       return EXIT_FAILURE;
029.    }
030.    if ((listening = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
031.    {
032.       cerr << "Can't create a socket! Quitting" << endl;
033.       WSACleanup();
034.       return EXIT_FAILURE;
035.    }
036.    else
037.       cout << "Creating a Socket..." << endl;
038. 
039.    hint.sin_family = AF_INET;
040.    hint.sin_port = htons(DEFAULT_PORT);
041.    hint.sin_addr.S_un.S_addr = INADDR_ANY;
042. 
043.    if (bind(listening, (sockaddr*)&hint, sizeof(hint)) == SOCKET_ERROR)
044.    {
045.       cout << "Bind failed with error:" << WSAGetLastError() << endl;
046.       WSACleanup();
047.       return EXIT_FAILURE;
048.    }
049.    else
050.       cout << "Bind success..." << endl;
051. 
052.    if (listen(listening, SOMAXCONN) == SOCKET_ERROR)
053.    {
054.       cout << "Listen failed with error:" << WSAGetLastError() << endl;
055.       WSACleanup();
056.       return EXIT_FAILURE;
057.    }
058.    else
059.       cout << "Listen success..." << endl;
060.    
061.    FD_ZERO(&master);
062.    FD_SET(listening, &master);
063.    cout << "Waiting for client connection" << endl;
064. 
065.    while (Looping)
066.    {
067.       fd_set tmp = master;
068. 
069.       ConnectCount = select(0, &tmp, nullptr, nullptr, nullptr);
070.       for (int c0 = 0; c0 < ConnectCount; c0++)
071.       {
072.          if ((slave = tmp.fd_array[c0]) == listening)
073.          {
074.             SOCKET NewClient = accept(listening, nullptr, nullptr);
075. 
076.             szMsg = "Sent by SERVER: Welcome to Mini Chart\r\n";
077.             FD_SET(NewClient, &master);
078.             send(NewClient, szMsg.c_str(), (int)szMsg.size() + 1, 0);
079.             cout << "Client #" << NewClient << " connecting..." << endl;
080.          }
081.          else
082.          {
083.             char buff[DEFAULT_BUFLEN];
084.             int bytesIn;
085. 
086.             ZeroMemory(buff, DEFAULT_BUFLEN);
087.             if ((bytesIn = recv(slave, buff, DEFAULT_BUFLEN, 0)) <= 0)
088.             {
089.                closesocket(slave);
090.                cout << "Client #" << slave << " disconnected..." << endl;
091.                FD_CLR(slave, &master);
092.             }
093.             else
094.             {
095.                if (buff[0] == '\\') //Check command ...
096.                {
097.                   szMsg = string(buff, bytesIn);
098. 
099.                   if (szMsg == "\\shutdown")
100.                   {
101.                      Looping = false;
102.                      break;
103.                   }
104.                   continue;
105.                }
106.                for (u_int c1 = 0; c1 < master.fd_count; c1++)
107.                {
108.                   SOCKET out = master.fd_array[c1];
109. 
110.                   if ((out != listening) && (out != slave))
111.                   {
112.                      ostringstream s1;
113. 
114.                      s1 << "Client #" << slave << ": " << buff << "\r\n";
115.                      send(out, s1.str().c_str(), (int)s1.str().size() + 1, 0);
116.                   }
117.                }
118.             }
119. 
120.          }
121.       }
122.    }
123. 
124.    FD_CLR(listening, &master);
125.    closesocket(listening);
126.    szMsg = "Server is shutting down. Goodbye\r\n";
127.    while (master.fd_count > 0)
128.    {
129.       slave = master.fd_array[0];
130.       send(slave, szMsg.c_str(), (int)szMsg.size() + 1, 0);
131.       FD_CLR(slave, &master);
132.       closesocket(slave);
133.    }
134. 
135.    WSACleanup();
136. 
137.    return EXIT_SUCCESS;
138. }

Исходный код сервиса

Один важный момент о коде этого сервера: хотя он написан на C++, нам придется компилировать его средствами Visual Studio. Но если вы поймете его работу, вы сможете изменить код до такой степени, что сможете компилировать его с помощью GCC. Но не зацикливайтесь на этом коде, вам нужно понять, как он работает, потому что это действительно важно.

Мы теперь объясним некоторые основные моменты, особенно для тех, кто не знаком с C++ и программированием сокетов на C/C++. Строка 01, как и 08, связана с компилятором Visual Studio. В строке 10 задается размер буфера, который будет использоваться для чтения сокета. Можно использовать буфер большего или меньшего размера, но имейте в виду, что если буфер слишком мал, то для получения всей информации потребуется несколько считываний из сокета. В любом случае, для наших целей (они чисто дидактические), достаточно одного буфера в 256 символов.

Строка 11, однако, очень важна, так как в ней определяется порт, который будет использоваться. Мы могли бы сделать это и по-другому, передав значение в качестве аргумента в командной строке, но идея заключается в том, чтобы сделать это как можно проще, чтобы объяснение было понятным. Так, между строками 17 и 23 мы объявляем несколько переменных, которые будут использоваться более последовательно. Хотя мы включили весь код в функцию main, в идеале он должен быть разделен на небольшие подпрограммы. Однако, поскольку данный сервер очень прост, мы можем позволить себе сделать всё это в рамках процедуры main.

Теперь обратите внимание, что на каждом шаге для настройки сокета и для разрешения серверу прослушивать порт, чтобы установить соединение с клиентом, на консоль выводится сообщение. Таким образом, когда сокет создается, строка 37 показывает подтверждение этой информации на консоли. Прошу заметить, что для этого нам нужно выполнить два шага. Первый - это использование вызова WSAStartup, а второй - сам вызов сокета. В таких системах, как Linux, вызов WSAStartup не нужен, и в некоторых реализация сокетов в Windows он также не используется. Но в большинстве случаев при работе на Windows рекомендуется использовать вызов WSAStartup.

В любом случае, сокет будет создан. Между строками 39 и 41 мы настраиваем параметры сокета. Данная конфигурация указывает, какой порт будет использоваться, какой адрес наблюдается и какой тип сокета используется. Так как мы тестируем сокет, мы позволим любому адресу подключаться к нему. Но при желании можно задать конкретный адрес. Однако на серверах он обычно остается активированным для любого соединения. Опять же, важно знать, как настроить данную часть, потому что если сделать это неправильно, то получится полный хаос. После этого нам нужно сообщить системе, как настроится сокет, что и делается в строке 43.

В строке 52 мы настраиваем максимальное количество одновременно открытых соединений, которые будет принимать сервер. Данный шаг совсем не сложный. На этом этапе сервер настроен и готов принимать соединения. Затем нам нужно подготовить несколько вещей, чтобы у нас была возможность отслеживать соединения, которые будут происходить. Это делается в строках 61 и 62. Идея состоит в том, чтобы создать динамический массив, содержащий все открытые соединения для работы сервера, как показано в демонстрационных видео.

После этого мы переходим на цикл на строке 65. Этот цикл может быть прерван клиентом, как показано в демонстрационном видео. Но есть несколько моментов, которые я хочу объяснить в рамках данного цикла. В основном это чтение той информации, которую клиент разместил на сокете, и передача её всем остальным клиентам, так что это не так важно.

Однако во время объяснения кода на MQL5 мы упомянули, что важно включить возврат каретки или символ новой строки, поскольку клиент не включил их в сообщение, так как они были добавлены сервером. Теперь, когда мы смотрим на код сервера, обратите внимание на строку 114, где мы размещаем символы, необходимые для того, чтобы MQL5-клиент знал о получении полного сообщения. Если этого не сделать здесь, на сервере, то мини-чат, созданный на MQL5, будет работать неправильно.

Это та часть, о которой нам придется позаботиться, если мы создадим свой собственный сервер. Надо убедиться в том, что сервер добавляет те же символы. Порядок не имеет значения, но они должны находиться в конце сообщения.

Теперь начинается самое интересное. Мы объяснили выше, и это можно увидеть в демонстрационном видео, что клиент может выключить сервер. Но как это происходит? Всё очень просто. Посмотрите на строку 95. В ней мы указываем, что в начале сообщения будет находиться символ, указывающий серверу о необходимости выполнения команды. Если проверка прошла успешно, команда будет проанализирована, а опубликованное сообщение не будет повторно передано другим подключенным клиентам. Теперь, в строке 99, мы пробуем одну из этих команд. В данном случае он чувствителен к регистру, поэтому команда должна быть набрана именно так, как ожидает сервер. Если это так, то строка 101 сообщит серверу о прекращении его деятельности и закрытии всех открытых соединений.

Видите, как всё просто? Однако вы должны помнить, что данный код для образовательных целей, поскольку любой клиент может отправить команду, которая будет выполнена сервером. На реальном сервере для предотвращения этого существуют уровни аутентификации, а также другие меры предосторожности, которые здесь не понадобятся.


Заключительные идеи

В сегодняшней статье мы завершаем создание мини-чата с помощью внешнего программирования и MQL5. Его цель - чисто дидактическая, хотя его можно изменять, улучшать и даже использовать в кругу товарищей-программистов для создания чего-то более профессионального. Думаю, вы сможете получить пользу от этих знаний. В приложении вы найдете готовый и скомпилированный MQL5-код мини-чата. Всё, что вам нужно будет сделать, - это настроить сервер. Я предпочитаю оставить это на ваше усмотрение, поскольку для этого нужно открыть порты на вашем компьютере. Программа, использованная в ролике вместе с мини-чатом - PuTTY.

ФайлОписание
Experts\Expert Advisor.mq5
Демонстрирует взаимодействие между Chart Trade и советником (для взаимодействия требуется Mouse Study).
Indicators\Chart Trade.mq5Создает окно для настройки отправляемого ордера (для взаимодействия требуется Mouse Study).
Indicators\Market Replay.mq5Создайте элементы управления для взаимодействия с сервисом репликации/моделирования (для взаимодействия требуется Mouse Study).
Indicators\Mouse Study.mq5Позволяет взаимодействовать между графическими элементами управления и пользователем (необходимо как для воспроизведения, так и для торговли на реальном рынке).
Servicios\Market Replay.mq5Создает и поддерживает сервис репликации/моделирования рынка (основной файл всей системы).
Код VS C++ Server.cppСоздает и поддерживает сокет-сервер, разработанный на C++ (версия мини-чат).
Код на Python Server.pyСоздание и поддержка сокета Python для связи между MetaTrader 5 и Excel.
ScriptsCheckSocket.mq5Позволяет проверить соединение с внешним сокетом.
Indicators\Mini Chat.mq5Позволяет реализовать мини-чат с помощью индикатора (для работы требуется сервер).
Experts\Mini Chat.mq5Позволяет реализовать мини-чат в советнике (для работы требуется использование сервера).

Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/12673

Прикрепленные файлы |
Anexo.zip (560.03 KB)
Нейросети в трейдинге: Спайковая архитектура пространственно-временного анализа рынка (Энкодер) Нейросети в трейдинге: Спайковая архитектура пространственно-временного анализа рынка (Энкодер)
В статье представлена адаптация фреймворка SDformerFlow, обеспечивающая высокую адаптивность за счёт интеграции спайкового внимания с многооконной свёрткой и взвешенным суммированием элементов Query. Архитектура позволяет каждой голове внимания обучать собственные параметры, что повышает точность и чувствительность модели к структуре анализируемых данных.
Оптимизатор Бонобо — Bonobo Optimizer (BO) Оптимизатор Бонобо — Bonobo Optimizer (BO)
В статье представлена реализация и анализ алгоритма Bonobo Optimizer, основанного на уникальных особенностях поведения приматов бонобо — динамической социальной структуре fission-fusion и трех стратегиях спаривания. Каковы интересные возможности этого метода?
Разрабатываем менеджер терминалов (Часть 3): Получаем информацию о счёте и добавляем конфигурацию Разрабатываем менеджер терминалов (Часть 3): Получаем информацию о счёте и добавляем конфигурацию
Добавляем в наше веб-приложение возможность получения и отображения информации о торговых счетах терминалов: о балансе, прибыли, статусе подключения и другой важной информации. Также реализуем гибкую систему конфигурации, позволяющую управлять параметрами приложения через внешний JSON-файл, и улучшаем пользовательский интерфейс главной страницы.
Индикатор тепловой карты рынка на основе плотности простых чисел Индикатор тепловой карты рынка на основе плотности простых чисел
Инновационный индикатор на основе теории простых чисел помогает находить сильные уровни разворота, которые не видят другие трейдеры. Тестирование на 10 активах показало: развороты в математически значимых зонах происходят в 1.5-1.8 раза чаще. Пять практических сценариев применения с конкретными правилами для фильтрации ложных пробоев и точного входа в рынок.