Советники: Программирование на MQL5 для трейдеров — исходные коды из книги. Часть 7 - страница 3

 
Я прилагаю несколько исправлений и улучшений в классах websockets.
Файлы:
wss.zip  16 kb
 
Stanislav Korotky #:
Я прилагаю несколько исправлений и улучшений в классах websockets.
Спасибо за ваши усилия
 
Stanislav Korotky #:
В кодовой базе опубликованы исправления и улучшения для чтения экономического календаря и экспорта в CSV. В частности, исправлен алгоритм сортировки для случая преимущественно отсортированных больших массивов (которые обычно получаются из MQL5 calendar API), что позволяет устранить замедления и переполнения стека.

В строке 126 msclient.mqh "return connection.handshake(url, host, origin, custom_headers);" при передаче полного 'url' вместо 'path' возникает ошибка 403 на python websocket. Мне пришлось внести это изменение, чтобы советник смог успешно подключиться к моему python websocket серверу.
Яприкрепил журнал сервера от Postman (прошел) и EA до внесения изменений (не прошел)

Но вот следующая проблема, мой сервер автоматически посылает keepalive ping с заданным интервалом, но MT5 websocket не отвечает, и это поведение заставляет сервер всегда терять соединение с клиентом сразу после таймаута ping.

Пожалуйста, любая помощь в отношении keepalive ping pong будет оценена по достоинству.

Файлы:
passed.png  88 kb
failed.png  50 kb
 
pauldic #:

В строке 126 msclient.mqh есть небольшой бутон "return connection.handshake(url, host, origin, custom_headers);", передача полного 'url' вместо 'path' приводит к ошибке 403 на python websocket. Мне пришлось внести это изменение, чтобы советник смог успешно подключиться к моему python websocket серверу.
Яприкрепил журнал сервера от Postman (прошел) и EA до внесения изменений (не прошел)

Но вот следующая проблема, мой сервер автоматически посылает keepalive ping с заданным интервалом, но MT5 websocket не отвечает, и это поведение заставляет сервер всегда терять соединение с клиентом сразу после таймаута ping.

Пожалуйста, любая помощь в отношении keepalive ping pong будет оценена по достоинству.

Здравствуйте, да, вы можете использовать путь в вызове клиента:

