Работа с сетью в Qt

WebSocket в Qt: QWebSocket и QWebSocketServer на практике

Введение

WebSocket — протокол для полнодуплексной связи в реальном времени между клиентом и сервером, широко используемый в чатах, биржевых тикерах и интерактивных дашбордах. В Qt поддержка WebSocket реализована в модуле Qt WebSockets через классы QWebSocket (клиент) и QWebSocketServer (сервер). В этой статье разберём минимальный рабочий пример клиента и сервера, обменивающихся текстовыми сообщениями.

Концепция

В отличие от обычного TCP-соединения, WebSocket начинается с HTTP-хэндшейка (upgrade-запроса), после которого канал переходит в режим фреймов WebSocket — Qt полностью скрывает эту деталь, предоставляя API на уровне сообщений: sendTextMessage()/sendBinaryMessage() и сигналы textMessageReceived()/binaryMessageReceived(). Это снимает необходимость вручную парсить границы сообщений, как в случае с «сырым» TCP — каждое сообщение приходит цельным.

QWebSocketServer работает аналогично QTcpServer: сигнал newConnection() извещает о новом клиенте, а nextPendingConnection() возвращает готовый объект QWebSocket для общения с ним.

Пример кода: WebSocket в Qt

// --- Сервер ---
#include <QCoreApplication>
#include <QWebSocketServer>
#include <QWebSocket>
#include <QDebug>

class WsServer : public QObject
{
    Q_OBJECT
public:
    explicit WsServer(quint16 port, QObject *parent = nullptr)
        : QObject(parent), m_server(QStringLiteral("Demo Server"), QWebSocketServer::NonSecureMode)
    {
        connect(&m_server, &QWebSocketServer::newConnection, this, &WsServer::onNewConnection);
        if (m_server.listen(QHostAddress::Any, port)) {
            qDebug() << "WebSocket-сервер запущен на порту" << port;
        }
    }

private slots:
    void onNewConnection()
    {
        QWebSocket *socket = m_server.nextPendingConnection();
        connect(socket, &QWebSocket::textMessageReceived, this, [socket](const QString &message) {
            qDebug() << "Получено:" << message;
            socket->sendTextMessage("Эхо: " + message);
        });
        connect(socket, &QWebSocket::disconnected, socket, &QWebSocket::deleteLater);
    }

private:
    QWebSocketServer m_server;
};

// --- Клиент ---
#include <QWebSocket>

class WsClient : public QObject
{
    Q_OBJECT
public:
    explicit WsClient(const QUrl &url, QObject *parent = nullptr) : QObject(parent)
    {
        connect(&m_socket, &QWebSocket::connected, this, []() {
            qDebug() << "Подключено к WebSocket-серверу";
        });
        connect(&m_socket, &QWebSocket::textMessageReceived, this, [](const QString &message) {
            qDebug() << "Ответ сервера:" << message;
        });
        m_socket.open(url);
    }

    void sendMessage(const QString &text) { m_socket.sendTextMessage(text); }

private:
    QWebSocket m_socket;
};

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    WsServer server(9999);
    WsClient client(QUrl("ws://127.0.0.1:9999"));
    QTimer::singleShot(500, &client, [&client]() { client.sendMessage("Привет, сервер!"); });
    return app.exec();
}

#include "main.moc"

Пояснения к коду

Сервер создаёт QWebSocketServer в режиме NonSecureMode (то есть ws://, без TLS) и для каждого подключившегося клиента подписывается на textMessageReceived, отвечая эхом. Клиент WsClient подключается через open(), передавая URL с протоколом ws://, и отправляет сообщение через sendTextMessage() после установления соединения.

Обратите внимание на connect(socket, &QWebSocket::disconnected, socket, &QWebSocket::deleteLater) — это идиоматичный способ автоматически удалить сокет клиента сразу после его отключения без ручного отслеживания списка клиентов (если он не нужен для рассылки).

Подводные камни

  • ws:// против wss://. Для продакшена через интернет обязательно использовать защищённый wss:// (QWebSocketServer::SecureMode на сервере), иначе сообщения передаются в открытом виде.
  • Размер сообщений по умолчанию ограничен. При передаче больших данных через sendBinaryMessage() стоит проверять лимиты maxAllowedIncomingMessageSize на сервере, иначе соединение может быть разорвано.
  • Подключение connected() сигнала после open() не сработает, если подключение происходит синхронно (редко, но возможно с уже готовым event loop) — безопаснее подключать сигналы до вызова open(), как сделано в примере.
  • Несоответствие версий протокола WebSocket между клиентом и прокси/балансировщиком может приводить к обрыву хэндшейка — это нужно учитывать при разворачивании за reverse-proxy типа nginx, который требует специальной настройки заголовков Upgrade и Connection.