- 1. QLocalServer
- 2. QLocalSocket
В статье описывается использование QLocalServer и QLocalSocket. Пример является переработкой кода из книги Шлее «Qt 5.3. Профессиональное программирование на C++», посвящённого QTcpServer и QTcpSocket соответственно. Не смотря на то, что наименования классов похожи, и используются в одном и том же модуле, пара существенных отличий есть. Их мы рассмотрим по ходу изложения.
QLocalServer и QLocalSocket реализуют механизмы именованных каналов или сокетов домена Unix. Подробнее об этом можно почитать здесь и здесь .
Пример состоит из двух частей. В первой реализуется QLocalServer, во второй – QlocalSocket.
Сервер реализует следующий функционал: на монитор выводится виджет с текстовым полем, в котором будет отображаться информация, поступающая извне, от сокета. Сервер ожидает входящих соединений и, в случае успешного подключения, отправляет сокету сообщение об этом. Кроме того, сервер ретранслирует сокету передаваемую от сокета информацию.
В свою очередь, сокет выводит на монитор виджет с текстовым полем и кнопкой. В текстовом поле отображается информация, поступающая от сервера и служебная информация самого сокета (обнаружение подключения в начале сеанса или ошибка подключения к серверу в случае его недоступности). По нажатию кнопки сокет отправляет информацию серверу.
Рассмотрим подробно код примера.
QLocalServer
# QLocalServer.pro # Кроме модулей core gui подключаем network QT += core gui network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = QLocalServer TEMPLATE = app DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ main.cpp \ mylocalserver.cpp HEADERS += \ mylocalserver.h
// main.cpp #include "mylocalserver.h" #include <QApplication> int main(int argc, char* argv[]) { QApplication app(argc, argv); // Создаём и показываем объект класса MyLocalServer, определённого далее, // для запуска сервера с именем "MyLocalServer" MyLocalServer server("MyLocalServer"); server.show(); return app.exec(); }
// mylocalserver.h #ifndef MYLOCALSERVER_H #define MYLOCALSERVER_H #include <QWidget> #include <QLocalServer> #include <QLocalSocket> #include <QTextEdit> class MyLocalServer : public QWidget { Q_OBJECT public: MyLocalServer(QString serverName, QWidget* parent = 0); ~MyLocalServer(); private: // Указатель на QLocalServer QLocalServer* localServer; // Указатель на QTextEdit, в котором будет показываться поступающая // от клиента информация QTextEdit* textEdit; // Переменная для хранения размера получаемого от клиента блока quint16 nextBlockSize; // Метод для отправки клиенту подтверждения о приёме информации void sendToClient(QLocalSocket* localSocket, const QString& string); public slots: // Слот обработки нового клиентского подключения virtual void slotNewConnection(); // Слот чтения информации от клиента void slotReadClient(); }; #endif // MYLOCALSERVER_H
// mylocalserver.cpp #include "mylocalserver.h" #include <QVBoxLayout> #include <QMessageBox> #include <QLabel> #include <QTime> MyLocalServer::MyLocalServer(QString serverName, QWidget* parent) : QWidget(parent), nextBlockSize(0) // Устанавливаем nextBlockSize равным нулю { // Создаём и запускаем сервер командой listen. // Если сервер не может быть запущен, выдать сообщение об ошибке и завершить работу программы localServer = new QLocalServer(this); if(!localServer->listen(serverName)) { QMessageBox::critical(0, "Server error", "Unable to start server:" + localServer->errorString()); localServer->close(); return; } // Соединяем сигнал сервера о наличии нового подключения с обработчиком нового клиентского подключения connect(localServer, SIGNAL(newConnection()), this, SLOT(slotNewConnection())); // Формируем окно для просмотра текстовых сообщений от клиента textEdit = new QTextEdit; textEdit->setReadOnly(true); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(new QLabel(serverName)); layout->addWidget(textEdit); setLayout(layout); } MyLocalServer::~MyLocalServer() { } // Слот обработки нового клиентского подключения void MyLocalServer::slotNewConnection() { // Получаем сокет, подключённый к серверу QLocalSocket* localSocket = localServer->nextPendingConnection(); // Соединяем сигнал отключения сокета с обработчиком удаления сокета connect(localSocket, SIGNAL(disconnected()), localSocket, SLOT(deleteLater())); // Соединяем сигнал сокета о готовности передачи данных с обработчиком данных connect(localSocket, SIGNAL(readyRead()), this, SLOT(slotReadClient())); // Отправляем информацию клиенту о соединении с сервером sendToClient(localSocket, "Server response: Connected!"); } // Слот чтения информации от клиента void MyLocalServer::slotReadClient() { // Получаем QLocalSocket после срабатывания сигнала о готовности передачи данных QLocalSocket* localSocket = (QLocalSocket*)sender(); // Создаём входной поток получения данных на основе сокета QDataStream in(localSocket); // Устанавливаем версию сериализации данных потока. У клиента и сервера они должны быть одинаковыми in.setVersion(QDataStream::Qt_5_3); // Бесконечный цикл нужен для приёма блоков данных разных размеров, от двух байт и выше for(;;) { // Если размер блока равен нулю if(!nextBlockSize) { // Если размер передаваемого блока меньше двух байт, выйти из цикла if(localSocket->bytesAvailable() < (int)sizeof(quint16)) break; // Извлекаем из потока размер блока данных in >> nextBlockSize; } // Извлекаем из потока время и строку QTime time; QString string; in >> time >> string; // Преобразуем полученные данные и показываем их в виджете QString message = time.toString() + " " + "Client has sent - " + string; textEdit->append(message); nextBlockSize = 0; // Отправляем ответ клиенту sendToClient(localSocket, "Server response: received \"" + string + "\""); } } // Метод для отправки клиенту подтверждения о приёме информации void MyLocalServer::sendToClient(QLocalSocket* localSocket, const QString& string) { // Поскольку заранее размер блока неизвестен (параметр string может быть любой длины), // вначале создаём объект array класса QByteArray QByteArray array; // На его основе создаём выходной поток QDataStream out(&array, QIODevice::WriteOnly); // Устанавливаем версию сериализации данных потока out.setVersion(QDataStream::Qt_5_3); // Записываем в поток данные для отправки. На первом месте идёт нулевой размер блока out << quint16(0) << QTime::currentTime() << string; // Перемещаем указатель на начало блока out.device()->seek(0); // Записываем двухбайтное значение действительного размера блока без учёта пересылаемого размера блока out << quint16(array.size() - sizeof(quint16)); // Отправляем получившийся блок клиенту localSocket->write(array); }
QLocalSocket
# QLocalSocket.pro # Кроме модулей core gui подключаем network QT += core gui network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = QLocalSocket TEMPLATE = app DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ main.cpp \ mylocalsocket.cpp HEADERS += \ mylocalsocket.h
// main.cpp #include "mylocalsocket.h" #include <QApplication> int main(int argc, char* argv[]) { QApplication app(argc, argv); // Создаём и показываем объект класса MyLocalSocket, определённого далее, // для запуска клиента, подключаемого к серверу с именем "MyLocalServer" MyLocalSocket socket("MyLocalServer"); socket.show(); return app.exec(); }
// mylocalsocket.h #ifndef MYLOCALSOCKET_H #define MYLOCALSOCKET_H #include <QWidget> #include <QLocalSocket> #include <QTextEdit> #include <QPushButton> class MyLocalSocket : public QWidget { Q_OBJECT public: MyLocalSocket(QString serverName, QWidget* parent = 0); ~MyLocalSocket(); private: // Указатель на QLocalSocket QLocalSocket* localSocket; // Указатели на элементы интерфейса QTextEdit* textEdit; QPushButton* sendRevision; // Размер принимаемого от сервера блока quint16 nextBlockSize; // Номер ревизии, отправляемый серверу // Увеличивается при каждом нажатии QPushButton int revision; private slots: // Слот чтения информации, получаемой от сервера void slotReadyRead(); // Слот обработки ошибок сокета void slotError(QLocalSocket::LocalSocketError error); // Слот передачи информации на сервер void slotSendToServer(); // Слот обработки сигнала соединения с сервером void slotConnected(); }; #endif // MYLOCALSOCKET_H
// mylocalsocket.cpp #include "mylocalsocket.h" #include <QVBoxLayout> #include <QLabel> #include <QTime> #include <QMessageBox> MyLocalSocket::MyLocalSocket(QString serverName, QWidget* parent) : QWidget(parent), nextBlockSize(0), revision(0) // Устанавливаем nextBlockSize и revision равными нулю { // Инициализируем сокет localSocket = new QLocalSocket(this); // Устанавливаем соединение между сигналом ошибки сокета с обработчиком ошибок connect(localSocket, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error), this, &MyLocalSocket::slotError); // Устанавливаем имя сервера, к которому сокет должен подключаться localSocket->setServerName(serverName); // Устанавливаем соединение между сигналом подключения сокета к серверу // и обработчиком сигнала connect(localSocket, SIGNAL(connected()), SLOT(slotConnected())); // Соединяем сигнал сокета о готовности приёма данных данных с обработчиком данных connect(localSocket, SIGNAL(readyRead()), SLOT(slotReadyRead())); // Инициализируем элементы интерфейса textEdit = new QTextEdit; sendRevision = new QPushButton("Send next revision"); // Соединяем нажатие кнопки с обработчиком, передающим информацию о ревизии на сервер connect(sendRevision, SIGNAL(clicked()), this, SLOT(slotSendToServer())); // Настраиваем элементы интерфейса и формируем вид окна клиента textEdit->setReadOnly(true); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(new QLabel("Sender revisions")); layout->addWidget(textEdit); layout->addWidget(sendRevision); setLayout(layout); // Подключаем сокет к серверу localSocket->connectToServer(); } MyLocalSocket::~MyLocalSocket() { } // Слот чтения информации, получаемой от сервера void MyLocalSocket::slotReadyRead() { // Всё аналогично приёму информации на стороне сервера QDataStream in(localSocket); in.setVersion(QDataStream::Qt_5_3); for(;;) { if(!nextBlockSize) { if(localSocket->bytesAvailable() < (int)sizeof(quint16)) break; } in >> nextBlockSize; if(localSocket->bytesAvailable() < nextBlockSize) break; QTime time; QString string; in >> time >> string; textEdit->append(time.toString() + " " + string); nextBlockSize = 0; } } // Слот обработки ошибок сокета void MyLocalSocket::slotError(QLocalSocket::LocalSocketError error) { QString strError = "Error: " + (error == QLocalSocket::ServerNotFoundError ? "The server was not found." : error == QLocalSocket::PeerClosedError ? "The server is closed." : error == QLocalSocket::ConnectionRefusedError ? "The connection was refused." : QString(localSocket->errorString())); textEdit->append(strError); } // Слот передачи информации на сервер void MyLocalSocket::slotSendToServer() { // Блок для передачи формируется аналогично тому, как это делается на сервере QByteArray arrayBlock; QDataStream out(&arrayBlock, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_5_3); ++revision; QString message = "Revision: " + QString("%1").arg(revision); out << quint16(0) << QTime::currentTime() << message; out.device()->seek(0); out << quint16(arrayBlock.size() - sizeof(quint16)); localSocket->write(arrayBlock); } // Слот обработки сигнала соединения с сервером void MyLocalSocket::slotConnected() { textEdit->append("Received the connected() signal"); }
Первая особенность заключается в том, как именно подключается для QLocalSocket сигнал ошибки слота к сигналу обработки ошибок: через QOverload.
connect(localSocket, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error), this, &MyLocalSocket::slotError);
Вторая особенность связана с тем, что для QLocalSocket это соединение должно быть объявлено до подключения к серверу, тогда как для QTcpSocket соединение сигнала ошибки и слота обработки ошибок может быть объявлено после подключения к серверу. Подробнее об этом см. тему форума: https://evileg.com/ru/forum/topic/965/ .
В итоге на мониторе должна быть такая картина.
Код примеров на GitHub: QLocalServer , QLocalSocket .
Остальные сигналы и слоты соединены между собой в старом стиле, так как фрагмент кода сокета, показанный ниже, компилировался с ошибками.