IscanderChe
July 31, 2019, 1:57 p.m.

Simple Tracker project. Part 7: server and client

Content

Consider now the server and the client.

Questions regarding the general organization of client-server interaction can be found in this article: " An example of using QLocalServer and QLocalSocket ". Here I will touch only on the points directly related to the transfer of information on the task and closing the task.

Server

  1. // trackerserver.cpp
  2.  
  3. TrackerServer::TrackerServer(QWidget* parent)
  4. : QWidget(parent)
  5. {
  6. // Задаём имя сервера
  7. nameServer = "TrackerServer";
  8. // Устанавливаем размер следующего блока равным нулю
  9. nextBlockSize = 0;
  10. ...
  11. }
  12.  
  13. // Метод создания и запуска сервера
  14. void TrackerServer::createServer()
  15. {
  16. localServer = new QLocalServer(this);
  17. // Если сервер не запустить, выдать сообщение и завершить программу
  18. if(!localServer->listen(nameServer))
  19. {
  20. QMessageBox::critical(0,
  21. tr("Ошибка сервера"),
  22. tr("Невозможно запустить сервер ") + nameServer + ": "
  23. + localServer->errorString());
  24. localServer->close();
  25. return;
  26. }
  27. }
  28. ...
  29.  
  30. // Метод создания подключений
  31. void TrackerServer::createConnections()
  32. {
  33. // Подключение сигнала сервера о новом подключении к обработчику нового подключения
  34. connect(localServer, &QLocalServer::newConnection, this, &TrackerServer::slotNewConnection);
  35. ...
  36. }

Now consider the server slots.

  1. // trackerserver.cpp
  2.  
  3. // Слот обработки нового соединения
  4. void TrackerServer::slotNewConnection()
  5. {
  6. // Получаем сокет, подключённый к серверу
  7. QLocalSocket* socket = localServer->nextPendingConnection();
  8.  
  9. // Соединяем сигнал отключения сокета с обработчиком удаления сокета
  10. connect(socket, &QLocalSocket::disconnected, socket, &QLocalSocket::deleteLater);
  11.  
  12. // Соединяем сигнал сокета о готовности передачи данных со слотом закрытия задачи
  13. connect(socket, &QLocalSocket::readyRead, this, &TrackerServer::slotCloseTask);
  14. }
  15.  
  16. // Слот получения информации от клиента и закрытия задачи
  17. void TrackerServer::slotCloseTask()
  18. {
  19. QLocalSocket* socket = (QLocalSocket*)sender();
  20. QDataStream in(socket);
  21. in.setVersion(QDataStream::Qt_5_3);
  22. for(;;)
  23. {
  24. if(!nextBlockSize)
  25. {
  26. if(socket->bytesAvailable() < (int)sizeof(quint16))
  27. break;
  28. in >> nextBlockSize;
  29. }
  30.  
  31. in >> clientTaskNumber >> clientRevision;
  32.  
  33. nextBlockSize = 0;
  34. }
  35.  
  36. if(!database->isClosed(clientTaskNumber) && clientTaskNumber && clientRevision)
  37. // Если задача не закрыта и если номер задачи и ревизия не пустые, закрываем задачу
  38. {
  39. database->closeTask(clientTaskNumber, QString("%1").arg(clientRevision));
  40.  
  41. // Обновляем SQL-таблицу задач
  42. taskSqlTableModel->select();
  43.  
  44. // Если все задачи для текущего проекта закрыты
  45. if(database->isClosedAllTasks(projectCurrentId))
  46. // сделать доступной кнопку "Архивировать"
  47. archiveProjectButton->setEnabled(true);
  48. else
  49. // иначе блокировать кнопку "Архивировать"
  50. archiveProjectButton->setDisabled(true);
  51. }
  52. }

Customer

  1. // main.cpp
  2.  
  3. #include "trackerclient.h"
  4. #include <QCoreApplication>
  5. #include <QFile>
  6. #include <QTextStream>
  7. #include <QDebug>
  8. #include <QRegExp>
  9.  
  10. int main(int argc, char* argv[])
  11. {
  12. QCoreApplication app(argc, argv);
  13.  
  14. // Считываем из аргумента 1 командной строки путь к файлу log.txt
  15. QString pathToFile = argv[1];
  16.  
  17. QString result;
  18.  
  19. // Если файл открывается, считываем данные из файла в строку
  20. QFile file(pathToFile);
  21. if(file.open(QIODevice::ReadOnly | QIODevice::Text))
  22. {
  23. QTextStream stream(&file);
  24. result = stream.readAll();
  25. file.close();
  26. }
  27. else
  28. qDebug() << "Unable open file " + pathToFile;
  29.  
  30. int taskNumber = 0;
  31. int revisionNumber = 0;
  32.  
  33. // С помощью регулярного выражения извлекаем из строки номер задачи и номер ревизии
  34. QString expr = "(.*)(\\#\\{)(\\d+)(\\})(.*)(r\\{)(\\d+)(\\})";
  35. QRegExp taskRevision(expr);
  36. int position = taskRevision.indexIn(result);
  37. if(position > -1)
  38. {
  39. taskNumber = taskRevision.cap(3).toInt(); // это номер задачи
  40. revisionNumber = taskRevision.cap(7).toInt(); // это номер ревизии
  41. }
  42.  
  43. // Инициализируем клиента с соответствующими параметрами
  44. TrackerClient client("TrackerServer", taskNumber, revisionNumber);
  45.  
  46. app.exit(0);
  47. }

