mirror of
https://github.com/eddyem/snippets_library.git
synced 2025-12-06 02:35:20 +03:00
38 lines
23 KiB
Plaintext
38 lines
23 KiB
Plaintext
<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 почему-то до сих пор нет такого "супердемона", который бы "давал по шапке" наблюдателям-вредителям. Иначе угробят телескоп, а за это даже никакого наказания не понесут (я бы отстранял наблюдателей минимум на полгода от наблюдений, если они открывают телескоп тогда, когда этого делать ни в коем случае нельзя).
|