WebSocket и GZIP

 
Подскажите, пожалуйста, как стандартными функциями MQL5 для работы с WebSocket прочитать и распаковать сообщения, упакованные в GZIP (часто встречающаяся упаковка данных, передаваемых по WebSocket)?
 
Ivan Titov:
Подскажите, пожалуйста, как стандартными функциями MQL5 для работы с WebSocket прочитать и распаковать сообщения, упакованные в GZIP (часто встречающаяся упаковка данных, передаваемых по WebSocket)?

Советую такие вопросы задавать ИИ GigaChat от Сбера. Код он вам не напишет, но как справочная система в области программирования, мне очень помогает. И бесплатный. 

---- Вот диалог ----

Можешь подсказать библиотеки для распаковки архивов QZIP? Язык С++ или MQL5

5 февраля в 11:31
                    

Для распаковки архивов QZIP на языке C++ вы можете использовать библиотеку libqzip . Эта библиотека предоставляет функции для чтения, записи и распаковки архивов QZIP. Однако, она не поддерживает MQL5.

                

Если вы хотите использовать библиотеку для распаковки архивов QZIP на MQL5, то вам придется написать свою собственную библиотеку или использовать существующую библиотеку, которая поддерживает MQL5 и имеет функции для работы с архивами QZIP.

Ошибочка, нужно распаковать GZIP!
5 февраля в 11:33

                    

Для распаковки архивов GZIP на языке C++ вы можете использовать библиотеку zlib . Эта библиотека предоставляет функции для чтения, записи и распаковки архивов GZIP. Она также поддерживает MQL5.

 
Ivan Titov:
Подскажите, пожалуйста, как стандартными функциями MQL5 для работы с WebSocket прочитать и распаковать сообщения, упакованные в GZIP (часто встречающаяся упаковка данных, передаваемых по WebSocket)?

А через MQL5 можно работать только с ZIP через  CryptDecode и  CryptEncode


 
Ivan Titov:
Подскажите, пожалуйста, как стандартными функциями MQL5 для работы с WebSocket прочитать и распаковать сообщения, упакованные в GZIP (часто встречающаяся упаковка данных, передаваемых по WebSocket)?

Для распаковки GZIP в MQL5, вам потребуется использовать стороннюю библиотеку или сервис, так как в стандартной библиотеке нет поддержки GZIP. Один из способов - написать обертку на C++ для zlib (библиотека для работы с GZIP) и подключить ее через DLL. Вот примерный код на C++ для функции распаковки:

#include <iostream>
#include <string>
#include <vector>
#include "zlib.h"

// Функция для распаковки GZIP
extern "C" __declspec(dllexport) int DecompressGZIP(const char *source, int sourceLen, char *dest, int destLen) {
    z_stream zs;
    memset(&zs, 0, sizeof(zs));

    if (inflateInit2(&zs, 16 + MAX_WBITS) != Z_OK)
        return -1;

    zs.next_in = (Bytef*)source;
    zs.avail_in = sourceLen;
    zs.next_out = (Bytef*)dest;
    zs.avail_out = destLen;

    int status = inflate(&zs, Z_FINISH);
    inflateEnd(&zs);

    if (status == Z_STREAM_END) {
        return zs.total_out;
    }

    return -1;
}

После создания DLL с функцией для распаковки GZIP, вы можете использовать ее в MQL5 следующим образом:

// Объявление функции DLL
#import "YourDLLName.dll"
int DecompressGZIP(const char &source[], int sourceLen, char &dest[], int destLen);
#import

void OnStart() {
  // Пример использования
  char compressedData[]; // Ваш сжатый GZIP данные
  int compressedDataLength = ArraySize(compressedData);
  char decompressedData[1000]; // Предполагаемый размер распакованных данных
  int decompressedLength = DecompressGZIP(compressedData, compressedDataLength, decompressedData, ArraySize(decompressedData));

  if (decompressedLength > 0) {
    Print("Декомпрессия успешна, размер распакованных данных: ", decompressedLength);
    // Теперь можно работать с распакованными данными
  } else {
    Print("Ошибка декомпрессии");
  }
}

Это примерный код как может быть реализован механизм распаковки  GZIP в MQL5

 
zhekstr #:

Это примерный код как может быть реализован механизм распаковки  GZIP в MQL5

Большое спасибо, попробую. Только проблема - получить упакованное сообщение целиком в потоке получаемых данных. Разработчикам MQL5 было бы проще вставить функционал распаковки стандартных упаковщиков в функции Socket<Tls>Read... Есть ли такие планы у разработчиков?

 
Работаем с ZIP-архивами средствами MQL5 без использования сторонних библиотек
Работаем с ZIP-архивами средствами MQL5 без использования сторонних библиотек
  • www.mql5.com
Язык MQL5 развивается, и в него постоянно добавляются новые функции для работы с данными. С некоторых пор, благодаря нововведениям, стало возможно работать с ZIP-архивами штатными средствами MQL5 без привлечения сторонних библиотек DLL. Данная статья подробно описывает, как это делается, на примере описания класса CZip — универсального инструмента для чтения, создания и модификации ZIP-архивов.
 
Ivan Titov #:

Большое спасибо, попробую. Только проблема - получить упакованное сообщение целиком в потоке получаемых данных. Разработчикам MQL5 было бы проще вставить функционал распаковки стандартных упаковщиков в функции Socket<Tls>Read... Есть ли такие планы у разработчиков?

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

1. Создайте буфер (например, динамический массив или список), в который будут записываться получаемые фрагменты данных до тех пор, пока сообщение не будет получено полностью. Это особенно важно, если сообщение приходит частями.

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

3.  Если протокол или формат данных подразумевает передачу данных частями (чанками), вам нужно будет реализовать логику сборки этих чанков в одно целое сообщение. Это может включать в себя считывание размера каждого чанка и последующую буферизацию данных до тех пор, пока не будет получен весь объем данных.

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

Примерная реализация на псевдокоде:

buffer = new DynamicBuffer()
messageComplete = false

while (not messageComplete and dataAvailable()) {
    chunk = readDataChunk()
    buffer.append(chunk)
    
    if (isMessageComplete(buffer)) {
        messageComplete = true
    }
}

if (messageComplete) {
    fullMessage = buffer.getData()
    // Произведите декомпрессию fullMessage здесь
}

Пример кода для сборки данных в MQL5:

// Предположим, у нас есть функция, которая читает данные
// Это может быть ваша функция для чтения данных из WebSocket или другого источника
int ReadData(uchar &data[], const int maxSize)
{
  // Здесь должна быть ваша реализация чтения данных
  // Возвращаемое значение - количество прочитанных байт
  return 0;
}

void OnStart()
{
  uchar buffer[];
  ArrayResize(buffer, 1024); // Предварительно выделяем память под буфер
  
  int bytesRead = 0;
  int totalBytesRead = 0;
  
  do
  {
    // Расширяем буфер, если необходимо
    if(ArraySize(buffer) <= totalBytesRead)
      ArrayResize(buffer, ArraySize(buffer) + 1024);
    
    // Читаем чанк данных
    bytesRead = ReadData(buffer + totalBytesRead, ArraySize(buffer) - totalBytesRead);
    totalBytesRead += bytesRead;
    
    // Проверяем условие выхода: например, если bytesRead равно 0 или если у вас есть другой механизм определения конца сообщения
  } while(bytesRead > 0 /* && !isMessageComplete(buffer, totalBytesRead) */);
  
  // Если необходимо, обрезаем буфер до фактически прочитанного размера
  if(totalBytesRead < ArraySize(buffer))
    ArrayResize(buffer, totalBytesRead);
  
  // Теперь у вас есть полное сообщение в 'buffer', которое можно декомпрессировать или обработать
}

В этом примере:

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

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "zlib.h"

// Экспортируемая функция декомпрессии GZIP
__declspec(dllexport) int GzipDecompress(const unsigned char *compressedBytes, int compressedSize, unsigned char *decompressedBytes, int decompressedBufferSize) {
    int ret;
    unsigned have;
    z_stream strm;
    unsigned char out[32768]; // Размер буфера для вывода может быть изменен

    // Инициализация структуры для декомпрессии
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = 0;
    strm.next_in = Z_NULL;
    
    // Инициализация для декомпрессии GZIP
    ret = inflateInit2(&strm, 16+MAX_WBITS);
    if (ret != Z_OK)
        return ret;

    // Настройка входного буфера
    strm.avail_in = compressedSize;
    strm.next_in = (unsigned char*)compressedBytes;

    // Декомпрессия в цикле
    int totalDecompressed = 0;
    do {
        strm.avail_out = sizeof(out);
        strm.next_out = out;
        ret = inflate(&strm, Z_NO_FLUSH);
        assert(ret != Z_STREAM_ERROR);  // Состояние потока должно быть корректным

        switch (ret) {
            case Z_NEED_DICT:
            case Z_DATA_ERROR:
            case Z_MEM_ERROR:
                (void)inflateEnd(&strm);
                return ret;
        }
        have = sizeof(out) - strm.avail_out;
        
        // Проверка на переполнение буфера
        if ((totalDecompressed + have) > decompressedBufferSize) {
            (void)inflateEnd(&strm);
            return Z_BUF_ERROR;
        }
        
        // Копирование декомпрессированных данных в выходной буфер
        memcpy(decompressedBytes + totalDecompressed, out, have);
        totalDecompressed += have;
    } while (strm.avail_out == 0);

    // Очистка
    (void)inflateEnd(&strm);
    return totalDecompressed;
}

После компиляции DLL вы можете использовать её в MQL5, подключив через импорт функций:

#import "YourDLLName.dll"
int GzipDecompress(const uchar &compressedBytes[], int compressedSize, uchar &decompressedBytes[], int decompressedBufferSize);
#import
 

Еще такой вариант по шагам:

Шаг 1: Создание функции распаковки в DLL

Сначала создадим код на C++, который будет компилироваться в DLL. Этот код будет содержать функцию для распаковки данных GZIP.

// Файл: GzipDecompressor.cpp

#include <iostream>
#include <string>
#include <vector>
#include "zlib.h"

extern "C" __declspec(dllexport) int GzipDecompress(const unsigned char *compressedBytes, int compressedSize, unsigned char *decompressedBytes, int decompressedBufferSize) {
    z_stream strm  = {0};
    strm.total_in  = strm.avail_in  = compressedSize;
    strm.total_out = strm.avail_out = decompressedBufferSize;
    strm.next_in   = (Bytef *)compressedBytes;
    strm.next_out  = (Bytef *)decompressedBytes;

    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;

    int err = -1;
    int ret = -1;

    err = inflateInit2(&strm, (15+32)); // 15 window bits, and the +32 tells zlib to to detect if using gzip or zlib
    if (err == Z_OK) {
        err = inflate(&strm, Z_FINISH);
        if (err == Z_STREAM_END) {
            ret = strm.total_out;
        } else {
            inflateEnd(&strm);
            return err;
        }
    } else {
        return err;
    }
    inflateEnd(&strm);
    return ret;
}

Здесь функция  GzipDecompress  принимает указатель на сжатые данные ( compressedBytes ), их размер ( compressedSize ), буфер для распакованных данных ( decompressedBytes ) и размер этого буфера ( decompressedBufferSize ). Она возвращает размер распакованных данных или код ошибки.

Шаг 2: Использование DLL в MQL5:

// Укажите корректное имя DLL
#import "GzipDecompressor.dll"
int GzipDecompress(const uchar &compressedBytes[], int compressedSize, uchar &decompressedBytes[], int decompressedBufferSize);
#import


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

void OnStart() {
  uchar compressedData[]; // Здесь должны быть ваши сжатые данные
  int compressedSize = ArraySize(compressedData);
  
  uchar decompressedData[10000]; // Пример размера буфера, возможно, вам понадобится больше
  int decompressedSize = GzipDecompress(compressedData, compressedSize, decompressedData, ArraySize(decompressedData));
  
  if (decompressedSize > 0) {
    Print("Декомпрессия успешна, размер распакованных данных: ", decompressedSize);
    // Теперь вы можете работать с decompressedData
  } else {
    Print("Ошибка декомпрессии, код ошибки: ", decompressedSize);
  }
}

Убедитесь, что размер буфера  decompressedData  достаточно велик, чтобы вместить все распакованные данные.

Это базовый пример использования.

 

Этого недостаточно. Посмотрите вопросы в заявке #3834829.

Расскажите, плиз, какие параметры поставить в ZLibOption на стороне веб-сервера (node.js), чтобы сообщения веб-сокетов со сжатием нормально разжимались с помощью CryptDecode(CRYPT_ARCH_ZIP...).

Что интересно - исходящие из MQL5-клиента сообщения, сжатые через CryptEncode(CRYPT_ARCH_ZIP...) нормально разжимаются сервером.

А вот в обратную сторону приходит массив байтов, но CryptDecode не может разжать - дает ошибку 4001.

 
zhekstr #:
необходимо точно знать, когда начинается и заканчивается сообщение

В этом-то и проблема.

 
Ivan Titov #:

В этом-то и проблема.

Насколько я помню, если опция "context takeover" отключена, то каждое отдельное сообщение WebSocket-а сжимается отдельно, а завершение сообщения протокол передает.