Now let's analyze the question of where log.txt comes from.

As I wrote in the previous part, to simplify the tracker, you can use the project creation button to create only a project without VCS support. That is, such a virtual project that exists only in the tracker database and nowhere else.

Projects with SLE support are formed using a bat-file. I wrote it a little earlier out of the need to create and use SVN repositories so that they are automatically archived when there are commits. Let's consider it in the part that concerns the creation of projects with support for hard currency, that is, projects that already exist in the file system.

  1. rem Запрашиваем наименование проекта
  2. set /p repo_name=[Input name project]:
  3. echo Name repository: %repo_name%
  4.  
  5. rem Устанавливаем путь к репозиторию
  6. set repo_path=%repo_root%\%repo_name%
  7. echo Full path to repository: %repo_path%
  8.  
  9. rem Создаём репозиторий
  10. svnadmin create %repo_path%
  11.  
  12. rem Очищаем папку репозитория hooks
  13. del /f /q %repo_path%\hooks\
  14.  
  15. rem Создаём / копируем в папке hooks файлы ini.bat, pre-commit.bat, post-commit.bat
  16. echo set commit=n>%repo_path%\hooks\ini.bat
  17. copy pre-commit.bat %repo_path%\hooks\
  18. copy post-commit2.bat %repo_path%\hooks\post-commit.bat
  19.  
  20. rem Импортируем файловую структуру по умолчанию и формируем первый коммит
  21. svn import %def_tree% file:///%repo_path% -m "initial import"

The ini.bat file contains the commit flag, set to "n" if there are no commits, or set to "y" if there is at least one commit. Based on its state, the archiving script determines whether the given repository should be archived.

  1. rem pre-commit.bat
  2. @echo off
  3.  
  4. rem Считываем переменные из командной строки
  5. set repo_path=%1
  6. set transaction=%2
  7.  
  8. rem Записываем сообщение коммита в файл log.txt
  9. "G:\soft\svn\bin\svnlook.exe" log -t "%transaction%" "%repo_path%">%repo_path%\hooks\log.txt

The commit message contains the number of the task to be closed in the format #{ <issue number> } .

  1. rem post-commit.bat
  2.  
  3. @echo off
  4.  
  5. rem Считываем переменные из командной строки
  6. set repo_path=%1
  7. set revision=%2
  8.  
  9. rem Устанавливаем в файле ini.bat флаг commit в "y"
  10. echo set commit=y>%repo_path%\hooks\ini.bat
  11.  
  12. rem Записываем номер ревизии в файл log.txt
  13. echo r{%revision%}>>%repo_path%\hooks\log.txt
  14.  
  15. rem
  16. %path_to_tracker%\ICTrackerClient.exe %repo_path%\hooks\log.txt

After writing the revision to post-commit.bat, the log.txt file looks something like this.

  1. Closing task #{2} end some editing files
  2. r{29}

And the last client is called, which reads this data from the file.

It remains only to transfer this data to the server.

  1. // trackerclient.h
  2.  
  3. #ifndef TRACKERCLIENT_H
  4. #define TRACKERCLIENT_H
  5.  
  6. #include <QObject>
  7. #include <QLocalSocket>
  8.  
  9. class TrackerClient : public QObject
  10. {
  11. Q_OBJECT
  12.  
  13. public:
  14. explicit TrackerClient(const QString& serverName, int taskId, int revision,
  15. QObject* parent = 0);
  16.  
  17. ~TrackerClient();
  18.  
  19. private:
  20. QLocalSocket* localSocket;
  21. int taskId;
  22. int revision;
  23.  
  24. private slots:
  25. void sendToServer();
  26. };
  27.  
  28. #endif // TRACKERCLIENT_H
  1. // trackerclient.cpp
  2.  
  3. #include "trackerclient.h"
  4. #include <QDataStream>
  5.  
  6. TrackerClient::TrackerClient(const QString &serverName, int taskId, int revision,
  7. QObject* parent) :
  8. QObject(parent), taskId(taskId), revision(revision)
  9. {
  10. localSocket = new QLocalSocket(this);
  11. localSocket->setServerName(serverName);
  12.  
  13. connect(localSocket, &QLocalSocket::connected, this, &TrackerClient::sendToServer);
  14.  
  15. localSocket->connectToServer();
  16. }
  17.  
  18. TrackerClient::~TrackerClient()
  19. {
  20. }
  21.  
  22. void TrackerClient::sendToServer()
  23. {
  24. QByteArray arrayBlock;
  25. QDataStream out(&arrayBlock, QIODevice::WriteOnly);
  26. out.setVersion(QDataStream::Qt_5_3);
  27. out << quint16(0) << taskId << revision;
  28.  
  29. out.device()->seek(0);
  30. out << quint16(arrayBlock.size() - sizeof(quint16));
  31.  
  32. localSocket->write(arrayBlock);
  33.  
  34. localSocket->disconnectFromServer();
  35. }

