1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wujehy-server-framework-cpp

Клонировать/Скачать
README.md 19 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 01.06.2025 16:03 4c8facd

ServerFrameworkCpp

Описание

C++ реализация клиент-серверной архитектуры.

Использует std + boost для создания кросс-платформенного, легко интегрируемого и модульного решения.

Использует сокращённую версию boost Yöntem 1.70.

Система логирования основана на glog.

Фреймворк не имеет утечек памяти.

Подходит для начинающих, а также для начальных коммерческих проектов.

Базовое руководство: добавление простого модуля

Добавление бизнес-модуля требует двух шагов: наследование базового класса и реализация бизнес-функциональности.

Второй шаг: регистрация модуля в менеджере.

Примечание: В настоящее время модули хранятся в директории Mod, для просмотра исходного кода.

Создайте класс TestMod.cpp / TestMod.h

Наследуйте базовый класс GeeJoan::BaseMod, и реализуйте init, mod_type и procMessage.

// файл: TestMod.h
// Наследование базового класса для реализации функциональности модуля
namespace GeeJoan
{
    class TestMod : public BaseMod
    {
    public:
        void init() override;
        ModTypeEnum mod_type() override;
        void procMessage(const HanderDataType *hander, StatusCodeTypeEnum &code, std::string &resposeBody) override;
    };
}
/*
 * // Если не хотите использовать пространство имен GeeJoan, используйте следующий вариант
class TestMod:public GeeJoan::BaseMod{
public:
    GeeJoan::ModTypeEnum mod_type() override;
    void procMessage(const GeeJoan::HanderDataType *hander, GeeJoan::StatusCodeTypeEnum &code, std::string &resposeBody) override;
    void init() override;
};
 */
// файл: TestMod.cpp
```// Включение базовых заголовков
#include <base_types.h> // базовые типы
#include "TestMod.h" // заголовок модуля
#include "NetworkProtocol.h" // заголовок для типов данных сетевых пакетов

// Это заголовок для системы логирования
#include "glog/logging.h"

using namespace GeeJoan;
// Возвращает тип модуля
ModTypeEnum TestMod::mod_type()
{
    return ModType_TestMod;
}
// Этот метод вызывается при получении пакета определённого типа
// hander содержит базовую информацию о пакете, такую как идентификатор соединения, тип бизнеса, подтип и сообщение от клиента
// code - это тип возвращаемого кода
// resposeBody - это данные, которые будут отправлены клиенту
// Процесс обработки заключается в заполнении кода обработки и данных, которые нужно отправить клиенту
void TestMod::procMessage(const HanderDataType *hander, StatusCodeTypeEnum &code, std::string &resposeBody)
{
    LOGINFO << "  TestMod::procMessage client[" << int(hander->clientid) << "] subtype = " << hander->subtype << " msg = " << hander->message;
    if (hander->subtype == OpTestModSubType_Test2)
    {
        // Когда подтип является Test2
        code = Status_SUCCESS; // Возвращаем успех
        resposeBody = "end"; // Возвращаем "end"

    } else
    {
        // Аналогично, для всех остальных случаев возвращаем "pong"
        code = Status_SUCCESS;
        resposeBody = "pong";
    }
    
// Если вы считаете, что это слишком сложная реализация, 
// вы можете удалить код выше и использовать следующие две строки для реализации простого ответа, 
// не учитывая подтипы
//    code = Status_SUCCESS;
//    resposeBody = "pong";
}```



После реализации бизнес-логики, регистрируем её в менеджере

