Знакомство с exsc (EXtensible Server Core). Часть 1

от автора

Приветствие

image

Всем привет! Хочу поделиться с общественностью библиотекой, на основе которой в данный момент множество серверов, обслуживают тысячи клиентов в различных серверных системах. exsc (EXtensible Server Core) — это библиотека, написанная на языке C и позволяет в рамках одного приложения, иметь один или несколько серверных потоков. Каждый серверный поток способен обслужить большое количество клиентов. Хотя библиотеку, можно использовать в модели типа запрос-ответ, в первую очередь она была рассчитана на поддержание постоянного соединения с большим количеством клиентов и обменом сообщений в реальном времени. Поскольку я и сам люблю взять готовый HelloWorld проект, скомпилировать его и посмотреть как всё работает, то в конце статьи я выложу ссылку на такой проект.

Документация

Многие операции делаются для определённого соединения. В рамках данной библиотеки за соединение отвечает структура exsc_excon. У этой структуры есть следующие поля:

ix — индекс соединения. Это порядковый номер соединения, которое было свободно в момент подключения клиента.
id — идентификатор соединения. Это уникальный номер соединения. В отличие от индекса он не повторяется.
addr — IP адрес клиента
name — имя соединения. Несколько соединений можно назвать одним именем и затем отослать какое-либо сообщение всем соединениям с одинаковым именем (смотрите функцию exsc_sendbyname).

Инициализация ядра

Для того, чтобы работать с ядром, нам необходимо его инициализировать с помощью функции

void exsc_init(int maxsrvcnt);

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

Запуск серверного потока

Далее нам нужно запустить серверный поток с помощью функции

int exsc_start(uint16_t port, int timeout, int timeframe, int recvbufsize, int concnt,                void newcon(struct exsc_excon excon),                void closecon(struct exsc_excon excon),                void recv(struct exsc_excon excon, char *buf, int bufsize),                void ext());

port — порт который будет прослушивать серверный поток.

timeout — указывает сколько времени серверный поток будет ожидать какой-либо активности от клиента. Если в течении этого времени клиент не прислал ни одного сообщения, то серверный поток закрывает такое соединение. Поэтому если мы хотим держать постоянную связь и выставили этот параметр на пример 30 секунд, то необходимо раз в 10-15 секунд присылать какое-либо сообщение типа ping.

timeframe — временные рамки, за которое мы позволяем выполнить запрос.Так на пример, если это значение выставлено на 100 миллисекунд и серверный поток обработал все текущие запросы от пользователей за 10 секунд, то он оставшиеся 90 миллисекунд оставит процессору для выполнения других задач. Таким образом, чем меньше это значение, тем быстрее серверный поток будет обрабатывать запросы, но тем больше он нагрузит процессор.

recvbufsize — размер буфера который серверный поток будет вычитывать за один раз.

concnt — максимальное количество соединений с которым серверный поток работает одновременно.

newcon — сallback функция, которая будет отрабатывать каждый раз когда будет подключаться новый клиент. В параметры этой функции будет передаваться соединение клиента который подключился.

closecon — сallback функция, которая будет отрабатывать каждый раз когда будет закрываться неактивное подключение. В параметры этой функции будет передаваться соединение клиента который отключился.

recv — сallback функция, которая будет вызываться когда клиент будет присылать пакеты. В параметры этой функции будет передаваться соединение клиента от которого пришли данные, указатель на данные и размер буфера с данными.

ext — сallback функция, которая будет вызываться каждый проход цикла серверного потока. Эта функция сделана для расширения функционала ядра. На пример сюда можно подвязать обработку таймеров.

Функция exsc_start возвращает дескриптор серверного потока, который понадобится для вызова некоторых функций.

Отправка сообщений

За отправку сообщений отвечает функция

void exsc_send(int des, struct exsc_excon *excon, char *buf, int bufsize);

Эта функция потокобезопасная (её можно вызывать её из любого потока). В качестве параметров необходимо передать ей дескриптор серверного потока (который мы получили как возвращаемое значение функции exsc_start), соединение на которое мы хотим отправить сообщение, указатель на буфер с сообщением и размер буфера.