By article asked0question(s)

3

Do you like it? Share on social networks!

Evgenii Legotckoi
  • Aug. 1, 2019, 2:18 p.m.

Добрый день, и снова немного ревью.

Вот это каст в стиле Си

  1. QLocalSocket* socket = (QLocalSocket*)sender();

Лучше кастовать в стиле С++, мы же на С++ пишем.

  1. QLocalSocket* socket = static_cast<QLocalSocket*>(sender());

Получается несколько многословнее, но честно, в большом проекте хоть есть за что глазу зацепиться. Плюс более контролируемый каст.

Далее, думаю, что вот это

  1. // Если все задачи для текущего проекта закрыты
  2. if(database->isClosedAllTasks(projectCurrentId))
  3. // сделать доступной кнопку "Архивировать"
  4. archiveProjectButton->setEnabled(true);
  5. else
  6. // иначе блокировать кнопку "Архивировать"
  7. archiveProjectButton->setDisabled(true);

Можно переписать так

  1. archiveProjectButton->setEnabled(database->isClosedAllTasks(projectCurrentId));

setEnabled и setDisabled делают тоже самое.

Вот это

  1. explicit TrackerClient(const QString& serverName, int taskId, int revision,
  2. QObject* parent = 0);

Нужно переписать так

  1. explicit TrackerClient(const QString& serverName, int taskId, int revision, QObject* parent = nullptr);

nullptr - это специальный псевдотип данных для нулевого указателя. В случае с 0, там по сути может оказаться какой угодно мусор в зависимости от платформы. В случае с nullptr поведение будет предсказуемым.

Далее вот это объявление

  1. QLocalSocket* localSocket;

Наверное стоит вам сразу писать как

  1. QLocalSocket* m_localSocket;

Это принятый код-стайл в Qt, а также именно на такую запись заточены макросы Q_PROPERTY. Ну и стоит в деструкторе удалять m_localSocket, чтобы не было утчечек памяти. Вообще этот как раз тот случай, где имеет смысл использовать умные указатели, как вы делали в прошлой статье.

IscanderChe
  • Aug. 1, 2019, 4:31 p.m.
  • (edited)

  1. QLocalSocket* socket = static_cast<QLocalSocket*>(sender());

Получается несколько многословнее, но честно, в большом проекте хоть есть за что глазу зацепиться. Плюс более контролируемый каст.

Да, я уже со static_cast освоился, такая запись понятнее.

setEnabled и setDisabled делают тоже самое

Ага.

nullptr - это специальный псевдотип данных для нулевого указателя

Понял.

Наверное стоит вам сразу писать как QLocalSocket* m_localSocket;

А что означает буква m перед наименованием? Эти буквы меня всегда путают и с толку сбивают.

Evgenii Legotckoi
  • Aug. 1, 2019, 4:37 p.m.

А что означает буква m перед наименованием? Эти буквы меня всегда путают и с толку сбивают.

member - член класса, некоторое поле. В Qt это обычное обозначение тех объектов, которые объявлены в private или protected секциях.

Просто в Q_PROPERTY это прописывается без m_ но макрос разворачивается на член класса, который содержит этот префикс.

IscanderChe
  • Aug. 1, 2019, 4:56 p.m.
  • (edited)

Вообще этот как раз тот случай, где имеет смысл использовать умные указатели

Как правильно объявить умный указатель и инициализировать по отдельности? Я пробовал так:

  1. QSharedPointer<HelloWorldClass> hwc2;
  2. hwc2 = new HelloWorldClass();

выдаёт ошибку при компиляции.

Evgenii Legotckoi
  • Aug. 1, 2019, 4:59 p.m.
  • (edited)
  1. QSharedPointer<HelloWorldClass> hwc2;
  2. hwc2.reset(new HelloWorldClass()); // Удаляет автоматически предыдущий объект, если он там был, и если нет указателей на тот объект в других QSharedPointer
IscanderChe
  • Aug. 1, 2019, 5:20 p.m.
  • (edited)

Теперь на коннекте ошибку выдаёт:

  1. connect(localSocket, &QLocalSocket::connected, this, &TrackerClient::sendToServer);
Evgenii Legotckoi
  • Aug. 1, 2019, 5:24 p.m.
  • (edited)

connect(m_localSocket.get(), &QLocalSocket::connected, this, &TrackerClient::sendToServer);

Comments

Only authorized users can post comments.
Please, Log in or Sign up
  • Last comments
  • AK
    April 1, 2025, 11:41 a.m.
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    March 9, 2025, 9:02 p.m.
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    March 9, 2025, 4:14 p.m.
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…
  • ИМ
    Nov. 22, 2024, 9:51 p.m.
    Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
  • Evgenii Legotckoi
    Oct. 31, 2024, 11:37 p.m.
    Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup