Введение
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.