Проект Simple Tracker. Часть 7: сервер и клиент

Simple Tracker, Qt, Iscander Che, C++

Рассмотрим теперь сервер и клиента.

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

Сервер

// trackerserver.cpp

TrackerServer::TrackerServer(QWidget* parent)
    : QWidget(parent)
{
    // Задаём имя сервера
    nameServer = "TrackerServer";
    // Устанавливаем размер следующего блока равным нулю
    nextBlockSize = 0;
    ...
}

// Метод создания и запуска сервера
void TrackerServer::createServer()
{
    localServer = new QLocalServer(this);
    // Если сервер не запустить, выдать сообщение и завершить программу
    if(!localServer->listen(nameServer))
    {
        QMessageBox::critical(0,
                              tr("Ошибка сервера"),
                              tr("Невозможно запустить сервер ") + nameServer + ": "
                              + localServer->errorString());
        localServer->close();
        return;
    }
}
...

// Метод создания подключений
void TrackerServer::createConnections()
{
    // Подключение сигнала сервера о новом подключении к обработчику нового подключения
    connect(localServer, &QLocalServer::newConnection, this, &TrackerServer::slotNewConnection);
    ...
}

Теперь рассмотрим слоты сервера.

// trackerserver.cpp

// Слот обработки нового соединения
void TrackerServer::slotNewConnection()
{
    // Получаем сокет, подключённый к серверу
    QLocalSocket* socket = localServer->nextPendingConnection();

    // Соединяем сигнал отключения сокета с обработчиком удаления сокета
    connect(socket, &QLocalSocket::disconnected, socket, &QLocalSocket::deleteLater);

    // Соединяем сигнал сокета о готовности передачи данных со слотом закрытия задачи
    connect(socket, &QLocalSocket::readyRead, this, &TrackerServer::slotCloseTask);
}

