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 )