IscanderChe
IscanderChe31 июля 2019 г. 3:57

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

Содержание

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

С вопросами, касающимися общей организации взаимодействия клиента и сервера, можно ознакомиться в этой статье: " Пример использования 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();
}
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

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

Evgenii Legotckoi
  • 1 августа 2019 г. 4:18

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

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

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, чтобы не было утчечек памяти. Вообще этот как раз тот случай, где имеет смысл использовать умные указатели, как вы делали в прошлой статье.

IscanderChe
  • 1 августа 2019 г. 6:31
  • (ред.)

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

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

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

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

Ага.

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

Понял.

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

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

Evgenii Legotckoi
  • 1 августа 2019 г. 6:37

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

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

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

IscanderChe
  • 1 августа 2019 г. 6:56
  • (ред.)

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

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

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

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

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

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

connect(localSocket, &QLocalSocket::connected, this, &TrackerClient::sendToServer);
Evgenii Legotckoi
  • 1 августа 2019 г. 7:24
  • (ред.)

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

Комментарии

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

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50баллов,
  • Очки рейтинга-4
m
  • molni99
  • 26 октября 2024 г. 7:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80баллов,
  • Очки рейтинга4
m
  • molni99
  • 26 октября 2024 г. 7:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20баллов,
  • Очки рейтинга-10
Последние комментарии
ИМ
Игорь Максимов22 ноября 2024 г. 17:51
Django - Урок 017. Кастомизированная страница авторизации на Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 октября 2024 г. 19:37
Django - Урок 064. Как написать расширение для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 октября 2024 г. 14:19
Читалка fb3-файлов на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5 октября 2024 г. 13:51
Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55 июля 2024 г. 17:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Сейчас обсуждают на форуме
m
moogo22 ноября 2024 г. 13:17
Mosquito Spray System Effective Mosquito Systems for Backyard | Eco-Friendly Misting Control Device & Repellent Spray - Moogo ; Upgrade your backyard with our mosquito-repellent device! Our misters conce…
Evgenii Legotckoi
Evgenii Legotckoi24 июня 2024 г. 21:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 ноября 2024 г. 12:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject4 июня 2022 г. 9:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…

Следите за нами в социальных сетях