```c++
void ModManager::registerMOD()
{
    auto do_reg_fun = [&](BaseMod *mod, bool auto_del = true)
    {
        LOGINFO << "register type " << mod->mod_type();
        auto pt = m_mod_map.insert( {mod->mod_type(), mod} );
        if (pt.second == false && auto_del == true)
        {
            delete mod;
        }
        return pt.second;
    };

    // TODO Регистрируйте ваш модуль
    do_reg_fun( new TestMod ); // Тестовый модуль
    do_reg_fun( new AuthMod ); // Модуль аутентификации
    // NOTE wujehy Регистрируем новый бизнес
    do_reg_fun( new KeyValueMod ); // KV

}

Затем скомпилируйте и запустите тестовый модуль. Скачайте версию для предварительного просмотра и запустите её для проверки, Windows 10, Ubuntu 20.04 (основные зависимости решены, если возникнут проблемы, рекомендуется использовать Linux или mingw)

Поддерживаемые клиентские команды:

>>> calltest1 --- > pong Ответ на тест
>>> calltest2 --- > Пресс-тест Отправка 10000 пакетов, затем "end" для завершения
>>> closelog ---> Закрыть лог, чтобы уменьшить вывод при тестировании calltest2
>>> showlog ---> Показать лог
>>> showtime ---> Показать время выполнения теста calltest2
>>> q ---> Выход из программы
>>> ls ---> Показать список поддерживаемых команд
>>> set ---> Установить ключ-значение
>>> get ---> Получить значение ключа

Основные сведения

Репозиторий: git clone https://gitee.com/wujehy/server-framework-cpp

Инициализация репозитория: git submodule update --init --recursive

Шаги компиляции:

mkdir build 

cd build 

cmake ..
``````cmake
cmake --build . 

Реализация сервера:

tests/testServer.cpp

MainServer/*

Реализация клиента:

tests/testClient.cpp

SubClient/*

Тестирование

Тестовый пример только для модуля Test

Если вам нужны примеры проектов, они будут перенесены в GeeJoanServerCpp для полной реализации. Сначала запустите сервер с портом по умолчанию 10000

Логи производительности:

Данные приведены для справки, результаты можно проверить в логах.

Единица измерения: наносекунды:

Количество запросов: 10000

>>> closelog
>>> calltest2
 send start  = 1600053802950994>>> recv taskid = 16 time  = 1600053803713844
>>> showtime
 start Time = 1600053802950994 last time = 1600053803713844
 useTime = 762850
>>>

Количество тестовых запросов: 10000 + 1

void Test2Function(GeeJoan::ClientService *client)
{
    auto callbackStatus = [](int status ){
        LOGINFO << "semd callback status = " << status;
    };
    cache_startTime = getTimeMicro();
    std::cout << " send start  = " << cache_startTime;

    for (int i = 0 ; i < 10000 ; i++)
    {
        client->send(ModType_TestMod , OpTestModSubType_Test1 , "test1" , callbackStatus);
    }
    client->send(ModType_TestMod , OpTestModSubType_Test2 , "end" , callbackStatus);
}

Примеры тестирования сервера:

AppManager appManager;
// Установка порта
appManager.setPort(10000);
appManager.setLogPath(".");
appManager.init_local();

app = &appManager;

signal(SIGINT, [](int s)
{
    LOGINFO << " exit ";
    app->stop();
});

signal(SIGTERM, [](int s)
{
    LOGINFO << " exit ";
    app->stop();
});
app->init_network();
app->run();

Операции клиента:

Запуск

$ ./Client 127.0.0.1 10000

Логи при запуске

 init 
 input `ls` show command list 
>>>
I20200913 03:34:46.232203 36651 ClientService.cpp:191]  init client 
I20200913 03:34:46.232262 36651 ClientModManager.cpp:28]  register type 99
I20200913 03:34:46.232318 36653 ThreadPool.cpp:52]   ThreadPool::work()  .... 
```Клиент поддерживает автоматическое переподключение, логи переподключения:

Переподключение каждые 5 секунд после отключения, реализовано в ClientService.cpp::do_reconnect()

I20200913 03:36:19.037448 36655 ClientService.cpp:103] close id I20200913 03:36:19.037559 36655 ClientService.cpp:201] reconnect doing... I20200913 03:36:19.037601 36655 ClientService.cpp:114] ip = 127.0.0.1 port 10000 I20200913 03:36:19.037636 36655 ClientService.cpp:26] ClientService::do_connect I20200913 03:36:24.037806 36655 ClientService.cpp:31] do_connect I20200913 03:36:24.037829 36655 ClientService.cpp:44] link fail I20200913 03:36:24.037847 36655 ClientService.cpp:201] reconnect doing... I20200913 03:36:24.037858 36655 ClientService.cpp:114] ip = 127.0.0.1 port 10000 I20200913 03:36:24.037870 36655 ClientService.cpp:26] ClientService::do_connect


Просмотр поддерживаемых команд:

ls cmd : calltest1 cmd : ls


Поскольку из-за логирования ">>>" всегда будет перезаписан, вы можете вводить команды напрямую.

`cmd` соответствует действиям, выполняемым командой.

Метод регистрации находится в файле: `tests/testClient.cpp` в функции `void registerFunction()`.

Функции конкретных действий реализованы в `ClientCmdFunction`.


Выполнение тестовых методов