Так же мы имеем возможность отправить сообщение группе клиентов. Для этого есть функция

void exsc_sendbyname(int des, char *conname, char *buf, int bufsize);

Она аналогична функции exsc_send, за исключением второго параметра, в который передаётся имя подключений которым будет отправлено сообщение.

Задание имени подключения

Для тог, чтобы в будущем как то дополнительно идентифицировать подключение, либо хранить вместе с подключением некоторую информацию о нём, либо отправлять сообщения группе клиентов, используется функция

void exsc_setconname(int des, struct exsc_excon *excon, char *name);

Эта функция потокобезопасная. В качестве первого параметра передаётся дескриптор серверного потока, вторым параметром передаём само подключение и третьим параметром передаём имя этого подключения.

Подключение серверного потока к другому серверу

Иногда, серверная логика требует того, чтобы подключиться к другому серверу, для того чтобы запросить или передать какие либо данные. Для таких задач была введена функция, которая создаёт такое подключение.

void exsc_connect(int des, const char *addr, uint16_t port, struct exsc_excon *excon);

Эта функция потокобезопасная. В качестве параметров нам необходимо передать дескриптор серверного потока, адресс сервера к которому нам необходимо подключиться, потр сервера к которому нам необходимо подключиться и последним параметром мы передаём указатель на соединение с помощью которого мы в дальнейшем сможем вызывать другие функции библиотеки. Стоит отметить что нам нет необходимости дожидаться, пока подключение состоится. Мы можем вызвать функции exsc_connect и exsc_send одну за другой и система сама проследит за тем, чтобы сообщение было отослано сразу после того как сможет подключиться к удалённому серверу.

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

#include <stdio.h>  // fgets #include <string.h> // strcmp #include "../exnetwork/exsc.h" // подключаем исходники ядра  int g_des; // дескриптор серверного потока  // ловим новое подключение void exsc_newcon(struct exsc_excon con) {     printf("the connection was open  %s\n", con.addr); }  // подключение закрывается void exsc_closecon(struct exsc_excon con) {     printf("the connection was closed  %s\n", con.addr); }  // принимаем сообщение от клиента void exsc_recv(struct exsc_excon con, char *buf, int bufsize) {     char msg[512] = { 0 };     memcpy(msg, buf, bufsize);     printf("receive data from %s\n%s\n", con.addr, msg);      if (strcmp(msg, "say hello") == 0)     {         strcpy(msg, "hello");         exsc_send(g_des, &con, msg, strlen(msg));     } }  void exsc_ext() { }  int main() {     printf("server_test_0 is started\n");      exsc_init(2); // инициализируем ядро с расчётом на два серверных потока      // запускаем серверный поток на порту 7777     // он будет держать неактивные соединения 30 секунд     // будет обрабатывать входящие сообщения в рамках 10 миллисекунд     // размер буфера для приёма задаём 1024 байта     // максимальное количество подключений ограничиваем до 10000     g_des = exsc_start(7777, 30, 10, 1024, 10000, exsc_newcon, exsc_closecon, exsc_recv, exsc_ext);      // тормозим главный поток, чтобы программа не завершилась раньше времени     // программа завершится когда мы введём команду exit и нажмём клавишу ENTER     while (1)     {         const int cmdmaxlen = 256;         char cmd[cmdmaxlen];         fgets(cmd, cmdmaxlen, stdin);         if (strcmp(cmd, "exit\n") == 0)         {             break;         }     }     return 0; } 

Заключение

Ядро exsc осуществляет только низкий уровень взоимодействия с клиентами. Хоть это и самый важный элемент серверной системы, основа на которой всё строится, помимо него нужно строить более верхние уровни, которые будут отвечать за управление подключений, генерирование сообщений, сборку сообщений (которые вероятно будут приходить за несколько этапов). Если статья будет иметь положительный отклик — то я напишу вторую чать, в которой разовью тему более верхнего уровня этой библиотеки написанной с использованием Qt, а именно про класс ExServer.

Ссылка на библиотеку

Пример расположен в архиве exsc_test_0.zip

ссылка на оригинал статьи https://habr.com/ru/post/545838/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *