started v.0.3.3 - more functionality for sockets

This commit is contained in:
Edward Emelianov 2025-06-27 11:59:26 +03:00
parent df1f2193ee
commit a445aec8da
13 changed files with 428 additions and 109 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.9) cmake_minimum_required(VERSION 4.0)
set(PROJ usefull_macros) set(PROJ usefull_macros)
set(MINOR_VERSION "2") set(MINOR_VERSION "3")
set(MID_VERSION "3") set(MID_VERSION "3")
set(MAJOR_VERSION "0") set(MAJOR_VERSION "0")
set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}") set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}")

View File

@ -1,10 +1,4 @@
Wed Dec 23 17:22:39 MSK 2020 Dec 16 2024
VERSION 0.1.1:
add tty_timeout()
fix read_tty() for disconnect
add logging
add sl_libversion()
VERSION 0.2.1: VERSION 0.2.1:
RENAME: RENAME:
@ -54,3 +48,11 @@ Remove "bool" type (change to int)
FIXED bug with several only long options with same shortopt mark (like 0) FIXED bug with several only long options with same shortopt mark (like 0)
Add simple config files, so you can move some of your parameters settings to it Add simple config files, so you can move some of your parameters settings to it
Wed Dec 23 17:22:39 MSK 2020
VERSION 0.1.1:
add tty_timeout()
fix read_tty() for disconnect
add logging
add sl_libversion()

37
Readme.rus Normal file
View File