Лог клиента:```
>>>calltest1
>>>I20200913 03:40:56.144132 36656 testClient.cpp:82]  начало ввода
I20200913 03:40:56.144166 36655 ClientService.cpp:134]  отправка сообщения msg = ca test1
I20200913 03:40:56.144222 36655 ClientService.cpp:137]  отправка сообщения write_in_progress = 0
I20200913 03:40:56.144248 36655 ClientService.cpp:141]  вызов отправки сообщения
I20200913 03:40:56.144266 36655 ClientCmdFunction.cpp:15] semd обратный вызов статус = 0
I20200913 03:40:56.144287 36655 ClientService.cpp:156] выполнение записи 
I20200913 03:40:56.144443 36655 ClientService.cpp:172]  выполнение do_write ложь
I20200913 03:40:56.145383 36655 ClientService.cpp:85]  выполнение чтения начало 
I220200913 03:40:56.146044 36653 ClientModManager.cpp:47]  поиск регистрации типа мода = 97
I20200913 03:40:56.146811 36653 TestClientMod.cpp:19]  TestClientMod::procMessage  modtype = 99,subtype = 97,ctaskid = 0,code = 0,len = 4

Лог сервера:

I20200913 03:40:56.144481 36841 Connection.cpp:74]  recv msg [0]:ca test1
I20200913 03:40:56.144974 36847 Connection.cpp:87] TODO proc message : ca test1
I20200913 03:40:56.145028 36847 ModManager.cpp:61]  find register mod type = 99
I20200913 03:40:56.145056 36847 TestMod.cpp:23]   TestMod::procMessage client[0] subtype = a msg = test1
I20200913 03:40:56.145169 36847 Connection.cpp:108]  send [0] : ca test
I20200913 03:40:56.145200 36847 NetworkManager.cpp:87]  send success

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

Регистрация модулей

Клиентский тестовый базовый класс: BaseClientMod, клиент может изменять его в зависимости от ситуации, рекомендуется использовать модель сервера.

Серверный базовый класс модуля: BaseMod, этот класс уже определен.

Серверная накопительная система с открытыми интерфейсами, описанием типов модулей и обработкой диспетчеров.

После наследования от BaseMod:

    class BaseMod : public BaseComponent
    {
    public:
        /**
         * @brief Маркирует тип интерфейса этого модуля
         * @return
         */
        virtual ModTypeEnum mod_type() = 0;
        /**
         * @brief Обработка сообщения
         * @param hander Данные, отправленные клиентом на сервер
         * @param code Статусный код, соответствующий ответу, если не указан, по умолчанию используется Fail
         * @param resposeBody Тело ответного сообщения, отправляемое клиенту
         */
        virtual void procMessage(const HanderDataType *hander, StatusCodeTypeEnum &code, std::string &resposeBody) = 0;
```        /**
         * @brief Завершение инициализации, распределение глобального контекста
         * @param context
         */
        virtual void init_complete(Global_Context *context);

    };

Основные интерфейсы модулей:

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

Регистрация модуля

После завершения реализации модуля, он регистрируется в ModManager. В этот момент управление указателем модуля переходит к менеджеру, который затем управляет и освобождает его единым образом

Обработка пакета от клиента

При получении пакета от клиента, он направляется в метод procMessage соответствующего модуля.

Если маршрут модуля не найден, пакет будет автоматически освобожден

    struct HanderDataType
    {
        uint8_t clientid; ///< ID клиента, хранящийся на сервере
        uint8_t modtype; ///< Тип текущего бизнес-модуля
        uint8_t subtype; ///< Подтип бизнес-операции
        uint8_t ctaskid; ///< ID задачи, указанной клиентом
        std::string message;
    };

Пример базового ответного пакета:

После получения пакета, обработка сообщения осуществляется на основе бизнес-логики

Затем через globalContext->networkManager->sendMsg отправляется ответное сообщение "test" клиенту```c++ void TestMod::procMessage(std::unique_ptr hander) { LOGINFO << " TestMod::procMessage client[" << int(hander->clientid) << "] subtype = " << hander->subtype << " msg = " << hander->message; std::string message; globalContext->networkManager->sendMsg(std::move(hander), Status_SUCCESS, "test"); }




Интерфейс сети:

Передача hander в метод ответного сообщения, а также указание статуса обработки и содержимого ответного сообщения, позволяют клиенту получить ответное сообщение

class BaseNetwork { friend class Connection;

virtual int remove_client(int id) = 0;

public: /** * @brief Сервер отправляет сообщение клиенту * @param hander Объект-句柄 * @param code Статус-код * @param msg Содержимое сообщения * @param callback Функция обратного вызова для отслеживания статуса отправки * @return */ virtual int sendMsg(std::unique_ptr hander, StatusCodeTypeEnum code, const std::string &msg, std::function<void(int status)> callback = nullptr) = 0; };


Аналогично, обработка сообщений клиентом:

void TestClientMod::procMessage(std::unique_ptrGeeJoan::ResposeDataPackage hander) { LOGINFO << " TestClientMod::procMessage " << ResposeDataPackageToString(*hander); }


Лог:

I20200913 03:40:56.146811 36653 TestClientMod.cpp:19] TestClientMod::procMessage modtype = 99,subtype = 97,ctaskid = 0,code = 0,len = 4


### Пользовательские бизнес-модули:

MainServer/include/NetworkProtocol.h

Добавьте и реализуйте модуль для расширения функциональности

Максимальное количество бизнес-типов: 255, максимальное количество подмодулей: 255

// Сетевое протокол, упрощено до двух 8-битовых значений для бизнес-типов и подмодулей enum ModTypeEnum { ModType_AuthMod = 1, // ModType_TestMod = 99, // Для удобства тестирования };

    {
        OpTestModSubType_Test1 = 97, // Подмодуль для тестирования
    };

Реализация нового бизнес-модуля

Простой модуль кэширования Key-Value

KeyValueMod.h

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wujehy-server-framework-cpp.git
git@api.gitlife.ru:oschina-mirror/wujehy-server-framework-cpp.git
oschina-mirror
wujehy-server-framework-cpp
wujehy-server-framework-cpp
master