// Слот получения информации от клиента и закрытия задачи
void TrackerServer::slotCloseTask()
{
    QLocalSocket* socket = (QLocalSocket*)sender();
    QDataStream in(socket);
    in.setVersion(QDataStream::Qt_5_3);
    for(;;)
    {
        if(!nextBlockSize)
        {
            if(socket->bytesAvailable() < (int)sizeof(quint16))
                break;
            in >> nextBlockSize;
        }

        in >> clientTaskNumber >> clientRevision;

        nextBlockSize = 0;
    }

    if(!database->isClosed(clientTaskNumber) && clientTaskNumber && clientRevision)
        // Если задача не закрыта и если номер задачи и ревизия не пустые, закрываем задачу
    {
        database->closeTask(clientTaskNumber, QString("%1").arg(clientRevision));

        // Обновляем SQL-таблицу задач
        taskSqlTableModel->select();

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

Клиент

// main.cpp

#include "trackerclient.h"
#include <QCoreApplication>
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <QRegExp>

int main(int argc, char* argv[])
{
    QCoreApplication app(argc, argv);

    // Считываем из аргумента 1 командной строки путь к файлу log.txt
    QString pathToFile = argv[1];

    QString result;

    // Если файл открывается, считываем данные из файла в строку
    QFile file(pathToFile);
    if(file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QTextStream stream(&file);
        result = stream.readAll();
        file.close();
    }
    else
        qDebug() << "Unable open file " + pathToFile;

    int taskNumber = 0;
    int revisionNumber = 0;

    // С помощью регулярного выражения извлекаем из строки номер задачи и номер ревизии
    QString expr = "(.*)(\\#\\{)(\\d+)(\\})(.*)(r\\{)(\\d+)(\\})";
    QRegExp taskRevision(expr);
    int position = taskRevision.indexIn(result);
    if(position > -1)
    {
        taskNumber = taskRevision.cap(3).toInt(); // это номер задачи
        revisionNumber = taskRevision.cap(7).toInt(); // это номер ревизии
    }

    // Инициализируем клиента с соответствующими параметрами
    TrackerClient client("TrackerServer", taskNumber, revisionNumber);

    app.exit(0);
}

Теперь разберём вопрос, откуда берётся log.txt.

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

Проекты с поддержкой СКВ формируются с помощью bat-файла. Его я написал несколько раньше из необходимости создавать и использовать репозитории SVN так, чтобы они при наличии коммитов автоматизированно архивировались. Рассмотрим его в той части, которая касается создания проектов с поддержкой СКВ, то есть уже реально существующих в файловой системе проектов.

rem Запрашиваем наименование проекта
set /p repo_name=[Input name project]: 
echo Name repository: %repo_name%

rem Устанавливаем путь к репозиторию
set repo_path=%repo_root%\%repo_name%
echo Full path to repository: %repo_path%

rem Создаём репозиторий
svnadmin create %repo_path%

rem Очищаем папку репозитория hooks
del /f /q %repo_path%\hooks\

rem Создаём / копируем в папке hooks файлы ini.bat, pre-commit.bat, post-commit.bat
echo set commit=n>%repo_path%\hooks\ini.bat
copy pre-commit.bat %repo_path%\hooks\
copy post-commit2.bat %repo_path%\hooks\post-commit.bat

rem Импортируем файловую структуру по умолчанию и формируем первый коммит
svn import %def_tree% file:///%repo_path% -m "initial import"

Файл ini.bat содержит в себе флаг commit, устанавливаемый в "n", если коммитов нет, или устанавливаемый в "y", если есть хотя бы один коммит. По его состоянию скрипт архивации определяет, надо ли архивировать данный репозиторий.

rem pre-commit.bat
@echo off

rem Считываем переменные из командной строки
set repo_path=%1
set transaction=%2

rem Записываем сообщение коммита в файл log.txt
"G:\soft\svn\bin\svnlook.exe" log -t "%transaction%" "%repo_path%">%repo_path%\hooks\log.txt

В сообщении коммита содержится номер закрываемой задачи в формате #{ <номер задачи> } .

rem post-commit.bat

@echo off

rem Считываем переменные из командной строки
set repo_path=%1
set revision=%2

rem Устанавливаем в файле ini.bat флаг commit в "y"
echo set commit=y>%repo_path%\hooks\ini.bat

rem Записываем номер ревизии в файл log.txt
echo r{%revision%}>>%repo_path%\hooks\log.txt

rem 
%path_to_tracker%\ICTrackerClient.exe  %repo_path%\hooks\log.txt

После записи ревизии в post-commit.bat файл log.txt выглядит примерно следующим образом.

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

И последним вызывается клиент, который считывает эти данные из файла.

Осталось только передать эти данные серверу.

// trackerclient.h

#ifndef TRACKERCLIENT_H
#define TRACKERCLIENT_H

#include <QObject>
#include <QLocalSocket>

class TrackerClient : public QObject
{
    Q_OBJECT

public:
    explicit TrackerClient(const QString& serverName, int taskId, int revision,
                           QObject* parent = 0);

    ~TrackerClient();

private:
    QLocalSocket* localSocket;
    int taskId;
    int revision;

private slots:
    void sendToServer();
};

#endif // TRACKERCLIENT_H
// trackerclient.cpp

#include "trackerclient.h"
#include <QDataStream>

TrackerClient::TrackerClient(const QString &serverName, int taskId, int revision,
                             QObject* parent) :
    QObject(parent), taskId(taskId), revision(revision)
{
    localSocket = new QLocalSocket(this);
    localSocket->setServerName(serverName);

    connect(localSocket, &QLocalSocket::connected, this, &TrackerClient::sendToServer);

    localSocket->connectToServer();
}

TrackerClient::~TrackerClient()
{
}

void TrackerClient::sendToServer()
{
    QByteArray arrayBlock;
    QDataStream out(&arrayBlock, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_5_3);
    out << quint16(0) << taskId << revision;

    out.device()->seek(0);
    out << quint16(arrayBlock.size() - sizeof(quint16));

    localSocket->write(arrayBlock);

    localSocket->disconnectFromServer();
}
We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

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

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

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

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

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

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

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

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

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

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

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

Вот это

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

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

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

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

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

QLocalSocket* localSocket;

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

QLocalSocket* m_localSocket;

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

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

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

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

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

Ага.

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

Понял.

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

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

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

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

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

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

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

QSharedPointer<HelloWorldClass> hwc2;
hwc2 = new HelloWorldClass();

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

QSharedPointer<HelloWorldClass> hwc2;
hwc2.reset(new HelloWorldClass()); // Удаляет автоматически предыдущий объект, если он там был, и если нет указателей на тот объект в других QSharedPointer

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

connect(localSocket, &QLocalSocket::connected, this, &TrackerClient::sendToServer);

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

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Donate

Hello, Dear Users of EVILEG!!!

If the site helped you, then support the development of the site financially, please.

You can do it by following ways:

Thank you, Evgenii Legotckoi

AT
Dec. 10, 2019, 8:06 a.m.
Anastasija Troschenkova

C++ - Test 001. The first program and data types

  • Result:60points,
  • Rating points-1
AT
Dec. 10, 2019, 8:02 a.m.
Anastasija Troschenkova

Qt - Test 001. Signals and slots

  • Result:73points,
  • Rating points1
g
Dec. 10, 2019, 4:51 a.m.
gisLu

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:40points,
  • Rating points-8
Last comments
Dec. 9, 2019, 3:41 a.m.
Evgenij Legotskoj

Эта ошибка invalid use of incomplete type ‘class Ui::AnotherWindow’ обычно говорит о том, что не найдено определение класса или структуры. Типичная проблема - не подключён заголовочны…
NB
Dec. 9, 2019, 3:36 a.m.
Nikolaj Batmanov

Ну, не настолько со мной всё полхо...))) Вроде бы. Я ж кнопки отрисовываю.
Dec. 9, 2019, 3:14 a.m.
Evgenij Legotskoj