@ -0,0 +1,37 @@
<a href=https://github.com/eddyem/snippets_library>Репозиторий с кодом</a>.
Сначала избавился от нескольких багов. Потом решил добавить сетевой функционал. Ну, а коль сеть — то и кольцевой буфер нужен. В общем, теперь там — не просто макросы и функционал, который в 99% случаев нужен (тот же разбор параметров командной строки), но и дополнительные облегчалки.
<lj-cut text="Подробней">
В релизе 0.2.1 код подвергся рефакторингу, многие функции переименованы, чтобы было единообразие: почти все функции и т.п. имеют префикс <tt>sl_</tt>; типы-перечисления имеют суффикс <tt>_e</tt>, типы данных — суффикс <tt>_t</tt>. Без префикса я оставил лишь функции <tt>red</tt> и <tt>green</tt> для цветного вывода сообщений. И многие макросы лень было переименовывать.
<p><b>"Старое":</b></p>
Для использования функционала в самом начале <tt>main()</tt> нужно выполнить функцию <tt>sl_init</tt>. Для работы с терминалом ввода в режиме "без эха" — <tt>sl_setup_con()</tt> (и не забыть в конце и в функции <tt>signals()</tt> вызвать <tt>sl_restore_con()</tt>, иначе по завершению придется ручками вводить <tt>reset</tt>). Функции <tt>sl_read_con()</tt> и <tt>sl_getchar()</tt> дают, соответственно, неблокирующее и блокирующее чтение символа, введенного пользователем.
Осталось и упрощение работы с <tt>mmap</tt>: <tt>sl_mmap()</tt> и <tt>sl_munmap()</tt>.
<tt>sl_random_seed()</tt> позволяет при помощи <tt>/dev/random</tt> сгенерировать случайную затравку для использования в ГСЧ. <tt>sl_libversion()</tt> позволяет получить строку с текущей версией библиотеки.
Для работы с gettext теперь нужно определить макрос <tt>GETTEXT</tt>, в этом случае макрос <tt>_()</tt> будет вызывать <tt>gettext()</tt>.
Функции работы с блочными устройствами не просто переименовались: по умолчанию я перешел на termios2. Если нужно собрать библиотеку с поддержкой "классической" termios со всеми ее убогими косяками, нужно определить макрос <tt>SL_USE_OLD_TTY</tt> (в этом случае еще и будет доступна функция <tt>sl_tty_convspd()</tt> для преобразования скорости интерфейса из числа в макрос). А остальные функции — как и были: <tt>sl_tty_new()</tt> для выделения памяти и инициализации структур; <tt>sl_tty_open()</tt> — открыть порт; <tt>sl_tty_read()</tt> и <tt>sl_tty_write()</tt> для чтения/записи; <tt>sl_tty_tmout()</tt> позволяет задать таймаут для <tt>select()</tt> в микросекундах. <tt>sl_tty_close()</tt> — закрыть устройство и очистить структуры данных.
Логгирование имеет "умолчальный" глобальный лог (<tt>sl_globlog</tt>), для которого нарисованы макросы вроде <tt>LOGERR()</tt>, <tt>LOGERRADD()</tt> и т.п. (все с суффиксом <tt>ADD</tt> добавляют текст без записи timestamp и уровня сообщения — если нужно вывести несколько строк текста; но лучше ими не пользоваться часто, особенно в многопоточных/многопроцессных приложениях). Чтобы логи писались вменяемо в многопоточных/многопроцессных, пришлось лог-файл каждый раз для записи открывать, блокировать, потом писать, закрывать и разблокировать. Чтобы избежать дедлоков или слишком долгих ожиданий, установлен "гвоздями прибитый" таймаут в 0.1с: если за это время заблокировать файл не получится, то отменяем запись и возвращаем пользователю 0; иначе возвращаем количество записанных символов. "Глобальный" лог-файл активируется при помощи макроса <tt>OPENLOG(nm, lvl, prefix)</tt> (имя, уровень, флаг добавления "префикса": строчки с расшифровкой уровня сообщения, например, [WARN] или [ERR]). Функции <tt>sl_createlog()</tt>, <tt>sl_deletelog()</tt> и <tt>sl_putlogt()</tt> позволяют активировать структуру-лог (и создать файл, если надо), удалить структуру (сам файл не удаляется) и что-нибудь туда записать/дописать.
В работе с аргументами командной строки я исправил один баг, связанный с выводом справки по "только длинным" аргументам, у которых в качестве "короткой альтернативы" проставлены одинаковые числа. Ну и была проблема с опцией "-?", которая криво обрабатывалась (т.к. я пользуюсь <tt>getopt_long</tt>). В остальном все осталось, как было: поддерживаются целочисленные аргументы (причем, arg_none означает, что к заданной переменной будет добавляться 1 каждый раз, как встречается этот флаг — удобно для задания уровня отображения как, например, <tt>-vvvv</tt>), long long, double, float, string и "аргумент-функция" (т.е. вместо присвоения значения будет выполняться заданная функция — например, чтобы парсить свои вложенные аргументы). Все также для каждого флага можно указать, что он не имеет аргументов, имеет обязательный, необязательный или множественный (в этом случае переменной-целью должен быть указатель соответствующего типа: при разборе получится NULL-terminated массив со всеми введенными пользователем данными (удобно, например, когда тебе нужно указать несколько файлов для считывания из них однообразной информации и т.п.). Поддерживаются "подопции" (когда за флагом следует строка вида "so1=par1;so2=par2..."). <tt>sl_showhelp()</tt> позволяет вызвать справку по всем опциям или только одной. Она же вызывается автоматом, если пользователь что-то не то введет. Чтобы поменять форматную строку справки, используется <tt>sl_helpstring()</tt> (там должен быть один-единственный "%s", куда вставится имя запускаемого файла). <tt>sl_parseargs()</tt> нужно запускать сразу после инициализации, и дальше уже пользоваться своей структурой с параметрами.
Для проверки, не запущен ли второй экземпляр, есть функция <tt>sl_check4running()</tt>. Если найден процесс, запускается слабая функция <tt>sl_iffound_deflt()</tt>, которую под свои нужды можно изменить. <tt>sl_getPSname()</tt> парсит <tt>/proc</tt> и выдает имя процесса с заданным PID.
FIFO/LIFO также остались без изменений: <tt>sl_list_push()</tt> и <tt>sl_list_push_tail()</tt> позволяют воткнуть запись в начало или хвост списка, а <tt>sl_list_pop()</tt> — извлечь последнюю запись.
<p><b>"Новое":</b></p>
<tt>sl_mem_avail()</tt> позволяет определить доступный объем ОЗУ. <tt>sl_omitspaces()</tt> и <tt>sl_omitspacesr()</tt> — удалить начальные и конечные пробелы. Помимо <tt>sl_str2d()</tt> ("безопасное" преобразование строки в double) я добавил <tt>sl_str2ll()</tt> и <tt>sl_str2i()</tt> — long long и int, соответственно.
Для проверки, можно ли в данный дескриптор писать или читать из него без блокировки, добавил функции <tt>sl_canread()</tt> и <tt>sl_canwrite()</tt>.
Поддержку конфигурационных файлов тоже добавил в библиотеку. В файле все данные должны быть в формате "параметр = значение" или просто "параметр". Допускается наличие пустых строк и комментариев (все, что за символом "#" в строке). "Гвоздями" прибил максимальную длину параметра (<tt>SL_KEY_LEN</tt>, 32 байта) и значения (<tt>SL_VAL_LEN</tt>, 128 байт). Работа с флагами похожа на работу с аргументами командной строки. <tt>sl_get_keyval()</tt> чистит лишние пробелы и комментарии, выделяя непосредственно строчку "параметр [= значение]" и помещая ее в отдельные аргументы (возвращает 0 если ничего в строке не найдено, 1 — если только параметр, 2 — если и параметр, и значение); эта функция используется и в обработке запросов по сети. <tt>sl_print_opts()</tt> печатает изменяемые параметры (или все, если задан флаг) с комментариями. <tt>sl_conf_readopts()</tt> читает конфигурационный файл. Опции задаются полностью аналогично опциям командной строки, что позволяет печатать сопровождающий текст помимо флага и его значения.
Для работы с кольцевым буфером есть следующие функции: <tt>sl_RB_new()</tt> и <tt>sl_RB_delete()</tt> — инициализация и удаление данных структуры кольцевого буфера. <tt>sl_RB_hasbyte()</tt> позволяет определить, есть ли в буфере нужный символ (например, конец строки), а <tt>sl_RB_readto()</tt> — считать до него (включительно). <tt>sl_RB_putbyte()</tt>, <tt>sl_RB_write()</tt> и <tt>sl_RB_writestr()</tt> позволяют записать данные в буфер (возвращают количество записанных символов, чтобы юзер мог понять, что что-то не то, если место кончилось). <tt>sl_RB_datalen()</tt> и <tt>sl_RB_freesize()</tt> позволяют узнать, сколько места в буфере занимают данные, и сколько там свободно. Нужно помнить, что размер данных будет на 1 байт меньше длины буфера (по понятным причинам). <tt>sl_RB_clearbuf()</tt> сбрасывает содержимое буфера, как будто бы там ничего нет.
Ну и последняя, самая жирная по количеству функций и структур — сетевая часть.
Для активации соединения есть две функции: <tt>sl_sock_run_client()</tt> и <tt>sl_sock_run_server()</tt> — для клиента и сервера, соответственно. Они инициализируют структуры, открывают соединение и подключаются для клиента или вызывают bind для сервера. Клиенту заодно запускается поток, читающий приходящие данные и складывающий в кольцевой буфер. У сервера, если указать не-NULL аргумент handlers, тоже запускается отдельный поток: он обслуживает присоединенных клиентов, запускает accept для новых, закрывает отключившиеся; полученные от клиентов данные парсятся и, если среди переданной структуры handlers находится подобный ключ, то запускается соответствующий обработчик + есть три стандартных обработчика: <tt>sl_sock_inthandler()</tt>, <tt>sl_sock_dblhandler()</tt> и <tt>sl_sock_strhandler()</tt> — для int64_t, double и char* (для того, чтобы знать, когда было произведено изменение значения, я дополнительные типы данных ввел, где помимо данных есть timestamp). Предельное количество клиентов можно задать функцией <tt>sl_sock_changemaxclients()</tt>, а узнать текущее — <tt>sl_sock_getmaxclients()</tt>. <tt>sl_sock_delete()</tt> закрывает соединение и удаляет структуру.
Помимо этого, есть еще обработчики событий: подключение клиента, отключение и подключение клиента сверх предельного количества. По умолчанию там NULL (т.е. ничего не делается), но можно сменить при помощи, соответственно, функций <tt>sl_sock_connhandler()</tt>, <tt>sl_sock_dischandler()</tt> и <tt>sl_sock_maxclhandler()</tt>. В них можно, например, в логи писать, мол клиент fd=xx ip=yyy подключился/отключился. А в последнем — писать клиенту "подожди, все соединения заняты".
Отправлять данные можно непосредственно при помощи write/send (не забывая сначала заблокировать, а потом разблокировать соответствующий мьютекс), либо же при помощи оберток: <tt>sl_sock_sendbyte()</tt>, <tt>sl_sock_sendbinmessage()</tt> и <tt>sl_sock_sendstrmessage()</tt>. Читать тоже можно или при помощи соответствующих функций работы с кольцевым буфером, или же текстовые строковые сообщения можно считывать посредством <tt>sl_sock_readline()</tt>.
У сервера бывают ситуации, когда одно и то же сообщение нужно отправить сразу всем клиентам. Для этого есть функция <tt>sl_sock_sendall()</tt> (она тупо перебирает все открытые и подключенные дескрипторы, да отправляет туда данные).
<p><b>Примеры</b></p>
<i>clientserver</i> — пример организации простейшего сервера и клиента. 226 строк всего: аргументы командной строки, примеры обработчиков, пример неблокирующего чтения с клавиатуры одновременно с чтением из сокета. Здесь я не парился насчет типа "локальный сетевой": можно открыть либо UNIX-сокет, либо INET. Заодно логи можно писать.
<i>conffile</i> — 101 строка. Дает пример ввода конфигурационных параметров как из командной строки, так и из конфигурационного файла. Также парсит строковый параметр вида "параметр=значение". Отображает состояние флагов после обработки аргументов командной строки, а затем — после считывания из конфигурационного файла. К сожалению, чтобы считать путь к конфигурационному файлу, надо сначала обработать аргументы командной строки, поэтому, если не постараться, а просто на тот же самый массив структур вызывать чтение конф-файла, то приоритет будет за ним. Хотя, логика подсказывает, что приоритет всегда должен быть за командной строкой. Поэтому в случае, когда необходимо именно иметь приоритет командной строки, нужно будет завести 2 раздельных структуры, инициализировать их сначала каким-то дефолтом (с данными, которые пользователь точно не соможет ввести), а после сравнить и все неинициализированное из командной строки инициализировать записями из конф-файла.
<i>fifo</i> — 61 строка. Пример работы с FIFO/LIFO. Показывает реальный размер свободной ОЗУ и затем все параметры командной строки помещает в списки: LIFO и FIFO. А затем из каждого списка по одному извлекает, демонстрируя, как это работает.
<i>helloworld</i> — 35 строк (из которых только 10 приходится на собственно код). Демонстрирует цветной вывод и ввод без эха.
<i>options</i> — 134 строки. Демонстрирует работу с разным типом опций (кроме функции: я и сам уже забыл, зачем оно мне нужно было), в т.ч. массивами. Ну и по умолчанию демонстрирует простейший терминал для работы с символьными устройствами (если пользователь введет путь).
<i>ringbuffer</i> — 80 строк, демонстрирующих работу кольцевого буфера. По умолчанию он имеет размер 1024 байт (т.е. вмещает 1023 символа). Дальше пользователю предлагается вводить построчно текст, который будет помещаться в буфер. Ввод заканчивается на переполнении буфера или же при нажатии ctrl+D. После чего весь буфер вычитывается и выводится на консоль.
</lj-cut>
Теперь как минимум все, что пользуется этой библиотекой, надо обновить. Ну, а сетевые демоны роботелескопов вообще радикально так переделать: чтобы "шапкой" с метеоданными не демон монтировки занимался (что вообще нелогично), а демон погоды; чтобы демоны купола, монтировки и телескопа "слушались" демона погоды. Скажем, если стоит prohibited=1, то прекращать наблюдения, когда они идут, или не давать наблюдателю открыться. Вот на БТА такую функцию АСУшник выполняет (правда, некоторые из рук вон плохо работают, позволяя наблюдателям-вредителям открывать телескоп при влажности свыше 90%, облачности или даже тумане). А на Ц-1000 почему-то до сих пор нет такого "супердемона", который бы "давал по шапке" наблюдателям-вредителям. Иначе угробят телескоп, а за это даже никакого наказания не понесут (я бы отстранял наблюдателей минимум на полгода от наблюдений, если они открывают телескоп тогда, когда этого делать ни в коем случае нельзя).

View File

@ -126,7 +126,7 @@ static sl_sock_string_t sflag = {0};
static sl_sock_hresult_e dtimeh(sl_sock_t _U_ *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){ static sl_sock_hresult_e dtimeh(sl_sock_t _U_ *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){
char buf[32]; char buf[32];
snprintf(buf, 31, "UNIXT=%.2f\n", sl_dtime()); snprintf(buf, 31, "UNIXT=%.2f\n", sl_dtime());
sl_sock_sendall((uint8_t*)buf, strlen(buf)); sl_sock_sendall(s, (uint8_t*)buf, strlen(buf));
return RESULT_SILENCE; return RESULT_SILENCE;
} }
@ -158,16 +158,24 @@ static void toomuch(int fd){
DBG("Disc after %gs", sl_dtime() - t0); DBG("Disc after %gs", sl_dtime() - t0);
LOGWARN("Client fd=%d tried to connect after MAX reached", fd); LOGWARN("Client fd=%d tried to connect after MAX reached", fd);
} }
// new connections handler // new connections handler (return FALSE to reject client)
static void connected(sl_sock_t *c){ static int connected(sl_sock_t *c){
if(c->type == SOCKT_UNIX) LOGMSG("New client fd=%d connected", c->fd); if(c->type == SOCKT_UNIX) LOGMSG("New client fd=%d connected", c->fd);
else LOGMSG("New client fd=%d, IP=%s connected", c->fd, c->IP); else LOGMSG("New client fd=%d, IP=%s connected", c->fd, c->IP);
return TRUE;
} }
// disconnected handler // disconnected handler
static void disconnected(sl_sock_t *c){ static void disconnected(sl_sock_t *c){
if(c->type == SOCKT_UNIX) LOGMSG("Disconnected client fd=%d", c->fd); if(c->type == SOCKT_UNIX) LOGMSG("Disconnected client fd=%d", c->fd);
else LOGMSG("Disconnected client fd=%d, IP=%s", c->fd, c->IP); else LOGMSG("Disconnected client fd=%d, IP=%s", c->fd, c->IP);
} }
static sl_sock_hresult_e defhandler(struct sl_sock *s, const char *str){
if(!s || !str) return RESULT_FAIL;
sl_sock_sendstrmessage(s, "You entered wrong command:\n```\n");
sl_sock_sendstrmessage(s, str);
sl_sock_sendstrmessage(s, "\n```\nTry \"help\"\n");
return RESULT_SILENCE;
}
static sl_sock_hitem_t handlers[] = { static sl_sock_hitem_t handlers[] = {
{sl_sock_inthandler, "int", "set/get integer flag", (void*)&iflag}, {sl_sock_inthandler, "int", "set/get integer flag", (void*)&iflag},
@ -185,16 +193,19 @@ int main(int argc, char **argv){
if(!G.node) ERRX("Point node"); if(!G.node) ERRX("Point node");
sl_socktype_e type = (G.isunix) ? SOCKT_UNIX : SOCKT_NET; sl_socktype_e type = (G.isunix) ? SOCKT_UNIX : SOCKT_NET;
if(G.isserver){ if(G.isserver){
sl_sock_changemaxclients(G.maxclients);
sl_sock_maxclhandler(toomuch);
sl_sock_connhandler(connected);
sl_sock_dischandler(disconnected);
s = sl_sock_run_server(type, G.node, -1, handlers); s = sl_sock_run_server(type, G.node, -1, handlers);
} else { } else {
sl_setup_con(); sl_setup_con();
s = sl_sock_run_client(type, G.node, -1); s = sl_sock_run_client(type, G.node, -1);
} }
if(!s) ERRX("Can't create socket and/or run threads"); if(!s) ERRX("Can't create socket and/or run threads");
if(G.isserver){
sl_sock_changemaxclients(s, G.maxclients);
sl_sock_maxclhandler(s, toomuch);
sl_sock_connhandler(s, connected);
sl_sock_dischandler(s, disconnected);
sl_sock_defmsghandler(s, defhandler);
}
sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR; sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR;
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1; if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
if(G.logfile) OPENLOG(G.logfile, lvl, 1); if(G.logfile) OPENLOG(G.logfile, lvl, 1);

View File

@ -25,7 +25,7 @@
int main(int argc, char *argv[argc]) { int main(int argc, char *argv[argc]) {
sl_list_t *f = NULL; sl_list_t *f = NULL;
printf("Available memory: %luMB\n", sl_mem_avail()/1024/1024); printf("Available memory: %luMB\n", sl_mem_avail()/1024/1024);
//initial_setup(); - there's no need for this function if you don't use locale & don't want to have sl_init();
// specific output in non-tty // specific output in non-tty
if(argc == 1){ if(argc == 1){
green("Usage:\n\t%s args - fill fifo with arguments\n", __progname); green("Usage:\n\t%s args - fill fifo with arguments\n", __progname);
@ -55,7 +55,6 @@ int main(int argc, char *argv[argc]) {
d = sl_list_pop(&f); d = sl_list_pop(&f);
green("pull: "); green("pull: ");
printf("%s\n", d); printf("%s\n", d);
// after last usage we should FREE data, but here it is parameter of main()
} }
return 0; return 0;
} }

View File

@ -24,7 +24,7 @@
/** /**
* @brief sl_list_push_tail - push data into the tail of a stack (like FIFO) * @brief sl_list_push_tail - push data into the tail of a stack (like FIFO)
* @param lst (io) - list * @param lst (io) - list
* @param v (i) - data to push * @param v (i) - data to push (DON'T FREE it after this function as it would be just a link to original!)
* @return pointer to just pused node or NULL in case of error * @return pointer to just pused node or NULL in case of error
*/ */
sl_list_t *sl_list_push_tail(sl_list_t **lst, void *v){ sl_list_t *sl_list_push_tail(sl_list_t **lst, void *v){

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-16 11:25+0300\n" "POT-Creation-Date: 2025-06-27 11:51+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -105,38 +105,39 @@ msgstr ""
msgid "Wrong argument \"%s\" of parameter \"%s\"" msgid "Wrong argument \"%s\" of parameter \"%s\""
msgstr "" msgstr ""
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:148 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:147
msgid "Server disconnected" msgid "Server disconnected"
msgstr "" msgstr ""
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:223 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:390
msgid "Can't start server handlers thread" msgid "Can't start server handlers thread"
msgstr "" msgstr ""
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:285 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:459
msgid "Limit of connections reached" msgid "Limit of connections reached"
msgstr "" msgstr ""
#. check for RB overflow #. check for RB overflow
#. -1 - buffer empty (can't be), -2 - buffer overflow #. -1 - buffer empty (can't be), -2 - buffer overflow
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:325 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:503
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:504
#, c-format #, c-format
msgid "Server thread: ring buffer overflow for fd=%d" msgid "Server thread: ring buffer overflow for fd=%d"
msgstr "" msgstr ""
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:339 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:518
#, c-format #, c-format
msgid "Server thread: can't write data to ringbuffer: overflow from fd=%d" msgid "Server thread: can't write data to ringbuffer: overflow from fd=%d"
msgstr "" msgstr ""
#. buffer overflow #. buffer overflow
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:351 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:530
#, c-format #, c-format
msgid "Server thread: buffer overflow from fd=%d" msgid "Server thread: buffer overflow from fd=%d"
msgstr "" msgstr ""
#. never reached #. never reached
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:416 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:620
#, c-format #, c-format
msgid "Unsupported socket type %d" msgid "Unsupported socket type %d"
msgstr "" msgstr ""

View File

@ -7,7 +7,7 @@
msgid "" msgid ""
msgstr "Project-Id-Version: PACKAGE VERSION\n" msgstr "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-13 09:10+0300\n" "POT-Creation-Date: 2025-06-27 11:49+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -85,7 +85,7 @@ msgstr "
msgid "Can't setup console" msgid "Can't setup console"
msgstr "îÅ ÍÏÇÕ ÎÁÓÔÒÏÉÔØ ËÏÎÓÏÌØ" msgstr "îÅ ÍÏÇÕ ÎÁÓÔÒÏÉÔØ ËÏÎÓÏÌØ"
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:223 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:390
msgid "Can't start server handlers thread" msgid "Can't start server handlers thread"
msgstr "îÅ ÍÏÇÕ ÚÁÐÕÓÔÉÔØ ÐÏÔÏË-ÏÂÒÁÂÏÔÞÉË ÓÅÒ×ÅÒÁ" msgstr "îÅ ÍÏÇÕ ÚÁÐÕÓÔÉÔØ ÐÏÔÏË-ÏÂÒÁÂÏÔÞÉË ÓÅÒ×ÅÒÁ"
@ -117,7 +117,7 @@ msgstr "
msgid "Key '%s' need value" msgid "Key '%s' need value"
msgstr "ëÌÀÞ '%s' ÔÒÅÂÕÅÔ ÚÎÁÞÅÎÉÑ" msgstr "ëÌÀÞ '%s' ÔÒÅÂÕÅÔ ÚÎÁÞÅÎÉÑ"
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:285 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:459
msgid "Limit of connections reached" msgid "Limit of connections reached"
msgstr "äÏÓÔÉÇÎÕÔ ÐÒÅÄÅÌ ÓÏÅÄÉÎÅÎÉÊ" msgstr "äÏÓÔÉÇÎÕÔ ÐÒÅÄÅÌ ÓÏÅÄÉÎÅÎÉÊ"
@ -146,20 +146,27 @@ msgstr "
msgid "Port name is missing" msgid "Port name is missing"
msgstr "ïÔÓÕÔÓÔ×ÕÅÔ ÉÍÑ ÐÏÒÔÁ" msgstr "ïÔÓÕÔÓÔ×ÕÅÔ ÉÍÑ ÐÏÒÔÁ"
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:148 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:147
msgid "Server disconnected" msgid "Server disconnected"
msgstr "óÅÒ×ÅÒ ÏÔËÌÀÞÅÎ" msgstr "óÅÒ×ÅÒ ÏÔËÌÀÞÅÎ"
#. buffer overflow #. buffer overflow
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:351 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:530
#, c-format
msgid "Server thread: buffer overflow from fd=%d" msgid "Server thread: buffer overflow from fd=%d"
msgstr "ðÏÔÏË ÓÅÒ×ÅÒÁ: ÐÅÒÅÐÏÌÎÅÎÉÅ ÂÕÆÅÒÁ ÏÔ fd=%d" msgstr "ðÏÔÏË ÓÅÒ×ÅÒÁ: ÐÅÒÅÐÏÌÎÅÎÉÅ ÂÕÆÅÒÁ ÏÔ fd=%d"
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:339 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:518
msgid "Server thread: can't write data to ringbuffer, overflow from fd=%d" #, fuzzy, c-format
msgstr "ðÏÔÏË ÓÅÒ×ÅÒÁ: ÎÅ ÍÏÇÕ ÓÏÈÒÁÎÉÔØ ÄÁÎÎÙÅ × ËÏÌØÃÅ×ÏÍ ÂÕÆÅÒÅ, ÐÅÒÅÐÏÌÎÅÎÉÅ ÏÔ fd=%d" msgid "Server thread: can't write data to ringbuffer: overflow from fd=%d"
msgstr "ðÏÔÏË ÓÅÒ×ÅÒÁ: ÎÅ ÍÏÇÕ ÓÏÈÒÁÎÉÔØ ÄÁÎÎÙÅ × ËÏÌØÃÅ×ÏÍ ÂÕÆÅÒÅ, "
"ÐÅÒÅÐÏÌÎÅÎÉÅ ÏÔ fd=%d"
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:325 #. check for RB overflow
#. -1 - buffer empty (can't be), -2 - buffer overflow
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:503
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:504
#, c-format
msgid "Server thread: ring buffer overflow for fd=%d" msgid "Server thread: ring buffer overflow for fd=%d"
msgstr "ðÏÔÏË ÓÅÒ×ÅÒÁ: ÐÅÒÅÐÏÌÎÅÎÉÅ ÂÕÆÅÒÁ ÏÔ fd=%d" msgstr "ðÏÔÏË ÓÅÒ×ÅÒÁ: ÐÅÒÅÐÏÌÎÅÎÉÅ ÂÕÆÅÒÁ ÏÔ fd=%d"
@ -168,7 +175,7 @@ msgid "Unsupported option type"
msgstr "îÅÐÏÄÄÅÒÖÉ×ÁÅÍÙÊ ÔÉÐ ÏÐÃÉÉ" msgstr "îÅÐÏÄÄÅÒÖÉ×ÁÅÍÙÊ ÔÉÐ ÏÐÃÉÉ"
#. never reached #. never reached
#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:416 #: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:620
#, c-format #, c-format
msgid "Unsupported socket type %d" msgid "Unsupported socket type %d"
msgstr "îÅÐÏÄÄÅÒÖÉ×ÁÅÍÙÊ ÔÉÐ ÓÏËÅÔÁ %d" msgstr "îÅÐÏÄÄÅÒÖÉ×ÁÅÍÙÊ ÔÉÐ ÓÏËÅÔÁ %d"

View File

@ -82,6 +82,9 @@ size_t sl_RB_freesize(sl_ringbuffer_t *b){
static ssize_t hasbyte(sl_ringbuffer_t *b, uint8_t byte){ static ssize_t hasbyte(sl_ringbuffer_t *b, uint8_t byte){
if(b->head == b->tail) return -1; // no data in buffer if(b->head == b->tail) return -1; // no data in buffer
size_t startidx = b->head; size_t startidx = b->head;
/*DBG("head: %zd, tail: %zd (%c %c %c %c), search %02x",
b->head, b->tail, b->data[startidx], b->data[startidx+1],
b->data[startidx+2], b->data[startidx+3], byte);*/
if(b->head > b->tail){ if(b->head > b->tail){
for(size_t found = b->head; found < b->length; ++found) for(size_t found = b->head; found < b->length; ++found)
if(b->data[found] == byte) return found; if(b->data[found] == byte) return found;
@ -222,6 +225,13 @@ size_t sl_RB_write(sl_ringbuffer_t *b, const uint8_t *str, size_t len){
DBG("rest: %zd, need: %zd", r, len); DBG("rest: %zd, need: %zd", r, len);
if(len > r) len = r; if(len > r) len = r;
if(!len) goto ret; if(!len) goto ret;
/*green("buf:\n");
for(size_t i = 0; i < len; ++i){
char c = (char)str[i];
if(c > 31) printf("%c", c);
else printf("\\x%02x", c);
}
green("(end)\n");*/
size_t _1st = b->length - b->tail; size_t _1st = b->length - b->tail;
if(_1st > len) _1st = len; if(_1st > len) _1st = len;
memcpy(b->data + b->tail, str, _1st); memcpy(b->data + b->tail, str, _1st);

326
socket.c
View File

@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <ctype.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netdb.h> #include <netdb.h>
#include <poll.h> #include <poll.h>
@ -29,37 +30,35 @@
#include "usefull_macros.h" #include "usefull_macros.h"
// max clients amount
static int maxclients = 32;
// too much clients handler; it is running for client connected with number>maxclients (before closing its fd)
static sl_sock_maxclh_t toomuch_handler = NULL;
// new client connected handler; it will be run each new connection
static sl_sock_connh_t newconnect_handler = NULL;
// client disconnected handler
static sl_sock_disch_t disconnect_handler = NULL;
/** /**
* @brief sl_sock_changemaxclients - change amount of max simultaneously connected clients * @brief sl_sock_changemaxclients - change amount of max simultaneously connected clients
* SHOULD BE run BEFORE running of server * SHOULD BE run BEFORE running of server
* @param val - maximal clients number * @param val - maximal clients number
*/ */
void sl_sock_changemaxclients(int val){ void sl_sock_changemaxclients(sl_sock_t *sock, int val){
maxclients = val; if(sock) sock->maxclients = val;
}
int sl_sock_getmaxclients(sl_sock_t *sock){
if(sock) return sock->maxclients;
else return -1;
} }
int sl_sock_getmaxclients(){ return maxclients; }
// in each next function changed default handlers you can set h to NULL to remove your handler // in each next function changed default handlers you can set h to NULL to remove your handler
// setter of "too much clients" handler // setter of "too much clients" handler
void sl_sock_maxclhandler(sl_sock_maxclh_t h){ void sl_sock_maxclhandler(sl_sock_t *sock, void (*h)(int)){
toomuch_handler = h; if(sock) sock->toomuch_handler = h;
} }
// setter of "new client connected" handler // setter of "new client connected" handler
void sl_sock_connhandler(sl_sock_connh_t h){ void sl_sock_connhandler(sl_sock_t *sock, int(*h)(struct sl_sock*)){
newconnect_handler = h; if(sock) sock->newconnect_handler = h;
} }
// setter of "client disconnected" handler // setter of "client disconnected" handler
void sl_sock_dischandler(sl_sock_disch_t h){ void sl_sock_dischandler(sl_sock_t *sock, void(*h)(struct sl_sock*)){
disconnect_handler = h; if(sock) sock->disconnect_handler = h;
}
// setter of default client message handler
void sl_sock_defmsghandler(struct sl_sock *sock, sl_sock_hresult_e(*h)(struct sl_sock *s, const char *str)){
if(sock) sock->defmsg_handler = h;
} }
// text messages for `hresult` // text messages for `hresult`
@ -163,33 +162,174 @@ errex:
return NULL; return NULL;
} }
// common for server thread and `sendall`
static sl_sock_t **clients = NULL;
/** /**
* @brief sl_sock_sendall - send data to all clients connected (works only for server) * @brief sl_sock_sendall - send data to all clients connected (works only for server)
* @param data - message * @param data - message
* @param len - its length * @param len - its length
* @return N of sends or -1 if no server process running * @return N of sends or -1 if no server process running
*/ */
int sl_sock_sendall(uint8_t *data, size_t len){ int sl_sock_sendall(sl_sock_t *sock, uint8_t *data, size_t len){
if(!clients) return -1; FNAME();
if(!sock || !sock->clients) return -1;
int nsent = 0; int nsent = 0;
for(int i = maxclients; i > 0; --i){ for(int i = sock->maxclients; i > 0; --i){
if(!clients[i]) continue; if(!sock->clients[i]) continue;
if(clients[i]->fd < 0 || !clients[i]->connected) continue; if(sock->clients[i]->fd < 0 || !sock->clients[i]->connected) continue;
if((ssize_t)len == sl_sock_sendbinmessage(clients[i], data, len)) ++nsent; if((ssize_t)len == sl_sock_sendbinmessage(sock->clients[i], data, len)) ++nsent;
} }
return nsent; return nsent;
} }
static sl_sock_hresult_e parse_post_data(sl_sock_t *c, char *str);
// return TRUE if this is header without data (also modify c->sockmethod)
static int iswebheader(sl_sock_t *client, char *str){
DBG("check header: _%s_", str);
const char *methods[] = {
[SOCKM_GET] = "GET",
[SOCKM_PUT] = "PUT",
[SOCKM_POST] = "POST",
[SOCKM_PATCH] = "PATCH",
[SOCKM_DELETE] = "DELETE"
};
const int metlengths[] = {
[SOCKM_GET] = 3,
[SOCKM_PUT] = 3,
[SOCKM_POST] = 4,
[SOCKM_PATCH] = 5,
[SOCKM_DELETE] = 6
};
for(sl_sockmethod_e m = SOCKM_GET; m < SOCKM_AMOUNT; ++m){
if(0 == strncmp(str, methods[m], metlengths[m])){
client->sockmethod = m;
DBG("SOCK method: %s", methods[m]);
if(m == SOCKM_GET){ // modify `str` by GET
char *slash = strchr(str, '/');
if(slash){
char *eol = strstr(slash, "HTTP");
if(eol){ // move to `str` head
size_t l = eol - slash - 2;
memmove(str, slash+1, l);
str[l] = 0;
DBG("User asks GET '%s' -> run parser", str);
//parse_post_data(client, str);
return TRUE;
}
}
}
return TRUE;
}
}
return FALSE;
}
static char *stringscan(char *str, char *needle){
char *a, *end = str + strlen(str);
a = strstr(str, needle);
if(!a) return NULL;
a += strlen(needle);
while (a < end && isspace(*a)) a++;
if(a >= end) return NULL;
DBG("Found a needle \"%s\"", needle);
return a;
}
// just check for header words - if so, return TRUE
static int chkwebreq(sl_sock_t *client, char *str){
if(client->contlen == 0){
char *cl = stringscan(str, "Content-Length:");
if(cl){
DBG("Contlen: _%s_", cl);
sl_str2i(&client->contlen, cl);
return TRUE;
}
}
DBG("retval: %d", !client->gotemptyline);
return !client->gotemptyline;
}
// In-place URL parser
void url_decode(char *str) {
if (!str) return;
char *src = str;
char *dst = str;
char hex[3] = {0};
DBG("STR ori: _%s_", str);
while(*src){
if(*src == '+'){
*dst++ = ' ';
src++;
}else if(*src == '%' && isxdigit((unsigned char)src[1]) && isxdigit((unsigned char)src[2])){
hex[0] = src[1];
hex[1] = src[2];
*dst++ = (char)strtol(hex, NULL, 16);
src += 3;
}else{
*dst++ = *src++;
}
}
*dst = 0;
DBG("STR DECODED to _%s_", str);
}
static sl_sock_hresult_e msgparser(sl_sock_t *client, char *str);
// parser of web-encoded data by POST/GET:
static sl_sock_hresult_e parse_post_data(sl_sock_t *c, char *str){
if (!c || !str) return RESULT_BADKEY;
if(0 == strcmp("favicon.ico", str)){
DBG("icon -> omit");
return RESULT_SILENCE;
}
char *start = str;
char *current = str;
DBG("\n\n\nSTART parser");
while(*current){
if(*current == '&'){
*current = '\0';
DBG("\n\n`start` = _%s_", start);
url_decode(start);
sl_sock_hresult_e r = msgparser(c, start);
if(r != RESULT_SILENCE) sl_sock_sendstrmessage(c, sl_sock_hresult2str(r));
start = current + 1;
}
current++;
}
if(*start){
url_decode(start);
sl_sock_hresult_e r = msgparser(c, start);
if(r != RESULT_SILENCE) sl_sock_sendstrmessage(c, sl_sock_hresult2str(r));
}
DBG("\n\n\nEND parser");
return RESULT_SILENCE;
}
// parser of client's message // parser of client's message
// "only-server's" fields of `client` are copies of server's
static sl_sock_hresult_e msgparser(sl_sock_t *client, char *str){ static sl_sock_hresult_e msgparser(sl_sock_t *client, char *str){
char key[SL_KEY_LEN], val[SL_VAL_LEN], *valptr; char key[SL_KEY_LEN], val[SL_VAL_LEN], *valptr;
if(!str || !*str) return RESULT_BADKEY; if(!str || !*str) return RESULT_BADKEY;
// check web headers and fields
DBG("\n\nLINENO: %lu", client->lineno);
if(client->lineno == 0){ // first data line from client -> check if it's a header
if(iswebheader(client, str)){
if(client->sockmethod == SOCKM_GET)
return parse_post_data(client, str);
return RESULT_SILENCE;
}
}else if(client->sockmethod != SOCKM_RAW){
if(chkwebreq(client, str)) return RESULT_SILENCE;
}
if(!client->handlers){ // have only default handler
if(!client->defmsg_handler) return RESULT_BADKEY;
return client->defmsg_handler(client, str);
}
int N = sl_get_keyval(str, key, val); int N = sl_get_keyval(str, key, val);
DBG("getval=%d, key=%s, val=%s", N, key, val); DBG("getval=%d, key=%s, val=%s", N, key, val);
if(N == 0) return RESULT_BADKEY; if(N == 0){
if(client->defmsg_handler) return client->defmsg_handler(client, str);
return RESULT_BADKEY;
}
if(N == 1) valptr = NULL; if(N == 1) valptr = NULL;
else valptr = val; else valptr = val;
if(0 == strcmp(key, "help")){ if(0 == strcmp(key, "help")){
@ -209,9 +349,31 @@ static sl_sock_hresult_e msgparser(sl_sock_t *client, char *str){
if(strcmp(h->key, key)) continue; if(strcmp(h->key, key)) continue;
return h->handler(client, h, valptr); return h->handler(client, h, valptr);
} }
if(client->defmsg_handler) return client->defmsg_handler(client, str);
return RESULT_BADKEY; return RESULT_BADKEY;
} }
// send http responce with data in c->outbuffer
static void send_http_response(sl_sock_t* c){
if(c->sockmethod == SOCKM_RAW) return;
DBG("Send to client HTTP response");
c->sockmethod = SOCKM_RAW; // dirty hack to send data by sl_sock_sendbinmessage
char tbuf[BUFSIZ];
ssize_t L = snprintf(tbuf, BUFSIZ-1,
"HTTP/2.0 200 OK\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Access-Control-Allow-Methods: GET, POST\r\n"
"Access-Control-Allow-Credentials: true\r\n"
"Content-type: text/plain\r\nContent-Length: %zd\r\n\r\n", c->outplen);
if(L < 0){
WARN("sprintf()");
LOGWARN("sprintf()");
return;
}
if(L != sl_sock_sendbinmessage(c, (uint8_t*)tbuf, L)) return;
if(c->outplen != (size_t)sl_sock_sendbinmessage(c, (uint8_t*)c->outbuffer, c->outplen)) return;
}
/** /**
* @brief serverrbthread - thread for standard server procedure (when user give non-NULL `handlers`) * @brief serverrbthread - thread for standard server procedure (when user give non-NULL `handlers`)
* @param d - socket descriptor * @param d - socket descriptor
@ -219,43 +381,50 @@ static sl_sock_hresult_e msgparser(sl_sock_t *client, char *str){
*/ */
static void *serverthread(void _U_ *d){ static void *serverthread(void _U_ *d){
sl_sock_t *s = (sl_sock_t*) d; sl_sock_t *s = (sl_sock_t*) d;
if(!s || !s->handlers){ if(!s || (!s->handlers && !s->defmsg_handler)){
WARNX(_("Can't start server handlers thread")); WARNX(_("Can't start server handlers thread"));
goto errex; goto errex;
} }
int sockfd = s->fd; int sockfd = s->fd;
if(listen(sockfd, maxclients) == -1){ if(listen(sockfd, s->maxclients) == -1){
WARN("listen"); WARN("listen");
goto errex; goto errex;
} }
DBG("Start server handlers thread"); DBG("Start server handlers thread");
int nfd = 1; // only one socket @start int nfd = 1; // only one socket @start
struct pollfd *poll_set = MALLOC(struct pollfd, maxclients+1); struct pollfd *poll_set = MALLOC(struct pollfd, s->maxclients+1);
clients = MALLOC(sl_sock_t*, maxclients+1); sl_sock_t **clients = MALLOC(sl_sock_t*, s->maxclients+1);
s->clients = clients;
// init default clients records // init default clients records
for(int i = maxclients; i > 0; --i){ for(int i = s->maxclients; i > 0; --i){
DBG("fill %dth client info", i); DBG("fill %dth client info", i);
clients[i] = MALLOC(sl_sock_t, 1); clients[i] = MALLOC(sl_sock_t, 1);
sl_sock_t *c = clients[i]; sl_sock_t *c = clients[i];
c->type = s->type; c->type = s->type;
if(s->node) c->node = strdup(s->node); if(s->node) c->node = strdup(s->node);
if(s->service) c->service = strdup(s->service); if(s->service) c->service = strdup(s->service);
c->handlers = s->handlers;
// fill addrinfo // fill addrinfo
c->addrinfo = MALLOC(struct addrinfo, 1); c->addrinfo = MALLOC(struct addrinfo, 1);
c->addrinfo->ai_addr = MALLOC(struct sockaddr, 1); c->addrinfo->ai_addr = MALLOC(struct sockaddr, 1);
// copy server data: we have no `self`, so use so
c->handlers = s->handlers;
c->defmsg_handler = s->defmsg_handler;
} }
// ZERO - listening server socket // ZERO - listening server socket
poll_set[0].fd = sockfd; poll_set[0].fd = sockfd;
poll_set[0].events = POLLIN; poll_set[0].events = POLLIN;
// disconnect client // disconnect client
void disconnect_(sl_sock_t *c, int N){ void disconnect_(sl_sock_t *c, int N){
DBG("client \"%s\" (fd=%d) disconnected", c->IP, c->fd); DBG("Disconnect client \"%s\" (fd=%d)", c->IP, c->fd);
if(disconnect_handler) disconnect_handler(c); if(s->disconnect_handler) s->disconnect_handler(c);
if(c->sockmethod != SOCKM_RAW) send_http_response(c); // we are closing HTTP request - send headers and answer
pthread_mutex_lock(&c->mutex); pthread_mutex_lock(&c->mutex);
DBG("close fd %d", c->fd); DBG("close fd %d", c->fd);
c->connected = 0; c->connected = 0;
close(c->fd); close(c->fd);
c->outplen = 0;
c->lineno = 0;
c->gotemptyline = 0;
sl_RB_clearbuf(c->buffer); sl_RB_clearbuf(c->buffer);
// now move all data of last client to disconnected // now move all data of last client to disconnected
if(nfd > 2 && N != nfd - 1){ // don't move the only or the last if(nfd > 2 && N != nfd - 1){ // don't move the only or the last
@ -281,9 +450,9 @@ static void *serverthread(void _U_ *d){
socklen_t len = sizeof(struct sockaddr); socklen_t len = sizeof(struct sockaddr);
int client = accept(sockfd, &a, &len); int client = accept(sockfd, &a, &len);
DBG("New connection, nfd=%d, len=%d", nfd, len); DBG("New connection, nfd=%d, len=%d", nfd, len);
if(nfd == maxclients + 1){ if(nfd == s->maxclients + 1){
WARNX(_("Limit of connections reached")); WARNX(_("Limit of connections reached"));
if(toomuch_handler) toomuch_handler(client); if(s->toomuch_handler) s->toomuch_handler(client);
close(client); close(client);
}else{ }else{
memset(&poll_set[nfd], 0, sizeof(struct pollfd)); memset(&poll_set[nfd], 0, sizeof(struct pollfd));
@ -302,12 +471,16 @@ static void *serverthread(void _U_ *d){
*c->IP = 0; *c->IP = 0;
} }
DBG("got IP:%s", c->IP); DBG("got IP:%s", c->IP);
if(newconnect_handler) newconnect_handler(c); ++nfd;
if(s->newconnect_handler && s->newconnect_handler(c) == FALSE){
DBG("Client %s rejected", c->IP);
disconnect_(c, nfd);
}else{
if(!c->buffer){ // allocate memory for client's ringbuffer if(!c->buffer){ // allocate memory for client's ringbuffer
DBG("allocate ringbuffer"); DBG("allocate ringbuffer");
c->buffer = sl_RB_new(s->buffer->length); // the same size as for master c->buffer = sl_RB_new(s->buffer->length); // the same size as for master
} }
++nfd; }
} }
} }
// scan connections // scan connections
@ -323,6 +496,7 @@ static void *serverthread(void _U_ *d){
// check for RB overflow // check for RB overflow
if(sl_RB_hasbyte(c->buffer, '\n') < 0){ // -1 - buffer empty (can't be), -2 - buffer overflow if(sl_RB_hasbyte(c->buffer, '\n') < 0){ // -1 - buffer empty (can't be), -2 - buffer overflow
WARNX(_("Server thread: ring buffer overflow for fd=%d"), fd); WARNX(_("Server thread: ring buffer overflow for fd=%d"), fd);
LOGERR(_("Server thread: ring buffer overflow for fd=%d"), fd);
disconnect_(c, fdidx); disconnect_(c, fdidx);
--fdidx; --fdidx;
} }
@ -352,15 +526,39 @@ static void *serverthread(void _U_ *d){
disconnect_(c, fdidx); disconnect_(c, fdidx);
--fdidx; --fdidx;
continue; continue;
}else if(got == 0) continue; }else if(got == 0){ // check last data in POST/GET methods
if(c->sockmethod == SOCKM_RAW) continue;
size_t l = sl_RB_datalen(c->buffer);
if(c->sockmethod == SOCKM_POST){
if(l != (size_t)c->contlen) continue; // wait for last data
if(l < bufsize - 1){
sl_RB_read(c->buffer, buf, l);
buf[l] = 0;
parse_post_data(c, (char*)buf);
}
}
disconnect_(c, fdidx);
--fdidx;
continue;
}
if(got > 1 && *buf && *buf != '\r'){ // not empty line
if(buf[got-2] == '\r'){
buf[got-2] = 0; // omit '\r' for "\r\n"
DBG("delete \\r: _%s_", buf);
}
sl_sock_hresult_e r = msgparser(c, (char*)buf); sl_sock_hresult_e r = msgparser(c, (char*)buf);
if(r != RESULT_SILENCE) sl_sock_sendstrmessage(c, sl_sock_hresult2str(r)); if(r != RESULT_SILENCE) sl_sock_sendstrmessage(c, sl_sock_hresult2str(r));
}else{
DBG("EMPTY line");
if(c->sockmethod != SOCKM_RAW) c->gotemptyline = TRUE;
}
++c->lineno;
} }
} }
// clear memory // clear memory
FREE(buf); FREE(buf);
FREE(poll_set); FREE(poll_set);
for(int i = maxclients; i > 0; --i){ for(int i = s->maxclients; i > 0; --i){
DBG("Clear %dth client data", i); DBG("Clear %dth client data", i);
sl_sock_t *c = clients[i]; sl_sock_t *c = clients[i];
if(c->fd > -1) close(c->fd); if(c->fd > -1) close(c->fd);
@ -372,6 +570,7 @@ static void *serverthread(void _U_ *d){
FREE(c); FREE(c);
} }
FREE(clients); FREE(clients);
s->clients = NULL;
errex: errex:
s->rthread = 0; s->rthread = 0;
return NULL; return NULL;
@ -420,11 +619,14 @@ static sl_sock_t *sl_sock_open(sl_socktype_e type, const char *path, sl_sock_hit
sl_sock_t *s = MALLOC(sl_sock_t, 1); sl_sock_t *s = MALLOC(sl_sock_t, 1);
s->type = type; s->type = type;
s->fd = -1; s->fd = -1;
s->maxclients = SL_DEF_MAXCLIENTS;
s->handlers = handlers; s->handlers = handlers;
s->buffer = sl_RB_new(bufsiz); s->buffer = sl_RB_new(bufsiz);
if(!s->buffer) sl_sock_delete(&s); if(!s->buffer){
else{ // fill node/service sl_sock_delete(&s);
if(type == SOCKT_UNIX) s->node = str; // str now is converted path return NULL;
}else{ // fill node/service
if(type == SOCKT_UNIX) s->node = strdup(str); // str now is converted path
else{ else{
char *delim = strchr(path, ':'); char *delim = strchr(path, ':');
if(!delim) s->service = strdup(path); // only port if(!delim) s->service = strdup(path); // only port
@ -444,13 +646,19 @@ static sl_sock_t *sl_sock_open(sl_socktype_e type, const char *path, sl_sock_hit
// now try to open socket // now try to open socket
if(type != SOCKT_UNIX){ if(type != SOCKT_UNIX){
DBG("try to get addrinfo for node '%s' and service '%s'", s->node, s->service); DBG("try to get addrinfo for node '%s' and service '%s'", s->node, s->service);
char *node = s->node;
if(isserver){ if(isserver){
if(s->type == SOCKT_NET) node = NULL; // common net server -> node==NULL if(!s->node){
else node = "127.0.0.1"; // localhost DBG("Socket type now is SOCKT_NET");
s->type = SOCKT_NET;
} }
DBG("---> node '%s', service '%s'", node, s->service); if(s->type == SOCKT_NETLOCAL){
int e = getaddrinfo(node, s->service, &ai, &res); DBG("SOCKT_NETLOCAL: change `node` to localhost");
FREE(s->node);
s->node = strdup("127.0.0.1"); // localhost
}
}
DBG("---> node '%s', service '%s'", s->node, s->service);
int e = getaddrinfo(s->node, s->service, &ai, &res);
if(e){ if(e){
WARNX("getaddrinfo(): %s", gai_strerror(e)); WARNX("getaddrinfo(): %s", gai_strerror(e));
sl_sock_delete(&s); sl_sock_delete(&s);
@ -491,7 +699,7 @@ static sl_sock_t *sl_sock_open(sl_socktype_e type, const char *path, sl_sock_hit
DBG("s->fd=%d, node=%s, service=%s", s->fd, s->node, s->service); DBG("s->fd=%d, node=%s, service=%s", s->fd, s->node, s->service);
int r = -1; int r = -1;
if(isserver){ if(isserver){
if(s->handlers) if(s->handlers || s->defmsg_handler)
r = pthread_create(&s->rthread, NULL, serverthread, (void*)s); r = pthread_create(&s->rthread, NULL, serverthread, (void*)s);
else r = 0; else r = 0;
}else{ }else{
@ -543,6 +751,15 @@ sl_sock_t *sl_sock_run_server(sl_socktype_e type, const char *path, int bufsiz,
*/ */
ssize_t sl_sock_sendbinmessage(sl_sock_t *socket, const uint8_t *msg, size_t l){ ssize_t sl_sock_sendbinmessage(sl_sock_t *socket, const uint8_t *msg, size_t l){
if(!msg || l < 1) return -1; if(!msg || l < 1) return -1;
if(socket->sockmethod != SOCKM_RAW){ // just fill buffer while socket isn't marked as "RAW"
DBG("Put to buffer: _%s_", (char*)msg);
size_t L = BUFSIZ - socket->outplen;
if(l > L) l = L;
memcpy(socket->outbuffer + socket->outplen, msg, l);
socket->outplen += l;
DBG("Now buflen=%zd, buf: ```%s```", socket->outplen, socket->outbuffer);
return l;
}
DBG("send to fd=%d message with len=%zd (%s)", socket->fd, l, msg); DBG("send to fd=%d message with len=%zd (%s)", socket->fd, l, msg);
while(socket && socket->connected && !sl_canwrite(socket->fd)); while(socket && socket->connected && !sl_canwrite(socket->fd));
if(!socket || !socket->connected) return -1; if(!socket || !socket->connected) return -1;
@ -578,6 +795,13 @@ ssize_t sl_sock_sendstrmessage(sl_sock_t *socket, const char *msg){
ssize_t sl_sock_sendbyte(sl_sock_t *socket, uint8_t byte){ ssize_t sl_sock_sendbyte(sl_sock_t *socket, uint8_t byte){
while(socket && socket->connected && !sl_canwrite(socket->fd)); while(socket && socket->connected && !sl_canwrite(socket->fd));
if(!socket || !socket->connected) return -1; if(!socket || !socket->connected) return -1;
if(socket->sockmethod != SOCKM_RAW){ // just fill buffer while socket isn't marked as "RAW"
DBG("Put to buffer: _%c_", (char)byte);
if(socket->outplen == BUFSIZ) return 0;
socket->outbuffer[socket->outplen++] = (char)byte;
DBG("Now buflen=%zd, buf: ```%s```", socket->outplen, socket->outbuffer);
return 1;
}
DBG("lock"); DBG("lock");
pthread_mutex_lock(&socket->mutex); pthread_mutex_lock(&socket->mutex);
ssize_t r = send(socket->fd, &byte, 1, MSG_NOSIGNAL); ssize_t r = send(socket->fd, &byte, 1, MSG_NOSIGNAL);

View File

@ -21,6 +21,7 @@
#pragma once #pragma once
#include <stdio.h>
#ifdef SL_USE_OLD_TTY #ifdef SL_USE_OLD_TTY
#include <termios.h> // termios #include <termios.h> // termios
#else #else
@ -445,7 +446,7 @@ struct sl_sock_hitem;
struct sl_sock; struct sl_sock;
// socket `key` handlers // socket `key` handlers
typedef sl_sock_hresult_e (*sl_sock_msghandler)(struct sl_sock *client, struct sl_sock_hitem *item, const char *val); typedef sl_sock_hresult_e (*sl_sock_msghandler)(struct sl_sock *s, struct sl_sock_hitem *item, const char *val);
// handler item // handler item
typedef struct sl_sock_hitem{ typedef struct sl_sock_hitem{
sl_sock_msghandler handler; // function-handler sl_sock_msghandler handler; // function-handler
@ -462,6 +463,30 @@ typedef enum{
SOCKT_AMOUNT // amount of types SOCKT_AMOUNT // amount of types
} sl_socktype_e; } sl_socktype_e;
struct sl_sock;
// default max clients amount
#define SL_DEF_MAXCLIENTS (32)
// custom socket handlers: connect/disconnect/etc
// max clients handler
void sl_sock_maxclhandler(struct sl_sock *s, void (*h)(int));
// client connected handler
void sl_sock_connhandler(struct sl_sock *s, int(*h)(struct sl_sock*));
// client disconnected handler
void sl_sock_dischandler(struct sl_sock *s, void(*h)(struct sl_sock*));
// unknown message handler (instead of "BADKEY" default message)
void sl_sock_defmsghandler(struct sl_sock *s, sl_sock_hresult_e(*h)(struct sl_sock *s, const char *str));
typedef enum{
SOCKM_RAW = 0, // default sockets
SOCKM_GET, // http methods - client should be closed after data processing
SOCKM_PUT,
SOCKM_POST,
SOCKM_PATCH,
SOCKM_DELETE,
SOCKM_AMOUNT
} sl_sockmethod_e;
// socket itself // socket itself
typedef struct sl_sock{ typedef struct sl_sock{
int fd; // file descriptor int fd; // file descriptor
@ -476,29 +501,33 @@ typedef struct sl_sock{
pthread_t rthread; // reading ring buffer thread for client and main server thread for server pthread_t rthread; // reading ring buffer thread for client and main server thread for server
char IP[INET_ADDRSTRLEN]; // client's IP address char IP[INET_ADDRSTRLEN]; // client's IP address
sl_sock_hitem_t *handlers; // if non-NULL, run handler's thread when opened sl_sock_hitem_t *handlers; // if non-NULL, run handler's thread when opened
sl_sockmethod_e sockmethod; // method
uint64_t lineno; // number of line read
int contlen; // content length for POST method
int gotemptyline; // == TRUE when found empty line in web request (to know that header is over)
char outbuffer[BUFSIZ]; // buffer for output data (if client is WEB)
size_t outplen; // amount of bytes in `outbuffer`
// server-only items
int maxclients; // max clients amount
void (*toomuch_handler)(int); // too much clients handler; it is running for client connected with number>maxclients (before closing its fd)
int (*newconnect_handler)(struct sl_sock*); // new client connected handler; it will be run each new connection
void (*disconnect_handler)(struct sl_sock*); // client disconnected handler
sl_sock_hresult_e(*defmsg_handler)(struct sl_sock *s, const char *str); // default message handler (the only without `handlers` array or instead of "BADKEY" answer
struct sl_sock **clients; // pointer to clients array for `sendall`
} sl_sock_t; } sl_sock_t;
const char *sl_sock_hresult2str(sl_sock_hresult_e r); const char *sl_sock_hresult2str(sl_sock_hresult_e r);
void sl_sock_delete(sl_sock_t **sock); void sl_sock_delete(sl_sock_t **sock);
sl_sock_t *sl_sock_run_client(sl_socktype_e type, const char *path, int bufsiz); sl_sock_t *sl_sock_run_client(sl_socktype_e type, const char *path, int bufsiz);
sl_sock_t *sl_sock_run_server(sl_socktype_e type, const char *path, int bufsiz, sl_sock_hitem_t *handlers); sl_sock_t *sl_sock_run_server(sl_socktype_e type, const char *path, int bufsiz, sl_sock_hitem_t *handlers);
void sl_sock_changemaxclients(int val); void sl_sock_changemaxclients(sl_sock_t *sock, int val);
int sl_sock_getmaxclients(); int sl_sock_getmaxclients(sl_sock_t *sock);
// max clients handler
typedef void (*sl_sock_maxclh_t)(int fd);
void sl_sock_maxclhandler(sl_sock_maxclh_t h);
// new client connected handler
typedef void (*sl_sock_connh_t)(sl_sock_t *s);
void sl_sock_connhandler(sl_sock_connh_t h);
// client disconnected handler
typedef void (*sl_sock_disch_t)(sl_sock_t *s);
void sl_sock_dischandler(sl_sock_disch_t h);
ssize_t sl_sock_sendbinmessage(sl_sock_t *socket, const uint8_t *msg, size_t l); ssize_t sl_sock_sendbinmessage(sl_sock_t *socket, const uint8_t *msg, size_t l);
ssize_t sl_sock_sendbyte(sl_sock_t *socket, uint8_t byte); ssize_t sl_sock_sendbyte(sl_sock_t *socket, uint8_t byte);
ssize_t sl_sock_sendstrmessage(sl_sock_t *socket, const char *msg); ssize_t sl_sock_sendstrmessage(sl_sock_t *socket, const char *msg);
ssize_t sl_sock_readline(sl_sock_t *sock, char *str, size_t len); ssize_t sl_sock_readline(sl_sock_t *sock, char *str, size_t len);
int sl_sock_sendall(uint8_t *data, size_t len); int sl_sock_sendall(sl_sock_t *sock, uint8_t *data, size_t len);
sl_sock_hresult_e sl_sock_inthandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str); sl_sock_hresult_e sl_sock_inthandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str);
sl_sock_hresult_e sl_sock_dblhandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str); sl_sock_hresult_e sl_sock_dblhandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str);

View File

@ -1,7 +1,6 @@
prefix=@CMAKE_INSTALL_PREFIX@ prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix} libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
libdir=${exec_prefix}/lib includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
includedir=${prefix}/include
Name: @PROJ@ Name: @PROJ@
Description: Library with a lot of usefull snippets Description: Library with a lot of usefull snippets