IscanderChe
19 июля 2019 г. 12:55

Пример использования QLocalServer и QLocalSocket

Содержание

В статье описывается использование QLocalServer и QLocalSocket. Пример является переработкой кода из книги Шлее «Qt 5.3. Профессиональное программирование на C++», посвящённого QTcpServer и QTcpSocket соответственно. Не смотря на то, что наименования классов похожи, и используются в одном и том же модуле, пара существенных отличий есть. Их мы рассмотрим по ходу изложения.

QLocalServer и QLocalSocket реализуют механизмы именованных каналов или сокетов домена Unix. Подробнее об этом можно почитать здесь и здесь .

Пример состоит из двух частей. В первой реализуется QLocalServer, во второй – QlocalSocket.

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

В свою очередь, сокет выводит на монитор виджет с текстовым полем и кнопкой. В текстовом поле отображается информация, поступающая от сервера и служебная информация самого сокета (обнаружение подключения в начале сеанса или ошибка подключения к серверу в случае его недоступности). По нажатию кнопки сокет отправляет информацию серверу.


Рассмотрим подробно код примера.

QLocalServer

  1. # QLocalServer.pro
  2.  
  3. # Кроме модулей core gui подключаем network
  4. QT += core gui network
  5.  
  6. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
  7.  
  8. TARGET = QLocalServer
  9. TEMPLATE = app
  10.  
  11. DEFINES += QT_DEPRECATED_WARNINGS
  12.  
  13. SOURCES += \
  14. main.cpp \
  15. mylocalserver.cpp
  16.  
  17. HEADERS += \
  18. mylocalserver.h
  1. // main.cpp
  2.  
  3. #include "mylocalserver.h"
  4. #include <QApplication>
  5.  
  6. int main(int argc, char* argv[])
  7. {
  8. QApplication app(argc, argv);
  9.  
  10. // Создаём и показываем объект класса MyLocalServer, определённого далее,
  11. // для запуска сервера с именем "MyLocalServer"
  12. MyLocalServer server("MyLocalServer");
  13. server.show();
  14.  
  15. return app.exec();
  16. }
  1. // mylocalserver.h
  2.  
  3. #ifndef MYLOCALSERVER_H
  4. #define MYLOCALSERVER_H
  5.  
  6. #include <QWidget>
  7. #include <QLocalServer>
  8. #include <QLocalSocket>
  9. #include <QTextEdit>
  10.  
  11. class MyLocalServer : public QWidget
  12. {
  13. Q_OBJECT
  14.  
  15. public:
  16. MyLocalServer(QString serverName, QWidget* parent = 0);
  17. ~MyLocalServer();
  18.  
  19. private:
  20. // Указатель на QLocalServer
  21. QLocalServer* localServer;
  22.  
  23. // Указатель на QTextEdit, в котором будет показываться поступающая
  24. // от клиента информация
  25. QTextEdit* textEdit;
  26.  
  27. // Переменная для хранения размера получаемого от клиента блока
  28. quint16 nextBlockSize;
  29.  
  30. // Метод для отправки клиенту подтверждения о приёме информации
  31. void sendToClient(QLocalSocket* localSocket, const QString& string);
  32.  
  33. public slots:
  34. // Слот обработки нового клиентского подключения
  35. virtual void slotNewConnection();
  36.  
  37. // Слот чтения информации от клиента
  38. void slotReadClient();
  39. };
  40.  
  41. #endif // MYLOCALSERVER_H
  1. // mylocalserver.cpp
  2.  
  3. #include "mylocalserver.h"
  4. #include <QVBoxLayout>
  5. #include <QMessageBox>
  6. #include <QLabel>
  7. #include <QTime>
  8.  
  9. MyLocalServer::MyLocalServer(QString serverName, QWidget* parent)
  10. : QWidget(parent), nextBlockSize(0) // Устанавливаем nextBlockSize равным нулю
  11. {
  12. // Создаём и запускаем сервер командой listen.
  13. // Если сервер не может быть запущен, выдать сообщение об ошибке и завершить работу программы
  14. localServer = new QLocalServer(this);
  15. if(!localServer->listen(serverName))
  16. {
  17. QMessageBox::critical(0, "Server error",
  18. "Unable to start server:" + localServer->errorString());
  19. localServer->close();
  20. return;
  21. }
  22.  
  23. // Соединяем сигнал сервера о наличии нового подключения с обработчиком нового клиентского подключения
  24. connect(localServer, SIGNAL(newConnection()), this, SLOT(slotNewConnection()));
  25.  
  26. // Формируем окно для просмотра текстовых сообщений от клиента
  27. textEdit = new QTextEdit;
  28. textEdit->setReadOnly(true);
  29. QVBoxLayout* layout = new QVBoxLayout;
  30. layout->addWidget(new QLabel(serverName));
  31. layout->addWidget(textEdit);
  32. setLayout(layout);
  33. }
  34.  
  35. MyLocalServer::~MyLocalServer()
  36. {
  37.  
  38. }
  39.  
  40. // Слот обработки нового клиентского подключения
  41. void MyLocalServer::slotNewConnection()
  42. {
  43. // Получаем сокет, подключённый к серверу
  44. QLocalSocket* localSocket = localServer->nextPendingConnection();
  45. // Соединяем сигнал отключения сокета с обработчиком удаления сокета
  46. connect(localSocket, SIGNAL(disconnected()), localSocket, SLOT(deleteLater()));
  47. // Соединяем сигнал сокета о готовности передачи данных с обработчиком данных
  48. connect(localSocket, SIGNAL(readyRead()), this, SLOT(slotReadClient()));
  49. // Отправляем информацию клиенту о соединении с сервером
  50. sendToClient(localSocket, "Server response: Connected!");
  51. }
  52.  
  53. // Слот чтения информации от клиента
  54. void MyLocalServer::slotReadClient()
  55. {
  56. // Получаем QLocalSocket после срабатывания сигнала о готовности передачи данных
  57. QLocalSocket* localSocket = (QLocalSocket*)sender();
  58. // Создаём входной поток получения данных на основе сокета
  59. QDataStream in(localSocket);
  60. // Устанавливаем версию сериализации данных потока. У клиента и сервера они должны быть одинаковыми
  61. in.setVersion(QDataStream::Qt_5_3);
  62. // Бесконечный цикл нужен для приёма блоков данных разных размеров, от двух байт и выше
  63. for(;;)
  64. {
  65. // Если размер блока равен нулю
  66. if(!nextBlockSize)
  67. {
  68. // Если размер передаваемого блока меньше двух байт, выйти из цикла
  69. if(localSocket->bytesAvailable() < (int)sizeof(quint16))
  70. break;
  71. // Извлекаем из потока размер блока данных
  72. in >> nextBlockSize;
  73. }
  74.  
  75. // Извлекаем из потока время и строку
  76. QTime time;
  77. QString string;
  78. in >> time >> string;
  79.  
  80. // Преобразуем полученные данные и показываем их в виджете
  81. QString message = time.toString() + " " + "Client has sent - " + string;
  82. textEdit->append(message);
  83.  
  84. nextBlockSize = 0;
  85.  
  86. // Отправляем ответ клиенту
  87. sendToClient(localSocket, "Server response: received \"" + string + "\"");
  88. }
  89. }
  90.  
  91. // Метод для отправки клиенту подтверждения о приёме информации
  92. void MyLocalServer::sendToClient(QLocalSocket* localSocket, const QString& string)
  93. {
  94. // Поскольку заранее размер блока неизвестен (параметр string может быть любой длины),
  95. // вначале создаём объект array класса QByteArray
  96. QByteArray array;
  97. // На его основе создаём выходной поток
  98. QDataStream out(&array, QIODevice::WriteOnly);
  99. // Устанавливаем версию сериализации данных потока
  100. out.setVersion(QDataStream::Qt_5_3);
  101. // Записываем в поток данные для отправки. На первом месте идёт нулевой размер блока
  102. out << quint16(0) << QTime::currentTime() << string;
  103.  
  104. // Перемещаем указатель на начало блока
  105. out.device()->seek(0);
  106. // Записываем двухбайтное значение действительного размера блока без учёта пересылаемого размера блока
  107. out << quint16(array.size() - sizeof(quint16));
  108.  
  109. // Отправляем получившийся блок клиенту
  110. localSocket->write(array);
  111. }

