eddy_em: (Default)
[personal profile] eddy_em
Репозиторий с кодом.
Сначала избавился от нескольких багов. Потом решил добавить сетевой функционал. Ну, а коль сеть — то и кольцевой буфер нужен. В общем, теперь там — не просто макросы и функционал, который в 99% случаев нужен (тот же разбор параметров командной строки), но и дополнительные облегчалки.

В релизе 0.2.1 код подвергся рефакторингу, многие функции переименованы, чтобы было единообразие: почти все функции и т.п. имеют префикс sl_; типы-перечисления имеют суффикс _e, типы данных — суффикс _t. Без префикса я оставил лишь функции red и green для цветного вывода сообщений. И многие макросы лень было переименовывать.

"Старое":


Для использования функционала в самом начале main() нужно выполнить функцию sl_init. Для работы с терминалом ввода в режиме "без эха" — sl_setup_con() (и не забыть в конце и в функции signals() вызвать sl_restore_con(), иначе по завершению придется ручками вводить reset). Функции sl_read_con() и sl_getchar() дают, соответственно, неблокирующее и блокирующее чтение символа, введенного пользователем.
Осталось и упрощение работы с mmap: sl_mmap() и sl_munmap().
sl_random_seed() позволяет при помощи /dev/random сгенерировать случайную затравку для использования в ГСЧ. sl_libversion() позволяет получить строку с текущей версией библиотеки.
Для работы с gettext теперь нужно определить макрос GETTEXT, в этом случае макрос _() будет вызывать gettext().
Функции работы с блочными устройствами не просто переименовались: по умолчанию я перешел на termios2. Если нужно собрать библиотеку с поддержкой "классической" termios со всеми ее убогими косяками, нужно определить макрос SL_USE_OLD_TTY (в этом случае еще и будет доступна функция sl_tty_convspd() для преобразования скорости интерфейса из числа в макрос). А остальные функции — как и были: sl_tty_new() для выделения памяти и инициализации структур; sl_tty_open() — открыть порт; sl_tty_read() и sl_tty_write() для чтения/записи; sl_tty_tmout() позволяет задать таймаут для select() в микросекундах. sl_tty_close() — закрыть устройство и очистить структуры данных.
Логгирование имеет "умолчальный" глобальный лог (sl_globlog), для которого нарисованы макросы вроде LOGERR(), LOGERRADD() и т.п. (все с суффиксом ADD добавляют текст без записи timestamp и уровня сообщения — если нужно вывести несколько строк текста; но лучше ими не пользоваться часто, особенно в многопоточных/многопроцессных приложениях). Чтобы логи писались вменяемо в многопоточных/многопроцессных, пришлось лог-файл каждый раз для записи открывать, блокировать, потом писать, закрывать и разблокировать. Чтобы избежать дедлоков или слишком долгих ожиданий, установлен "гвоздями прибитый" таймаут в 0.1с: если за это время заблокировать файл не получится, то отменяем запись и возвращаем пользователю 0; иначе возвращаем количество записанных символов. "Глобальный" лог-файл активируется при помощи макроса OPENLOG(nm, lvl, prefix) (имя, уровень, флаг добавления "префикса": строчки с расшифровкой уровня сообщения, например, [WARN] или [ERR]). Функции sl_createlog(), sl_deletelog() и sl_putlogt() позволяют активировать структуру-лог (и создать файл, если надо), удалить структуру (сам файл не удаляется) и что-нибудь туда записать/дописать.
В работе с аргументами командной строки я исправил один баг, связанный с выводом справки по "только длинным" аргументам, у которых в качестве "короткой альтернативы" проставлены одинаковые числа. Ну и была проблема с опцией "-?", которая криво обрабатывалась (т.к. я пользуюсь getopt_long). В остальном все осталось, как было: поддерживаются целочисленные аргументы (причем, arg_none означает, что к заданной переменной будет добавляться 1 каждый раз, как встречается этот флаг — удобно для задания уровня отображения как, например, -vvvv), long long, double, float, string и "аргумент-функция" (т.е. вместо присвоения значения будет выполняться заданная функция — например, чтобы парсить свои вложенные аргументы). Все также для каждого флага можно указать, что он не имеет аргументов, имеет обязательный, необязательный или множественный (в этом случае переменной-целью должен быть указатель соответствующего типа: при разборе получится NULL-terminated массив со всеми введенными пользователем данными (удобно, например, когда тебе нужно указать несколько файлов для считывания из них однообразной информации и т.п.). Поддерживаются "подопции" (когда за флагом следует строка вида "so1=par1;so2=par2..."). sl_showhelp() позволяет вызвать справку по всем опциям или только одной. Она же вызывается автоматом, если пользователь что-то не то введет. Чтобы поменять форматную строку справки, используется sl_helpstring() (там должен быть один-единственный "%s", куда вставится имя запускаемого файла). sl_parseargs() нужно запускать сразу после инициализации, и дальше уже пользоваться своей структурой с параметрами.
Для проверки, не запущен ли второй экземпляр, есть функция sl_check4running(). Если найден процесс, запускается слабая функция sl_iffound_deflt(), которую под свои нужды можно изменить. sl_getPSname() парсит /proc и выдает имя процесса с заданным PID.
FIFO/LIFO также остались без изменений: sl_list_push() и sl_list_push_tail() позволяют воткнуть запись в начало или хвост списка, а sl_list_pop() — извлечь последнюю запись.

"Новое":


sl_mem_avail() позволяет определить доступный объем ОЗУ. sl_omitspaces() и sl_omitspacesr() — удалить начальные и конечные пробелы. Помимо sl_str2d() ("безопасное" преобразование строки в double) я добавил sl_str2ll() и sl_str2i() — long long и int, соответственно.
Для проверки, можно ли в данный дескриптор писать или читать из него без блокировки, добавил функции sl_canread() и sl_canwrite().
Поддержку конфигурационных файлов тоже добавил в библиотеку. В файле все данные должны быть в формате "параметр = значение" или просто "параметр". Допускается наличие пустых строк и комментариев (все, что за символом "#" в строке). "Гвоздями" прибил максимальную длину параметра (SL_KEY_LEN, 32 байта) и значения (SL_VAL_LEN, 128 байт). Работа с флагами похожа на работу с аргументами командной строки. sl_get_keyval() чистит лишние пробелы и комментарии, выделяя непосредственно строчку "параметр [= значение]" и помещая ее в отдельные аргументы (возвращает 0 если ничего в строке не найдено, 1 — если только параметр, 2 — если и параметр, и значение); эта функция используется и в обработке запросов по сети. sl_print_opts() печатает изменяемые параметры (или все, если задан флаг) с комментариями. sl_conf_readopts() читает конфигурационный файл. Опции задаются полностью аналогично опциям командной строки, что позволяет печатать сопровождающий текст помимо флага и его значения.
Для работы с кольцевым буфером есть следующие функции: sl_RB_new() и sl_RB_delete() — инициализация и удаление данных структуры кольцевого буфера. sl_RB_hasbyte() позволяет определить, есть ли в буфере нужный символ (например, конец строки), а sl_RB_readto() — считать до него (включительно). sl_RB_putbyte(), sl_RB_write() и sl_RB_writestr() позволяют записать данные в буфер (возвращают количество записанных символов, чтобы юзер мог понять, что что-то не то, если место кончилось). sl_RB_datalen() и sl_RB_freesize() позволяют узнать, сколько места в буфере занимают данные, и сколько там свободно. Нужно помнить, что размер данных будет на 1 байт меньше длины буфера (по понятным причинам). sl_RB_clearbuf() сбрасывает содержимое буфера, как будто бы там ничего нет.

Ну и последняя, самая жирная по количеству функций и структур — сетевая часть.
Для активации соединения есть две функции: sl_sock_run_client() и sl_sock_run_server() — для клиента и сервера, соответственно. Они инициализируют структуры, открывают соединение и подключаются для клиента или вызывают bind для сервера. Клиенту заодно запускается поток, читающий приходящие данные и складывающий в кольцевой буфер. У сервера, если указать не-NULL аргумент handlers, тоже запускается отдельный поток: он обслуживает присоединенных клиентов, запускает accept для новых, закрывает отключившиеся; полученные от клиентов данные парсятся и, если среди переданной структуры handlers находится подобный ключ, то запускается соответствующий обработчик + есть три стандартных обработчика: sl_sock_inthandler(), sl_sock_dblhandler() и sl_sock_strhandler() — для int64_t, double и char* (для того, чтобы знать, когда было произведено изменение значения, я дополнительные типы данных ввел, где помимо данных есть timestamp). Предельное количество клиентов можно задать функцией sl_sock_changemaxclients(), а узнать текущее — sl_sock_getmaxclients(). sl_sock_delete() закрывает соединение и удаляет структуру.
Помимо этого, есть еще обработчики событий: подключение клиента, отключение и подключение клиента сверх предельного количества. По умолчанию там NULL (т.е. ничего не делается), но можно сменить при помощи, соответственно, функций sl_sock_connhandler(), sl_sock_dischandler() и sl_sock_maxclhandler(). В них можно, например, в логи писать, мол клиент fd=xx ip=yyy подключился/отключился. А в последнем — писать клиенту "подожди, все соединения заняты".
Отправлять данные можно непосредственно при помощи write/send (не забывая сначала заблокировать, а потом разблокировать соответствующий мьютекс), либо же при помощи оберток: sl_sock_sendbyte(), sl_sock_sendbinmessage() и sl_sock_sendstrmessage(). Читать тоже можно или при помощи соответствующих функций работы с кольцевым буфером, или же текстовые строковые сообщения можно считывать посредством sl_sock_readline().
У сервера бывают ситуации, когда одно и то же сообщение нужно отправить сразу всем клиентам. Для этого есть функция sl_sock_sendall() (она тупо перебирает все открытые и подключенные дескрипторы, да отправляет туда данные).

Примеры


clientserver — пример организации простейшего сервера и клиента. 226 строк всего: аргументы командной строки, примеры обработчиков, пример неблокирующего чтения с клавиатуры одновременно с чтением из сокета. Здесь я не парился насчет типа "локальный сетевой": можно открыть либо UNIX-сокет, либо INET. Заодно логи можно писать.
conffile — 101 строка. Дает пример ввода конфигурационных параметров как из командной строки, так и из конфигурационного файла. Также парсит строковый параметр вида "параметр=значение". Отображает состояние флагов после обработки аргументов командной строки, а затем — после считывания из конфигурационного файла. К сожалению, чтобы считать путь к конфигурационному файлу, надо сначала обработать аргументы командной строки, поэтому, если не постараться, а просто на тот же самый массив структур вызывать чтение конф-файла, то приоритет будет за ним. Хотя, логика подсказывает, что приоритет всегда должен быть за командной строкой. Поэтому в случае, когда необходимо именно иметь приоритет командной строки, нужно будет завести 2 раздельных структуры, инициализировать их сначала каким-то дефолтом (с данными, которые пользователь точно не соможет ввести), а после сравнить и все неинициализированное из командной строки инициализировать записями из конф-файла.
fifo — 61 строка. Пример работы с FIFO/LIFO. Показывает реальный размер свободной ОЗУ и затем все параметры командной строки помещает в списки: LIFO и FIFO. А затем из каждого списка по одному извлекает, демонстрируя, как это работает.
helloworld — 35 строк (из которых только 10 приходится на собственно код). Демонстрирует цветной вывод и ввод без эха.
options — 134 строки. Демонстрирует работу с разным типом опций (кроме функции: я и сам уже забыл, зачем оно мне нужно было), в т.ч. массивами. Ну и по умолчанию демонстрирует простейший терминал для работы с символьными устройствами (если пользователь введет путь).
ringbuffer — 80 строк, демонстрирующих работу кольцевого буфера. По умолчанию он имеет размер 1024 байт (т.е. вмещает 1023 символа). Дальше пользователю предлагается вводить построчно текст, который будет помещаться в буфер. Ввод заканчивается на переполнении буфера или же при нажатии ctrl+D. После чего весь буфер вычитывается и выводится на консоль.

Теперь как минимум все, что пользуется этой библиотекой, надо обновить. Ну, а сетевые демоны роботелескопов вообще радикально так переделать: чтобы "шапкой" с метеоданными не демон монтировки занимался (что вообще нелогично), а демон погоды; чтобы демоны купола, монтировки и телескопа "слушались" демона погоды. Скажем, если стоит prohibited=1, то прекращать наблюдения, когда они идут, или не давать наблюдателю открыться. Вот на БТА такую функцию АСУшник выполняет (правда, некоторые из рук вон плохо работают, позволяя наблюдателям-вредителям открывать телескоп при влажности свыше 90%, облачности или даже тумане). А на Ц-1000 почему-то до сих пор нет такого "супердемона", который бы "давал по шапке" наблюдателям-вредителям. Иначе угробят телескоп, а за это даже никакого наказания не понесут (я бы отстранял наблюдателей минимум на полгода от наблюдений, если они открывают телескоп тогда, когда этого делать ни в коем случае нельзя).
This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org

April 2025

S M T W T F S
  1 23 45
67 89101112
13141516171819
20212223242526
27282930   

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated May. 22nd, 2025 12:13 pm
Powered by Dreamwidth Studios