Добрый день. У вас ui файлов по ходу нет. UI файлы используются для вёрстки в графическом дизайнере.
NB
Dec. 9, 2019, 3:05 a.m.
Nikolaj Batmanov

Здравствуйте! Полностью скопировал ваш пример к себе, чтобы разобраться. А он не хочет запускаться, дает ошибку: invalid use of incomplete type ‘class Ui::AnotherWindow’ ui(new Ui…
Dec. 8, 2019, 7:23 a.m.
Evgenij Legotskoj

У меня здесь есть одна старая статья с примером векторного редактора. Там есть ответы на ваши вопросы. Поизучайте Qt/C++ - Урок 072. Пример векторного редактора на Qt QGraphicsItem, QG…
Now discuss on the forum
MU
Dec. 11, 2019, 8:27 a.m.
Maciej Urmański

Thank you! Now works, and this is solution. num_embed = Embed.objects.filter(added_by=recipe.added_by).count()
Dec. 11, 2019, 8:12 a.m.
Mihailll

Так работает. Взял этот пример https://api-2d3d-cad.com/face_recognition_with_opencv/ void MainWindow::on_pushButton_4_clicked() //фото определение лица{ // Load Face cascade (.xml…
TD
Dec. 10, 2019, 4:14 a.m.
Timur Dosov

Спасибо, работает. А ещё вопрос: как загрузить страницу с динамической подгрузкой контента по скроллингу? Например - [https://ntvplus.ru/tv/]. Пока делаю через костыль - QApplication::s…
Dec. 9, 2019, 7:16 a.m.
qml_puthon_user

Я сделал простой вывод текста по испусканию сигнала... Чего не хватает программе?) Python: # системные библиотекиimport cv2import numpy as npimport sysimport threading# PyQt б…
SK
Dec. 8, 2019, 4:11 p.m.
Semen Kosandjak

інтерфейс qt, приклад того додаю на малюнку, я натискаю на кнопку і у мене з'являється 2 текст лайну в які я вводжу значення, тобто в 1 цифри,у другому випадку це літери, тобто c++, без графічно…
EVILEG
About
Services
© EVILEG 2015-2019
Recommend hosting TIMEWEB