QLocalSocket

  1. # QLocalSocket.pro
  2.  
  3. # Кроме модулей core gui подключаем network
  4. QT += core gui network
  5.  
  6. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
  7.  
  8. TARGET = QLocalSocket
  9. TEMPLATE = app
  10.  
  11. DEFINES += QT_DEPRECATED_WARNINGS
  12.  
  13. SOURCES += \
  14. main.cpp \
  15. mylocalsocket.cpp
  16.  
  17. HEADERS += \
  18. mylocalsocket.h
  1. // main.cpp
  2.  
  3. #include "mylocalsocket.h"
  4. #include <QApplication>
  5.  
  6. int main(int argc, char* argv[])
  7. {
  8. QApplication app(argc, argv);
  9. // Создаём и показываем объект класса MyLocalSocket, определённого далее,
  10. // для запуска клиента, подключаемого к серверу с именем "MyLocalServer"
  11. MyLocalSocket socket("MyLocalServer");
  12. socket.show();
  13.  
  14. return app.exec();
  15. }
  1. // mylocalsocket.h
  2.  
  3. #ifndef MYLOCALSOCKET_H
  4. #define MYLOCALSOCKET_H
  5.  
  6. #include <QWidget>
  7. #include <QLocalSocket>
  8. #include <QTextEdit>
  9. #include <QPushButton>
  10.  
  11. class MyLocalSocket : public QWidget
  12. {
  13. Q_OBJECT
  14.  
  15. public:
  16. MyLocalSocket(QString serverName, QWidget* parent = 0);
  17. ~MyLocalSocket();
  18.  
  19. private:
  20. // Указатель на QLocalSocket
  21. QLocalSocket* localSocket;
  22.  
  23. // Указатели на элементы интерфейса
  24. QTextEdit* textEdit;
  25. QPushButton* sendRevision;
  26.  
  27. // Размер принимаемого от сервера блока
  28. quint16 nextBlockSize;
  29.  
  30. // Номер ревизии, отправляемый серверу
  31. // Увеличивается при каждом нажатии QPushButton
  32. int revision;
  33.  
  34. private slots:
  35. // Слот чтения информации, получаемой от сервера
  36. void slotReadyRead();
  37.  
  38. // Слот обработки ошибок сокета
  39. void slotError(QLocalSocket::LocalSocketError error);
  40.  
  41. // Слот передачи информации на сервер
  42. void slotSendToServer();
  43.  
  44. // Слот обработки сигнала соединения с сервером
  45. void slotConnected();
  46. };
  47.  
  48. #endif // MYLOCALSOCKET_H
  1. // mylocalsocket.cpp
  2.  
  3. #include "mylocalsocket.h"
  4. #include <QVBoxLayout>
  5. #include <QLabel>
  6. #include <QTime>
  7. #include <QMessageBox>
  8.  
  9. MyLocalSocket::MyLocalSocket(QString serverName, QWidget* parent)
  10. : QWidget(parent), nextBlockSize(0), revision(0)
  11. // Устанавливаем nextBlockSize и revision равными нулю
  12. {
  13. // Инициализируем сокет
  14. localSocket = new QLocalSocket(this);
  15.  
  16. // Устанавливаем соединение между сигналом ошибки сокета с обработчиком ошибок
  17. connect(localSocket, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error),
  18. this, &MyLocalSocket::slotError);
  19.  
  20. // Устанавливаем имя сервера, к которому сокет должен подключаться
  21. localSocket->setServerName(serverName);
  22.  
  23. // Устанавливаем соединение между сигналом подключения сокета к серверу
  24. // и обработчиком сигнала
  25. connect(localSocket, SIGNAL(connected()), SLOT(slotConnected()));
  26. // Соединяем сигнал сокета о готовности приёма данных данных с обработчиком данных
  27. connect(localSocket, SIGNAL(readyRead()), SLOT(slotReadyRead()));
  28.  
  29. // Инициализируем элементы интерфейса
  30. textEdit = new QTextEdit;
  31. sendRevision = new QPushButton("Send next revision");
  32.  
  33. // Соединяем нажатие кнопки с обработчиком, передающим информацию о ревизии на сервер
  34. connect(sendRevision, SIGNAL(clicked()), this, SLOT(slotSendToServer()));
  35.  
  36. // Настраиваем элементы интерфейса и формируем вид окна клиента
  37. textEdit->setReadOnly(true);
  38. QVBoxLayout* layout = new QVBoxLayout;
  39. layout->addWidget(new QLabel("Sender revisions"));
  40. layout->addWidget(textEdit);
  41. layout->addWidget(sendRevision);
  42. setLayout(layout);
  43.  
  44. // Подключаем сокет к серверу
  45. localSocket->connectToServer();
  46. }
  47.  
  48. MyLocalSocket::~MyLocalSocket()
  49. {
  50.  
  51. }
  52.  
  53. // Слот чтения информации, получаемой от сервера
  54. void MyLocalSocket::slotReadyRead()
  55. {
  56. // Всё аналогично приёму информации на стороне сервера
  57. QDataStream in(localSocket);
  58. in.setVersion(QDataStream::Qt_5_3);
  59. for(;;)
  60. {
  61. if(!nextBlockSize)
  62. {
  63. if(localSocket->bytesAvailable() < (int)sizeof(quint16))
  64. break;
  65. }
  66. in >> nextBlockSize;
  67.  
  68. if(localSocket->bytesAvailable() < nextBlockSize)
  69. break;
  70.  
  71. QTime time;
  72. QString string;
  73. in >> time >> string;
  74.  
  75. textEdit->append(time.toString() + " " + string);
  76. nextBlockSize = 0;
  77. }
  78. }
  79.  
  80. // Слот обработки ошибок сокета
  81. void MyLocalSocket::slotError(QLocalSocket::LocalSocketError error)
  82. {
  83. QString strError =
  84. "Error: " + (error == QLocalSocket::ServerNotFoundError ?
  85. "The server was not found." :
  86. error == QLocalSocket::PeerClosedError ?
  87. "The server is closed." :
  88. error == QLocalSocket::ConnectionRefusedError ?
  89. "The connection was refused." :
  90. QString(localSocket->errorString()));
  91. textEdit->append(strError);
  92. }
  93.  
  94. // Слот передачи информации на сервер
  95. void MyLocalSocket::slotSendToServer()
  96. {
  97. // Блок для передачи формируется аналогично тому, как это делается на сервере
  98. QByteArray arrayBlock;
  99. QDataStream out(&arrayBlock, QIODevice::WriteOnly);
  100. out.setVersion(QDataStream::Qt_5_3);
  101. ++revision;
  102. QString message = "Revision: " + QString("%1").arg(revision);
  103. out << quint16(0) << QTime::currentTime() << message;
  104.  
  105. out.device()->seek(0);
  106. out << quint16(arrayBlock.size() - sizeof(quint16));
  107.  
  108. localSocket->write(arrayBlock);
  109. }
  110.  
  111. // Слот обработки сигнала соединения с сервером
  112. void MyLocalSocket::slotConnected()
  113. {
  114. textEdit->append("Received the connected() signal");
  115. }

Первая особенность заключается в том, как именно подключается для QLocalSocket сигнал ошибки слота к сигналу обработки ошибок: через QOverload.

  1. connect(localSocket, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error),
  2. this, &MyLocalSocket::slotError);

Вторая особенность связана с тем, что для QLocalSocket это соединение должно быть объявлено до подключения к серверу, тогда как для QTcpSocket соединение сигнала ошибки и слота обработки ошибок может быть объявлено после подключения к серверу. Подробнее об этом см. тему форума: https://evileg.com/ru/forum/topic/965/ .

В итоге на мониторе должна быть такая картина.

Код примеров на GitHub: QLocalServer , QLocalSocket .

По статье задано0вопрос(ов)

3

Вам это нравится? Поделитесь в социальных сетях!

IscanderChe
  • 19 июля 2019 г. 12:57

Остальные сигналы и слоты соединены между собой в старом стиле, так как фрагмент кода сокета, показанный ниже, компилировался с ошибками.

  1. void MyLocalServer::slotNewConnection()
  2. {
  3. QLocalSocket* localSocket = localServer->nextPendingConnection();
  4. connect(localSocket, SIGNAL(disconnected()), localSocket, SLOT(deleteLater()));
  5. connect(localSocket, SIGNAL(readyRead()), this, SLOT(slotReadClient()));
  6. sendToClient(localSocket, "Server response: Connected!");
  7. }

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь