banner
Qt/C++. SSL and WebSocket Protocols in Object Recognition Program.

Программы распознавания объектов (Objects Detection) могут быть объединены в сеть посредством протоколов SSL и WebSocket. Что для этого требуется? Немногое. Включить в состав Objects Detection клиента, работающего с этими протоколами, и написать сервер, поддерживающий эти же протоколы. Статья содержит краткое описание трех проектов: Objects Detection версии 1.2, SslWebSocketServer, SslWebSocketClient в качестве примера объединения программ в сеть с использованием протоколов SSL и WebSocket.
Как выглядит алгоритм работы программ? Objects Detection обнаруживает объекты, сохраняет данные у себя на диске и передает их через сервер SslWebSocketServer другим клиентам, в том числе, и другим экземплярам Objects Detection. Обмен данными происходит без участия человека. Клиенты объединяются на основе подписок. Темы подписок ничем не ограничены. Например, возможны такие темы: люди, автомобили, домашние животные и так далее. Понятно, что программы будут обмениваться данными именно об этих объектах.
Какие данные передаются между программами? В предлагаемой реализации кроме названия подписки - это: имя класса распознанного объекта (человек, авто, собака, ...), время и дата обнаружения (в качестве имени файла), изображение найденного объекта в формате jpg
Как может быть использовано имя объекта? Дело в том, что клиентский компъютер часто выполняет функции контроллера по управлению устройствами (освещения, температуры и так далее), то есть, имеет дело с датчиками и приводами. Для принятия решения о запуске того или иного физического процесса требуется имя распознанного объекта.
В состав загрузочного пакета включен проект SslWebSocketClient, который содержит несколько экземпляров SSL/WebSocket клиента для отладки взаимодействия в сети.
Проект SslWebSocketServer выполняет функции сервера и обеспечивает прием/передачу/сохранение данных, получаемых от киентов, которыми являются программы Objects Detection и SslWebSocketClient. Фрагмет исходного текста сервера приведен на рисунке.
Это четырнадцатая статья из цикла "Real-Time Object Recognition". Первые тринадцать опубликованы здесь: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 и 13
При разработке программ использовались:
  • операционная система macOS Big Sur версия 11.6.2;
  • среда разработки Qt Creator версия 8.0.0;
  • компилятор Apple Clang версия 13.0.0 (clang-1300.0.29.30);
  • библиотека OpenCV версия 4.5.5
  • нейронная сеть из группы YOLO, обученная на наборе данных COCO.
  • СУБД (система управления базами данных) PostgreSQL 14.7
  • администратор баз данных pqAdmin 4 version 6.4
1. Программа "Objects Detection" выполняет следующие функции:
  • принимает поток данных с видеокамеры в режиме реального времени;
  • детектирует объекты с помощью нейронной сети (Deep Neural Networks) yolov4-tiny;
  • принимает настройки пользователя для установки связи между объектами и действиями;
  • озвучивает (произносит) имена обнаруженных объектов и вероятности их распознавания;
  • принимает и устанавливает настройки пользователя для модуля голосовых данных (высоту тона, громкость, скорость речи);
  • сохраняет изображения обнаруженных объектов (images) в виде jpg файллов на SSD диске и ссылки на них в базе данных PostgreSQL;
  • отсылает файлы images по заданному в программе e-mail адресу;
  • отсылает файлы images по протоколу FTP на удаленный сервер;
  • записывает звук с микрофона на SSD диск в виде wav файлов;
  • отображает в Status Bar приложения уровень звукового сигнала, поступающего на вход микрофона;
  • выводит на экран сохраненные на диске images;
  • формирует текущeе графическое положение устройства и сохраняет его в базе данных;
  • подключается к брокеру broker.hivemq.com;
  • подписывается на канал приема команд от программы Client Mqtt;
  • передает программе Client Mqtt через брокера фотографии обнаруженных объектов;
  • выполняет команды, полученные от приложения Client Mqtt;
  • тестирует исправность канала связи Mqtt;
  • открывает карту и показывает место нахождения (обнаружения) объекта(объектов);
  • выполняет SQL выборки по комбинациям параметров: тип объекта, период времени, вероятность распознавания;
  • предоставляет возможность редактирования снимков распознанных объектов;
  • загружает с веб сервера HTML страницу с фотографиями распознанных объектов и сохраняет ее на локальном диске компьютера;
  • сканирует и находит устройства Bluetooth;
  • подключается к выбранному пользователем устройству Bluetooth, образуя пару;
  • пересылает на удаленное устройство Bluetooth, например, телефон файлы изображений, распознанных программой объектов.
  • формирует матрицы настроек программы;
  • выполняет операции: Save As, Save, Open, Open Recent для файлов, содержащих матрицы настроек;
  • выполняет операции connect/disconnect для клиентов, работающих по протоколу TCP/IP;
  • выполняет операции read/send для объектов QByteArray и QString, содержащих изображения и имена файлов изображений по протоколу TCP/IP;
  • реализует протоколы SSL и WebSocket.
2. Как реализованы протоколы SSL и WebSocket в программе "Objects Detection".
Реализованы протоколы SSL/WebSocket в классе "SslWebSocketClient". После подключения к серверу командой webSocket.open(QUrl(url)) клиент передает на сервер имя подписки, с которой он хочет работать, например: webSocket.sendTextMessage("Subscription:NewFolder"). При передаче данных на сервер клиент последовательно передает: имя обнаруженного объекта, имя файла с указанием даты и времени обнаружения и jpg файл изображения объекта. Программа обеспечивает и проверку канала связи с сервером посредством выдачи и приема сигналов ping pong.
3. Клиент в проекте "SslWebSocketClient".
Клиент имеет GUI полностью совпадающим с интерфесом TCP/IP, описанным в статье. Однако протоколами сязи с сервером являются SSL и WebSocket. Проект входит в состав загрузочного файла.
4. Сервер в проекте "SslWebSocketServer".
Для обеспечения связи между клиентами написан отдельнный проект сервера SslWebSocketServer. Сервер реализует протоколы SSL и WebSocket. SSL протокол написан на основе примера. Ключ и сертификат для SslWebSocketServer сгенерированы на сайте. Сервер обеспечивает подписку и прием/передачу/сохранение данных, поступающих от клиентов. Для сохранение принятых данных сервер создает отдельные папки с именами подпискок. Запись данных на диск выполняется в отдельном потоке. Проект входит в состав загрузочного файла.

Code SslWebSocketServer.cpp

#include "sslWebSocketServer.h"

SslWebSocketServer::SslWebSocketServer(quint16 portQObject *parent) :
    QObject(parent),
    webSocketServer(nullptr)
{

    webSocketServer = new QWebSocketServer(QStringLiteral("SSL WebSocket Server"),
                                              QWebSocketServer::SecureMode,
                                            this);

    QSslConfiguration sslConfiguration;
    QFile certFile(QStringLiteral(":/localhost.crt"));
    QFile keyFile(QStringLiteral(":/localhost.key"));    
    certFile.open(QIODevice::ReadOnly);
    keyFile.open(QIODevice::ReadOnly);
    QSslCertificate certificate(&certFileQSsl::Pem);
    QSslKey sslKey(&keyFileQSsl::RsaQSsl::Pem);
    certFile.close();
    keyFile.close();
    sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
    sslConfiguration.setLocalCertificate(certificate);
    sslConfiguration.setPrivateKey(sslKey);

    webSocketServer->setSslConfiguration(sslConfiguration);

    if (webSocketServer->listen(QHostAddress::Anyport))
    {
        qDebug() << "SSL WebSocket Server listening on port" << port;
        connect(webSocketServer, &QWebSocketServer::newConnection,      this, &SslWebSocketServer::onNewConnection);
        connect(webSocketServer, &QWebSocketServer::closed,             this, &SslWebSocketServer::closed);
        connect(webSocketServer, &QWebSocketServer::sslErrors,          this, &SslWebSocketServer::onSslErrors);
    }

    // Запись на диск в отдельном потоке
    SaveImage *saveImage = new SaveImage();
    saveImage->moveToThread(&imageSaveThread);
    connect(&imageSaveThread,   &QThread::finished,             saveImage,  &QObject::deleteLater);
    connect(this,               &SslWebSocketServer::operate,   saveImage,  &SaveImage::doWork);
    connect(saveImage,          &SaveImage::resultReady,        this,       &SslWebSocketServer::handleResults);

}

SslWebSocketServer::~SslWebSocketServer(){

    imageSaveThread.quit();
    imageSaveThread.wait();
    webSocketServer->close();

    qDeleteAll(clients.keyBegin(), clients.keyEnd());
    clients.clear();
}

// Create and get localPath
QString SslWebSocketServer::getDataPath(QString &localPath)
{
    QString user_movie_path = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).at(0);
    QDir movie_dir(user_movie_path);
    movie_dir.mkpath("SslWebSocketServer/" + localPath);
    return movie_dir.absoluteFilePath("SslWebSocketServer/" + localPath);
}

// Новое соединение. Adds socket in QMap<QWebSocket*, QString>
void SslWebSocketServer::onNewConnection()
{
    QWebSocket *pSocket = webSocketServer->nextPendingConnection();

    qDebug() << "Client connected:" << pSocket;

    connect(pSocket, &QWebSocket::textMessageReceived,      this, &SslWebSocketServer::getTextMessage);
    connect(pSocket, &QWebSocket::binaryMessageReceived,    this, &SslWebSocketServer::getBinaryMessage);
    connect(pSocket, &QWebSocket::disconnected,             this, &SslWebSocketServer::socketDisconnected);
    imageSaveThread.start();

    clients.insert(pSocket"empty");
    qDebug() <<  clients.size();

}

// Slot.
// Прием имени подписки и ее сохранение.
// Передача имени объекта и имени файла клиентам.
void SslWebSocketServer::getTextMessage(QString message)
{
    QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
    if (pClient)
    {
        // index - позиция разделителя и длина принятой команды
        int index = message.indexOf(":");
        int length = message.size() - (message.indexOf(":") + 1);

        // Для текущего клиента
        for(QMap<QWebSocket*, QString >::iterator it = clients.begin(); it != clients.end(); ++it) {
             if(it.key() == pClient) {
                // Если команда подписка - сохраним ее имя в карте
                if(message.left(index) == "Subscription") {
                     subscription = message.right(length);
                     //qDebug() << subscription << pClient;
                     it.value()= subscription;
                     pClient->sendTextMessage("O.K.");
                     return;
                 }

                //ObjectName
                if(message.left(index) == "ObjectName"){
                    objectName = message.right(length);
                    return;
                }

                //File name - не уходим, идем к передаче для других клиентов
                if(message.left(index) == "FileName") {
                   // Имя файла для своего клиента
                   fileName = message.right(length);
                }
            }
        }

        // Для других клиентов. Отправка текстовых имен клиентам с той же подпиской
        for(QMap<QWebSocket*, QString >::iterator it = clients.begin(); it != clients.end(); ++it) {
            if((it.key() != pClient) && (subscription == it.value())) {
                it.key()->sendTextMessage(message);
            }
        }
    }
}

// Slot. Получить image и записать на диск
// Отправка изображения клиентам с той же подпиской
void SslWebSocketServer::getBinaryMessage(QByteArray message)
{
    QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
    if (pClient)
    {
        // Сохраним изображение на диске
        if(!message.isEmpty()) image = QImage::fromData(message,"jpg"); else { qDebug()<< "ImageArray empty..."return;}
        for(QMap<QWebSocket*, QString>::iterator it = clients.begin(); it != clients.end(); ++it) {
            // find a client
            if(it.key() == pClient) {
                subscription = it.value();
                if(it.value()!= "empty") {
                    emit operate(imagegetDataPath(subscription)+"/" + fileName + ".jpg");
                } else
                    qDebug()<< Q_FUNC_INFO << "Error subscription for: "  << pClient;
            }
        }

         // Отправка изображения клиентам с той же подпиской
          for(QMap<QWebSocket*, QString>::iterator it = clients.begin(); it != clients.end(); ++it) {
              if((it.key() != pClient) && (subscription == it.value())) {
                  it.key()->sendBinaryMessage(message);
                  //qDebug() << "send bynary" << it.key();
                }
          }
    }
}

// Отключить клиент и удалить его из списка
void SslWebSocketServer::socketDisconnected()
{
    QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
    if (pClient)
    {
        for(QMap<QWebSocket*, QString>::iterator it = clients.begin(); it != clients.end(); ++it) {
            if(it.key() == pClient) {
                qDebug() << "Client disconnected " << pClient;
                clients.erase(it);
                pClient->disconnect();
                pClient->deleteLater();
            }
       }
       // qDebug() <<  clients.size();
    }
}

// Finish writing to disk
void SslWebSocketServer::handleResults(const QString &result){
    qDebug()<< result ;
}

void SslWebSocketServer::onSslErrors(const QList<QSslError> &)
{
    qDebug() << "Ssl errors occurred";
}


Полное описание исходного текста проектов "iOS MQTT Client", "macOS MQTT Client", "Client TCP", "Objects Detection version 1.2", "SslWebSocketServer", "SslWebSocketClient" предоставляются платно в файлах pdf или odt при запросе через e-mail: veresov@jdesign.ru.
Проекты можно загрузить и поблагодарить: Спасибо!  2202   2005   9442   6213   (сбербанк)
Евгений Вересов.
08.03.2023 года.