template<typename T>
class WebSocketClient: public IWebSocketObserver
{
   ...
   bool open(const string custom_headers = NULL)
   {
      ...
      connection = new T(&this, socket, compression);
      return connection.handshake(path, host, origin, custom_headers);
   }

С другой стороны, синтаксис HTTP-запроса поддерживает полный URI в качестве цели, и я проводил тесты с исходными кодами на разных серверах (включая wss://ws.postman-echo.com/raw), и они работали нормально. Так что я не уверен, является ли это нарушением или ваша реализация python в каком-то смысле слишком жесткая.

Что касается ping/pong, то он должен работать. Взгляните на wsprotocol.mqh:

   // Обработка входящих управляющих кадров: отправка Pong на Ping и закрытие соединения после запроса Close
   void processControlFrame(IWebSocketFrame *frame)
   {
      switch(frame.getType())
      {
      case WS_FRAME_OPCODE::WS_CLOSE_FRAME:
         if(closeRequested) // наше закрытие было подтверждено
         {
            Print("Server close ack");
         }
         else if(!disconnecting) // закрытие по инициативе сервера
         {
            if(openMessage) // все еще не доработано(!)
            {
               owner.onMessage(openMessage);
               openMessage = NULL;
            }
          
            WebSocketFrame temp(WS_FRAME_OPCODE::WS_CLOSE_FRAME); // отправьте наш ack
            sendFrame(&temp);
         }
         close();
         break;
      case WS_FRAME_OPCODE::WS_PING_FRAME:
         {
            IWebSocketFrame *temp = WebSocketFrame::create(WS_FRAME_OPCODE::WS_PONG_FRAME, frame.getData());
            sendFrame(temp);
            delete temp;
         }
         break;
      }
   }

Так что, пожалуйста, отладьте это на вашей стороне, поскольку у вас есть сервер, отправляющий пинги вашему клиенту.

 
BTW, я забыл добавить новую версию MQL5Book/StringUtils.mqh, используемую в ws/sources.
Файлы:
 
Stanislav Korotky #:
BTW, я забыл добавить новую версию MQL5Book/StringUtils.mqh, используемую в ws/sources.
@Stanislav Korotky еще раз спасибо за помощь. Что касается проблемы пинг-понга с сервером python websocket, то при дальнейшей отладке я заметил, что сервер отправляет запрос на пинг в виде текстовых или бинарных данных, а MT5 успешно отвечает только при отправке текстового пинга. Это помогло мне отследить и устранить проблему.

Проблема находится в строке 310 в переключателе/кейсе фрейма ws ping, когда создается фрейм websocket:

IWebSocketFrame *temp = WebSocketFrame::create(WS_FRAME_OPCODE::WS_PONG_FRAME, frame.getData());

frame.getData() сервер, похоже, подтверждает понг, только если данные пинга основаны на тексте/строке, но терпит неудачу, если был отправлен двоичный пинг

, поэтому мне пришлось использовать перегруженный frame.getData(uchar &buf[]), чтобы заставить его работать в обоих случаях. Не знаю, встретятся ли мне другие варианты использования, которые (мое изменение) сломает, но пока все выглядит нормально.


uchar data[]
; frame.getData(data)
; IWebSocketFrame *temp = WebSocketFrame::create(WS_FRAME_OPCODE::WS_PONG_FRAME, data);

Просто из любопытства, мне интересно, почему мы должны вручную проверять наличие сообщений с интервалом в обработчике событий OnTimer(), в то время как я вижу что-то, что похоже на функцию обработчика событий OnMessage входящих сообщений. Хотя текущая настройка работает как есть, но я буду признателен, если вы сможете подробнее объяснить, почему именно такой подход
 
pauldic #:
@Stanislav Korotky еще раз спасибо за помощь. Что касается проблемы пинг-понга с сервером python websocket, при дальнейшей отладке я заметил, что сервер посылает запрос на пинг в виде текстовых или бинарных данных, а MT5 успешно отвечает только в том случае, если посылается текстовый пинг. Это помогло мне отследить и устранить проблему.

Проблема находится в строке 310 в переключателе/кейсе фрейма ws ping, когда создается фрейм websocket:

IWebSocketFrame *temp = WebSocketFrame::create(WS_FRAME_OPCODE::WS_PONG_FRAME, frame.getData());

frame.getData() сервер, похоже, подтверждает понг, только если данные пинга основаны на тексте/строке, но терпит неудачу, если был отправлен двоичный пинг

, поэтому мне пришлось использовать перегруженный frame.getData(uchar &buf[]), чтобы заставить его работать в обоих случаях. Не знаю, встретятся ли мне другие варианты использования, которые (мое изменение) сломает, но пока все выглядит нормально.


uchar data[]
; frame.getData(data)
; IWebSocketFrame *temp = WebSocketFrame::create(WS_FRAME_OPCODE::WS_PONG_FRAME, data);

Просто из любопытства, мне интересно, почему мы должны вручную проверять наличие сообщений с интервалом в обработчике событий OnTimer(), в то время как я вижу что-то, что похоже на функцию обработчика событий OnMessage входящих сообщений. Хотя текущая настройка работает как есть, но я буду признателен, если вы сможете подробнее объяснить, почему именно такой подход
.

Спасибо за ваши усилия. Я рассмотрю возможность включения ваших правок в исходные коды.

Что касается OnTimer и OnMessage, они предоставлены потому, что вы можете захотеть реализовать различную логику обработки в вашем приложении. Чтение из сокетов блокируется на уровне MQL5 API, с заданным таймаутом. Таким образом, если вас интересует только ожидание новых сообщений, их обработка и автоматическая генерация ответов, вы можете делать это в блокирующем режиме (с бесконечным таймаутом при чтении и без OnTimer) - то есть приложение как бы застыло в ожидании данных. Но если вам нужно обеспечить отзывчивый пользовательский интерфейс (как в случае с чатом), то в OnTimer-обработчике вы устанавливаете небольшой таймаут для коротких попыток чтения (они могут выдать пустые данные, если таймаут истек) и отслеживаете действия пользователя в промежутках между ними.

 
Stanislav Korotky #:

Спасибо за ваши усилия. Я рассмотрю возможность включения ваших правок в исходные коды.

Что касается OnTimer и OnMessage, то они приведены потому, что вы можете захотеть реализовать различную логику обработки в своем приложении. Чтение из сокетов блокируется на уровне MQL5 API, с заданным таймаутом. Таким образом, если вас интересует только ожидание новых сообщений, их обработка и автоматическая генерация ответов, вы можете делать это в блокирующем режиме (с бесконечным таймаутом при чтении и без OnTimer) - то есть приложение как бы застыло в ожидании данных. Но если вам нужно обеспечить отзывчивый пользовательский интерфейс (как в случае с чатом), то в OnTimer-обработчике вы устанавливаете небольшой таймаут для коротких попыток чтения (они могут выдать пустые данные, если таймаут истек) и отслеживаете действия пользователя в промежутках между ними.

Еще раз спасибо @StanislavKorotky за разъяснения
 

Очередная порция мелких исправлений и улучшений исходных кодов wss в zip-архиве.

Помимо прочего, теперь вы можете проанализировать неудачное обновление (например, если требуется авторизация и возвращается код статуса, отличный от 101) и принять меры:

if(wss.open(initial_headers))
{
   // успех, нормальный поток
}
else
{
  // что-то пошло не так - не обновлено или не подключено
  if(wss.isConnected())
  {
     Print("Can't upgrade to websockets");
     bool resolved = false;
     string headers[][2]; // response
     if(wss.getHeaders(headers))
     {
       // TODO: проанализировать состояние и решить проблему
       resolved = ... // true/false
     }
     if(!resolved)
     {
       wss.close(); // закрываем, чтобы предотвратить дальнейшие попытки чтения/записи с не обновленного сокета
     }
     else
     {
       wss.open(updated_headers); // try again
     }
  }
}

Также важные исправления получили StringUtils.mqh и URL.mqh.

Файлы:
wss.zip  16 kb
URL.mqh  5 kb
 

Привет @Stanislav Korotky, я новичок в MQL5. Нашел, что вы выкладываете файл wss.zip для использования websocket. Как его использовать, есть ли демо или что-то, что я могу изучить. Искренне благодарю!

Stanislav Korotky
Stanislav Korotky
  • 2025.04.07
  • www.mql5.com
Trader's profile