Программирование в стандарте POSIX

         

Опрос идентифицирующих данных хостов


Самую общую информацию о характеристиках хоста позволяют получить служебная программа uname

uname [-snrvma]

и одноименная функция (см. листинг 10.1).

#include <sys/utsname.h> int uname (struct utsname *name);

Листинг 10.1. Описание функции uname (html, txt)

Функция uname( помещает информацию, идентифицирующую опрашиваемую систему, в структуру типа utsname, которая должна содержать по крайней мере следующие поля.

char sysname []; /* Имя реализации ОС */ char nodename []; /* Имя хоста как узла */ /* коммуникационной сети */ char release []; /* Номер выпуска ОС */ char version []; /* Номер версии ОС */ char machine []; /* Название аппаратной */ /* конфигурации */

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

Аналогичные данные выдает на стандартный вывод служебная программа uname, опции которой соответствуют полям структуры utsname (-m – выдать название аппаратной конфигурации, -n – имя узла, -s, -r и -v – имя реализации ОС, номера выпуска и версии, соответственно). Опция -a предписывает выдавать все; без опций выдается имя реализации.

Результат выполнения команды

uname -a

может выглядеть так, как показано в листинге 10.2. Правда, здесь фигурирует нестандартная составляющая, выдаваемая по опции -p, – тип процессора (в данном случае unknown).

Linux t94 2.4.18-3 #1 Thu Apr 18 07:37:53 EDT 2002 i686 unknown

Листинг 10.2. Возможный результат выполнения команды uname -a. (html, txt)

Функция gethostname() (см. листинг 10.3) возвращает в массиве name длины namelen   имя хоста. Подходящее значение для namelen – HOST_NAME_MAX + 1 (см. следующий раздел).

#include <unistd.h> int gethostname (char *name, size_t namelen);

Листинг 10.3. Описание функции gethostname(). (html, txt)



Опрос конфигурационных параметров хоста


Значения конфигурационных параметров – важнейшая характеристика хоста, а настройка на конфигурацию целевой системы – обязательный элемент мобильного программирования приложений. Подобная настройка может выполняться статически, средствами условной компиляции, а также динамически, путем опроса значений конфигурационных параметров во время выполнения.

Основная часть статической конфигурационной информации сосредоточена в упоминавшемся ранее заголовочном файле <unistd.h>. Кратко опишем наиболее важные характеристики.

_POSIX_VERSION

Поддерживаемая версия системного интерфейса для языка C стандарта POSIX. Для POSIX-2001 значение этой константы должно равняться 200112L. Предыдущей версии (1996 года) соответствует значение 199506L.

_POSIX2_VERSION



Поддерживаемая версия интерфейса к системным сервисам на уровне командного языка и служебных программ; для реализаций, соответствующих стандарту POSIX-2001, значение этой константы должно равняться 200112L.

В листинге 10.4 показан пример использования значения _POSIX_VERSION для условной компиляции.

#if _POSIX_VERSION >= 200112L /* Используем новую функцию, применимую к */ /* большим файлам */ off_t fpos = ftello (fp); #else /* Либо реализация поддерживает только */ /* старую версию стандарта POSIX, либо */ /* константа _POSIX_VERSION вообще не */ /* определена. */ /* Используем старую, традиционную функцию */ /* опроса текущей позиции в файле */ long fpos = ftell (fp); #endif

Листинг 10.4. Пример условной компиляции с использованием конфигурационной константы _POSIX_VERSION. (html, txt)

Следующая группа констант описывает поддерживаемые необязательные возможности стандарта POSIX-2001. Значения этих констант могут равняться -1 (необязательная возможность не поддерживается), 0 (в системе присутствуют все необходимые описания, но наличие поддержки нужно опросить во время выполнения) или быть положительными (возможность полностью поддерживается реализацией).

_POSIX_CHOWN_RESTRICTED

Функции chown() и fchown() могут вызываться для смены владельца только процессами, обладающими соответствующими привилегиями.


_POSIX_IPV6

Реализация поддерживает IPv6. Положительное значение этой константы должно равняться 200112L.

_POSIX_JOB_CONTROL

Реализация поддерживает управление заданиями.

_POSIX_NO_TRUNC

Компоненты маршрутных имен, имеющие длину более NAME_MAX, вызывают сообщения об ошибках.

_POSIX_REGEXP

Реализация поддерживает обработку регулярных выражений.

_POSIX_SAVED_IDS

Каждый процесс имеет сохраненный ПДП-идентификатор и сохраненный ПДГ-идентификатор.

_POSIX_SHELL

Реализация поддерживает стандартный командный интерпретатор.

_POSIX_VDISABLE

Значением этой константы должен быть символ, отключающий обработку соответствующего специального управляющего символа терминала, если его (символ _POSIX_VDISABLE) сделать значением элемента массива c_cc[].

_POSIX2_C_BIND

Реализация поддерживает системный интерфейс для языка C. Значение этой константы должно равняться 200112L.

_POSIX2_CHAR_TERM

Реализация поддерживает по крайней мере один тип терминалов.

_POSIX2_LOCALEDEF

Реализация поддерживает создание языково-культурных сред. Положительное значение должно равняться 200112L.

_POSIX_V6_ILP32_OFF32

Реализация предоставляет среду компиляции C-программ с 32-битными типами int, long, off_t и такими же указателями.

_POSIX_V6_ILP32_OFFBIG

Реализация предоставляет среду компиляции C-программ с 32-битными типами int, long и такими же указателями; размер значений типа off_t составляет не менее 64 бит.

_POSIX_V6_LP64_OFF64

Реализация предоставляет среду компиляции C-программ с 32-битным типом int и 64-битными указателями и типами long и off_t.

_POSIX_V6_LPBIG_OFFBIG

Реализация предоставляет среду компиляции C-программ с не менее чем 32-битными значениями типа int и не менее чем 64-битными указателями и значениями типов long и off_t.

_POSIX_ASYNC_IO

Поддерживается асинхронный ввод/вывод.

_POSIX_PRIO_IO

Поддерживается приоритетный ввод/вывод.

_POSIX_SYNC_IO

Поддерживается синхронизированный ввод/вывод.

Три константы задают номера файловых дескрипторов для стандартных ввода (STDIN_FILENO со значением 0), вывода (STDOUT_FILENO – 1) и протокола (STDERR_FILENO – 2).Их нужно не анализировать, а использовать вместо явных числовых значений.


int uname


#include <sys/utsname.h> int uname (struct utsname *name);
Листинг 10.1. Описание функции uname
Закрыть окно




Linux t94 2.4.18-3 #1 Thu Apr 18 07:37: 53 EDT 2002 i686 unknown
Листинг 10.2. Возможный результат выполнения команды uname -a.
Закрыть окно




#include <unistd.h> int gethostname (char *name, size_t namelen);
Листинг 10.3. Описание функции gethostname().
Закрыть окно




#if _POSIX_VERSION >= 200112L /* Используем новую функцию, применимую к */ /* большим файлам */ off_t fpos = ftello (fp); #else /* Либо реализация поддерживает только */ /* старую версию стандарта POSIX, либо */ /* константа _POSIX_VERSION вообще не */ /* определена. */ /* Используем старую, традиционную функцию */ /* опроса текущей позиции в файле */ long fpos = ftell (fp); #endif
Листинг 10.4. Пример условной компиляции с использованием конфигурационной константы _POSIX_VERSION.
Закрыть окно




#include <unistd.h> long sysconf (int name); size_t confstr ( int name, char *buf, size_t len); long fpathconf (int fildes, int name); long pathconf (const char *path, int name);
Листинг 10.5. Описание функций sysconf(), confstr(), fpathconf() и pathconf().
Закрыть окно




if unm=$(getconf "$@"); then if [ "$unm" = "undefined" ]; then echo Значение "$@" не определено else echo Значение "$@" равно $unm. fi else echo Ошибка при выполнении getconf fi
Листинг 10.6. Пример аккуратной обработки результатов утилиты getconf.
Закрыть окно




#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main (void) { size_t buf_len; char *buf; if ((buf_len = confstr (_CS_PATH, (char *) NULL, (size_t) 0)) > 0) { if ((buf = (char *) malloc (buf_len)) != NULL) { (void) confstr (_CS_PATH, buf, buf_len); printf ("Standard PATH: %s\n", buf); } } return 0; }
Листинг 10.7. Пример аккуратного опроса конфигурационной цепочки символов.
Закрыть окно



Функции для работы с сокетами


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

#include <sys/socket.h> int socket (int af, int type, int protocol); int socketpair (int af, int type, int protocol, int sds [2]);

Листинг 11.19. Описание функций socket() и socketpair(). (html, txt)

Аргумент af задает адресное семейство создаваемого сокета, аргумент type - тип, аргумент protocol - конкретный протокол (0 обозначает неспецифицированный подразумеваемый протокол, пригодный для запрошенного типа). Напомним, что подходящие значения для аргументов af, type и protocol можно получить с помощью описанной ранее функции getaddrinfo().

Функция socketpair() по назначению аналогична pipe(), только организуется не безымянный канал, а пара соединенных, безымянных (не привязанных к каким-либо адресам), идентичных сокетов, открытые файловые дескрипторы которых помещаются в массив sds. Обычно она используется для адресного семейства AF_UNIX; поддержка для других семейств не гарантируется.

После того как посредством функции bind() (см. пример 11.20) создан сокет, идентифицируемый дескриптором sd, ему присваивают локальный адрес, заданный аргументом address (address_len - длина структуры sockaddr, на которую указывает address). Источником локальных адресов для сокетов может служить вышеупомянутая функция getaddrinfo().

#include <sys/socket.h> int bind (int sd, const struct sockaddr *address, socklen_t address_len);

Листинг 11.20. Описание функции bind(). (html, txt)

Опросить присвоенный локальный адрес (его иногда называют именем сокета) можно с помощью функции getsockname() (см. пример 11.21): она помещает его в структуру sockaddr, на которую указывает аргумент address, а длину адреса записывает по указателю address_len.


#include <sys/socket.h> int getsockname (int sd, struct sockaddr *restrict address, socklen_t *restrict address_len);

Листинг 11.21. Описание функции getsockname(). (html, txt)

Если сокет ориентирован на режим с установлением соединения (имеет тип SOCK_STREAM), то, воспользовавшись функцией listen() (см. пример 11.22), его следует пометить как готового принимать соединения ("слушающего").

#include <sys/socket.h> int listen (int sd, int backlog);

Листинг 11.22. Описание функции listen(). (html, txt)

Аргумент backlog сообщает операционной системе рекомендуемую длину очереди соединений, ожидающих обработки слушающим сокетом. Реализация должна поддерживать значения аргумента backlog вплоть до конфигурационной константы SOMAXCONN, определенной в

заголовочном файле <sys/socket.h>. ОС имеет право установить длину очереди меньше рекомендуемой. При неположительном значении backlog очередь будет иметь зависящую от реализации минимальную длину.

Прием соединений выполняет функция accept() (см. пример 11.23). Она выбирает первое соединение из очереди, ассоциированной с заданным дескриптором sd слушающим сокетом, создает новый сокет с теми же адресным семейством, типом и протоколом и возвращает в качестве результата его файловый дескриптор.

#include <sys/socket.h> int accept (int sd, struct sockaddr *restrict address, socklen_t *restrict address_len);

Листинг 11.23. Описание функции accept(). (html, txt)

Если значение аргумента address отлично от пустого указателя, то в структуру sockaddr, на которую указывает address, помещается адрес сокета, пытающегося установить соединение. По указателю address_len при обращении к функции accept() должна быть задана длина переданной структуры sockaddr, а на выходе туда записывается длина адреса партнера по взаимодействию.

Если очередь соединений, ожидающих обработки слушающим сокетом, пуста и для дескриптора sd не установлен флаг O_NONBLOCK, то вызвавший функцию accept() процесс (поток управления) блокируется до появления подобного соединения.При непустой очереди функция select() сообщит о готовности дескриптора sd к чтению.

Другая сторона, т. е. потенциальный партнер по взаимодействию, инициирует соединение с помощью функции connect() (см. пример 11.24). Аргументы address и address_len стандартным образом задают адрес сокета (как правило, слушающего), с которым необходимо установить соединение.

#include <sys/socket.h> int connect (int sd, const struct sockaddr *address, socklen_t address_len);

Листинг 11.24. Описание функции connect(). (html, txt)



При непустой очереди функция select() сообщит о готовности дескриптора sd к чтению.

Другая сторона, т. е. потенциальный партнер по взаимодействию, инициирует соединение с помощью функции connect() (см. пример 11.24). Аргументы address и address_len стандартным образом задают адрес сокета (как правило, слушающего), с которым необходимо установить соединение.

#include <sys/socket.h> int connect (int sd, const struct sockaddr *address, socklen_t address_len);

Листинг 11.24. Описание функции connect().

Если для сокета, заданного аргументом sd (запрашивающего установление соединения), еще не выполнена привязка к локальному адресу, функция connect() сама осуществит связывание со свободным локальным адресом (правда, лишь при условии, что адресное семейство сокета отлично от AF_UNIX).

Функция connect() ограничится фиксацией адреса сокета, взаимодействующего с заданным аргументом sd, если тип сокета не требует установления соединения. В частности, для сокетов типа SOCK_DGRAM таким способом можно специфицировать адреса отправляемых (с помощью функции send()) и принимаемых (посредством обращения к функции recv()) датаграмм.

Когда в качестве аргумента address передается пустой указатель, адрес взаимодействующего сокета сбрасывается.

Попытка установить соединение блокирует вызывающий процесс на неспецифицируемый промежуток времени (в случае наличия флага O_NONBLOCK). Если по истечении этого промежутка соединение установить не удалось, вызов connect(), равно как и попытка установления соединения, завершаются неудачей. Если ожидание прерывается обрабатываемым сигналом, вызов connect() завершается неудачей (переменной errno присваивается значение EINTR), но установление соединения продолжается и будет завершено асинхронно.

Если для дескриптора sd задан флаг O_NONBLOCK, а соединение не может быть установлено немедленно, то вызов connect() завершается неудачей со значением errno, равным EINPROGRESS, но установление соединения продолжается и будет завершено асинхронно.


Последующие обращения к функции connect() с тем же сокетом, выполненные до установления соединения, завершаются неудачей (EALREADY).

Сокет оказывается в неспецифицированном состоянии, если функция connect() завершается неудачей по другим причинам. Приложения, соответствующие стандарту POSIX-2001, должны закрыть файловый дескриптор sd и создать новый сокет для продолжения попыток установить соединение.

После асинхронного установления соединения функции select() и poll() сообщат, что файловый дескриптор sd готов к записи.

Функция poll(), позволяющая мультиплексировать ввод/вывод в пределах набора файловых дескрипторов, была описана нами выше. Имеющая сходную направленность (но входящая в обязательную часть стандарта POSIX-2001) функция select() и ее недавно введенный аналог pselect() представлены в пример 11.25.

#include <sys/select.h> int pselect (int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, const struct timespec *restrict timeout, const sigset_t *restrict sigmask); int select (int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout); void FD_CLR (int fd, fd_set *fdset); int FD_ISSET (int fd, fd_set *fdset); void FD_SET (int fd, fd_set *fdset); void FD_ZERO (fd_set *fdset);

Листинг 11.25. Описание функций семейства select*().

Если значение аргумента readfds (writefds, errorfds) функции pselect() отлично от NULL, оно ссылается на объект типа fd_set, который на входе специфицирует набор файловых дескрипторов, проверяемых на готовность к чтению (или к записи, или к обработке исключительных ситуаций), а на выходе указывает, какие из них успешно прошли проверку. Аргумент nfds задает границу проверяемых дескрипторов (они являются небольшими целыми числами): дескриптор подлежит проверке, если его значение находится в пределах от 0 до (nfds - 1) включительно.

Стандарт POSIX-2001 определяет тип fd_set как абстрактный. Для работы со значениями этого типа служат макросы FD_ZERO() (сделать набор пустым), FD_SET() (добавить дескриптор к набору), FD_CLR() (удалить дескриптор из набора), FD_ISSET() (проверить принадлежность дескриптора набору).


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

В качестве результата pselect() возвращается общее количество дескрипторов во всех трех наборах, успешно прошедших проверку.

При отсутствии готовых дескрипторов вызывающий процесс блокируется, пока таковые не появятся, или пока не истечет заданное аргументом timeout время ожидания, или пока ожидание не будет прервано сигналом. Пустой указатель в качестве значения timeout означает бесконечное ожидание, а нулевые значения полей структуры timespec - отсутствие блокировки. Реализация может накладывать ограничение на максимальное время ожидания, но оно не должно быть меньше тридцати одного дня.

Если значение аргумента sigmask отлично от пустого указателя, функция pselect() на входе заменяет маску сигналов процесса на заданную, а на выходе восстанавливает прежнюю маску.

Функция pselect() поддерживает обычные файлы, терминалы и псевдотерминалы, каналы и сокеты.

Функция select() эквивалентна pselect() со следующими оговорками.

Для функции select() время ожидания задается в секундах и микросекундах в виде структуры типа timeval, а для pselect() - в секундах и наносекундах как аргумент типа struct timespec.У функции select() нет аргумента - маски сигналов, что эквивалентно пустому указателю в качестве значения аргумента sigmask функции pselect().В случае успешного завершения функция select() может модифицировать структуру, на которую указывает аргумент timeout.

С сокетами могут быть ассоциированы опции, влияющие на их функционирование. Значения этих опций можно опросить или изменить с помощью функций getsockopt() и setsockopt() (см. пример 11.26).

#include <sys/socket.h> int getsockopt (int sd, int level, int option_name, void *restrict option_value, socklen_t *restrict option_len); int setsockopt (int sd, int level, int option_name, const void *option_value, socklen_t option_len);

Листинг 11.26. Описание функций getsockopt() и setsockopt().

Опции задаются именованными константами (аргумент option_name), которые определены в заголовочном файле <sys/socket.h>.


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

SO_ERROR

Статус ошибок (после опроса очищается).

SO_TYPE

Тип сокета.

Ко второй группе отнесем булевы опции, представленные целочисленными значениями (0 означает ложь).

SO_DEBUG

Сообщает, записывается ли отладочная информация о работе сокета.

SO_ACCEPTCONN

Указывает, является ли сокет слушающим.

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

SO_SNDBUF

Размер буфера для передаваемых данных (выходного буфера).

SO_RCVBUF

Размер входного буфера.

SO_RCVLOWATM

Минимальное число байт, обрабатываемых при вводе. Подразумеваемое значение равно единице. Большие значения могут вызвать блокировку принимающего процесса до поступления в сокет необходимого объема данных.

SO_SNDLOWAT

Минимальное число байт, обрабатываемых при выводе.

В четвертую группу входят опции со структурными значениями.

SO_LINGER

Определяет, блокировать ли процесс при закрытии дескриптора sd до передачи буферизованных данных, и если блокировать, то на какой срок. Значением опции является структура linger, определенная в заголовочном файле <sys/socket.h> и содержащая, согласно стандарту POSIX-2001, по крайней мере следующие поля.

int l_onoff; /* Признак, включена ли опция блокирования */ /* при закрытии */

int l_linger; /* Длительность блокирования в секундах */ SO_RCVTIMEO

Длительность ожидания поступления данных при вводе. Значение - упомянутая выше структура типа timeval. Подразумевая длительность равна нулю. Если в течение специфицированного промежутка времени новых данных не поступило, операция ввода вернет число байт, меньше запрошенного, или завершится ошибкой EAGAIN.

SO_SNDTIMEO

Длительность ожидания отправки данных при выводе.

Не все из перечисленных опций могут быть переустановлены функцией setsockopt().


Исключение по понятным причинам составляют SO_ERROR, SO_TYPE, SO_ACCEPTCONN.

Аргумент level задает протокольный уровень опции. Уровню сокетов соответствует значение SOL_SOCKET, уровню TCP - IPPROTO_TCP.

Функция getpeername() (см. пример 11.27), во многом аналогичная рассмотренной выше функции getsockname(), позволяет опросить еще одну характеристику - адрес (имя) сокета, с которым установлено соединение.

#include <sys/socket.h> int getpeername (int sd, struct sockaddr *restrict address, socklen_t *restrict address_len);

Листинг 11.27. Описание функции getpeername().

После привязки сокета к локальному адресу, а также установления, при необходимости, соединения и задания значений опций, можно приступать к отправке и/или приему данных через сокет. Для этого служат функции, описания которых показаны в пример 11.28.

#include <sys/socket.h> ssize_t recvfrom (int sd, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len); ssize_t recv (int sd, void *buffer, size_t length, int flags); ssize_t recvmsg (int sd, struct msghdr *message, int flags); ssize_t sendto (int sd, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len); ssize_t send (int sd, const void *buffer, size_t length, int flags); ssize_t sendmsg (int sd, const struct msghdr *message, int flags);

Листинг 11.28. Описание функций обмена данными через сокет.

Функция recvfrom() позволяет прочитать данные (в рассматриваемом контексте называемые также сообщением) из сокета с дескриптором sd и поместить их в буфер buffer длины length. Обычно функцию recvfrom() применяют к сокетам, ориентированным на режим без установления соединения, поскольку она выдает исходный адрес в структуре типа sockaddr.

В число возможных составляющих значения аргумента flags входят следующие флаги.

MSG_PEEK

Не удалять прочитанные данные. Следующий вызов recvfrom() или другой функции ввода снова вернет их.

MSG_WAITALL



Для сокетов типа SOCK_STREAM флаг означает, что вызывающий процесс блокируется до получения всего запрошенного объема данных (а не до прихода первого сообщения).

MSG_OOB

Запрашиваются экстренные данные. Трактовка этого понятия зависит от протокола.

Как и положено функции ввода, в результате recvfrom() возвращает количество прочитанных и помещенных в буфер данных. Для сокетов, учитывающих границы сообщений (SOCK_RAW, SOCK_DGRAM, SOCK_SEQPACKET), за одно обращение читается все сообщение; если оно не помещается в буфер, лишние байты (в отсутствие флага MSG_PEEK) отбрасываются.

Формально можно считать, что функция recv() эквивалентна recvfrom() с нулевым значением аргумента address_len. Поскольку она не позволяет узнать исходный адрес, ее обычно используют для сокетов, установивших соединение. (Можно провести и еще одну аналогию: если пренебречь флагами, функция recv() эквивалентна read().)

По сравнению с recv(), более содержательным аналогом функции recvfrom() является recvmsg(). Отличия в данном случае носят скорее синтаксический характер и по сути сводятся к способу передачи входных значений и возврата результатов: для минимизации числа аргументов функция recvmsg() использует структуру типа msghdr, которая, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля.

void *msg_name; /* Указатель на буфер для адреса */ /* (исходного или целевого) */

socklen_t msg_namelen; /* Размер буфера для адреса */

struct iovec *msg_iov; /* Массив для разнесения/сборки сообщений */

int msg_iovlen; /* Число элементов в массиве */ /* для разнесения/сборки сообщений */

void *msg_control; /* Указатель на буфер */ /* для вспомогательных данных */

socklen_t msg_controllen; /* Размер буфера для вспомогательных данных */

int msg_flags; /* Флаги принятого сообщения */

Если для сокета, специфицированного дескриптором sd, соединение не установлено, в заданный указателем msg_name буфер длины msg_namelen помещается исходный адрес сообщения; если этого не нужно, указатель msg_name можно сделать пустым.


При установлении соединения оба поля игнорируются.

Массив msg_iov совместно с полем msg_iovlen задает набор буферов для размещения принимаемых данных. Структура типа iovec определяется в заголовочном файле <sys/uio.h> и содержит по крайней мере два поля.

void *iov_base; /* Адрес области памяти (буфера) */

size_t iov_len; /* Размер области памяти (в байтах) */

Заданные полем msg_iov области памяти по очереди заполняются поступающими данными, пока не будут размещены все принятые данные или не заполнятся все буфера.

Трактовка вспомогательных данных (поля msg_control и msg_controllen структуры типа msghdr) в стандарте POSIX-2001 весьма туманна. Описаны лишь средства доступа к ним. Мы, однако, не будем на этом останавливаться.

В случае успешного завершения функции recvmsg() в поле msg_flags могут быть установлены следующие флаги.

MSG_EOR

Достигнута граница записи (если это понятие поддерживается протоколом).

MSG_OOB

Получены экстренные данные.

MSG_TRUNC

Пришлось урезать обычные данные.

MSG_CTRUNC

Пришлось урезать управляющие данные.

Подобно тому, как write() составляет пару read(), функцию sendto() можно считать парной по отношению к recvfrom(). Она предназначена для отправки через сокет sd сообщения, заданного аргументами message и length, по адресу dest_addr длины dest_len. Если для сокета установлено соединение, целевой адрес dest_addr игнорируется.

В число возможных составляющих значения аргумента flags входят следующие флаги.

MSG_EOR

Обозначает границу записи (если это понятие поддерживается протоколом).

MSG_OOB

Отправляются экстренные данные.

Разумеется, успешное завершение функции sendto() не гарантирует доставку сообщения. Результат, равный -1, указывает только на локально выявляемые ошибки.

Функция send() - пара для recv() со всеми следующими из этого обстоятельства эквивалентностями и аналогиями.

Для функции sendmsg() структура типа msghdr, на которую указывает аргумент message, является входной (что показывает спецификатор const).В поле msg_name задается целевой адрес. Поле msg_flags игнорируется.

Для завершения операций приема и/или отправки данных через сокет служит функция shutdown() (см. пример 11.29).

#include <sys/socket.h> int shutdown (int sd, int how);

Листинг 11.29. Описание функции shutdown().

Значение аргумента how показывает, что именно завершается: SHUT_RD прекращает прием, SHUT_WR - отправку, SHUT_RDWR - и то, и другое.


Опрос данных о сети


Данные о хостах как узлах сети хранятся в сетевой базе, последовательный доступ к которой обслуживается функциями sethostent(), gethostent() и endhostent() (см. пример 11.1).

#include <netdb.h> void sethostent (int stayopen); struct hostent *gethostent (void); void endhostent (void);

Листинг 11.1. Описание функций последовательного доступа к сетевой базе данных о хостах - узлах сети. (html, txt)

Функция sethostent() устанавливает соединение с базой, остающееся открытым после вызова gethostent(), если значение аргумента stayopen отлично от нуля. Функция gethostent() последовательно читает элементы базы, возвращая результат в структуре типа hostent, содержащей по крайней мере следующие поля.

char *h_name; /* Официальное имя хоста */ char **h_aliases; /* Массив указателей на альтернативные */ /* имена хоста, завершаемый пустым */ /* указателем */ int h_addrtype; /* Тип адреса хоста */ int h_length; /* Длина в байтах адреса данного типа */ char **h_addr_list; /* Массив указателей на сетевые адреса */ /* хоста, завершаемый пустым указателем */

Функция endhostent() закрывает соединение с базой.

В пример 11.2 показана программа, осуществляющая последовательный просмотр сетевой базы данных о хостах - узлах сети, а в пример 11.3 приведен фрагмент ее возможной выдачи.

Листинг 11.2. Пример программы, осуществляющей последовательный доступ к сетевой базе данных о хостах - узлах сети. (html, txt)

Листинг 11.3. Фрагмент возможных результатов работы программы, осуществляющей последовательный доступ к сетевой базе данных о хостах - узлах сети. (html, txt)

К рассматриваемой базе возможен и случайный доступ по ключам - именам и адресам хостов с помощью функций gethostbyname() и gethostbyaddr(), однако они считаются устаревшими и из новой версии стандарта POSIX могут быть исключены. Вместо них предлагается использовать функции getnameinfo() и getaddrinfo() (см. пример 11.4).

#include <sys/socket.h> #include <netdb.h>

void freeaddrinfo (struct addrinfo *ai);


int getaddrinfo (const char *restrict nodename, const char * restrict servname, const struct addrinfo *restrict hints, struct addrinfo **restrict res);

int getnameinfo (const struct sockaddr *restrict sa, socklen_t salen, char *restrict node, socklen_t nodelen, char *restrict service, socklen_t servicelen, int flags);

Листинг 11.4. Описание функций freeaddrinfo(), getaddrinfo(), getnameinfo(). (html, txt)

Функция getaddrinfo() позволяет по имени узла сети (хоста) (аргумент nodename) и/или имени сетевого сервиса (servname) получить набор адресов сокетов и ассоциированную информацию, что дает возможность создать сокет для обращения к заданному сервису.

Если аргумент nodename отличен от пустого указателя, он способен задавать описательное имя или адресную цепочку. Для адресных семейств   AF_INET и AF_UNSPEC (см. ниже описание аргумента hints) именем может служить имя хоста, а адресной цепочкой - стандартные для Internet адреса в точечных обозначениях (например, 193.232.173.17).

При пустом значении nodename подразумевается хост, локальный для вызывающего процесса.

Аргумент servname может задавать имя сервиса или (для адресных семейств   AF_INET и AF_UNSPEC) десятичный номер порта. Пустое значение servname означает запрос сетевого адреса.

Аргумент hints позволяет передать дополнительную информацию об опрашиваемом сервисе - адресное семейство, тип сокета, протокол, флаги. Согласно стандарту, структура addrinfo, описанная в заголовочном файле <netdb.h>, должна содержать по крайней мере следующие поля.

int ai_flags; /* Входные флаги */

int ai_family; /* Адресное семейство сокета */

int ai_socktype; /* Тип сокета */

int ai_protocol; /* Протокол сокета */

socklen_t ai_addrlen; /* Длина адреса сокета */

struct sockaddr *ai_addr; /* Адрес сокета */

char *ai_canonname; /* Официальное имя узла сети */

struct addrinfo *ai_next; /* Указатель на следующий элемент списка */

При обращении к функции getaddrinfo() все поля структуры addrinfo, на которую указывает аргумент hints, кроме первых четырех (ai_flags, ai_family, ai_socktype, ai_protocol), должны быть нулевыми или равными NULL.


Значение AF_UNSPEC в поле ai_family подразумевает, что вызывающего устроит любое адресное семейство. Аналогичный смысл имеют нулевые значения полей ai_socktype и ai_protocol. При hints, равном NULL, подразумевается AF_UNSPEC для ai_family и нулевые значения для других полей.

Из флагов, которые могут быть установлены в поле ai_flags, упомянем следующие.

AI_PASSIVE

Если значение аргумента nodename равно NULL, этот флаг игнорируется. В противном случае, если он указан, будет возвращен адрес сокета, предназначенного для принятия входящих соединений.

AI_CANONNAME

Данный флаг предписывает выяснить официальное имя узла сети.

AI_NUMERICHOST

Флаг означает, что хост задан адресной цепочкой, и не допускает использования какого-либо сервиса имен.

AI_NUMERICSERV

Флаг помечает, что сервис (аргумент servname) задан номером порта, и налагает запрет на обращение к какому-либо сервису имен.



AI_NUMERICSERV

Флаг помечает, что сервис ( аргумент servname) задан номером порта, и налагает запрет на обращение к какому-либо сервису имен.

Признаком успешного завершения функции getaddrinfo() является нулевой результат. В таком случае выходной аргумент res будет ссылаться на указатель на список структур типа addrinfo (связанных полем ai_next) - принадлежащие им значения полей ai_family, ai_socktype, ai_protocol пригодны для создания подходящих сокетов с помощью функции socket(), а значения ai_addr и ai_addrlen, в зависимости от флага AI_PASSIVE, могут служить аргументами функций connect() или bind(), применяемых к созданному сокету.

Функция freeaddrinfo() позволяет освободить память, занятую списком структур типа addrinfo и ассоциированными данными.

Функцию getnameinfo() можно считать обратной по отношению к getaddrinfo(). Аргумент sa - входной, он задает транслируемый адрес сокета (salen - размер структуры sockaddr). Аргументы node и service - выходные, задающие адреса областей памяти, куда помещаются, соответственно, имя узла и сервиса; размеры этих областей ограничены значениями nodelen и servicelen.

По умолчанию предполагается, что сокет имеет тип SOCK_STREAM, а кроме того, возвращается полное доменное имя хоста. Аргумент flags позволяет изменить подразумеваемое поведение. Если задан флаг NI_DGRAM, сокет считается датаграммным. При установленном флаге NI_NOFQDN возвращается короткое имя узла. Флаги NI_NUMERICHOST и NI_NUMERICSERV предписывают возвращать числовые цепочки для адресов хоста и сервиса, соответственно.

Обратим внимание на несколько технических деталей. При работе с адресами сокетов вместо родовой структуры типа sockaddr обычно используются более специализированные (описанные в заголовочном файле <netinet/in.h>) - sockaddr_in для адресного семейства   AF_INET (IPv4) и sockaddr_in6 для AF_INET6 (IPv6). Первая из них, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля (все с сетевым порядком байт).

sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* Номер порта */ struct in_addr sin_addr; /* IP-адрес */



Структура типа sockaddr_in6 устроена несколько сложнее; мы не будем ее рассматривать.

Структуры типов sockaddr и sockaddr_in (или sockaddr_in6) мысленно накладываются друг на друга, а преобразование типов между ними выполняется по мере необходимости. Отметим также, что тип in_port_t эквивалентен uint16_t, структура типа in_addr содержит по крайней мере одно поле:

in_addr_t s_addr; тип in_addr_t эквивалентен uint32_t.

Техническую роль играют и функции преобразования IP-адресов из текстового представления в числовое и наоборот (см. пример 11.5).

#include <arpa/inet.h> in_addr_t inet_addr (const char *cp); char *inet_ntoa (struct in_addr in); int inet_pton (int af, const char *restrict src, void *restrict dst); const char *inet_ntop (int af, const void *restrict src, char *restrict dst, socklen_t size);

Листинг 11.5. Описание функций преобразования IP-адресов из текстового представления в числовое и наоборот.

Первые две функции манипулируют только адресами IPv4: inet_addr() преобразует текстовую цепочку cp (адрес в стандартных точечных обозначениях) в пригодное для использования в качестве IP-адреса целочисленное значение, inet_ntoa() выполняет обратное преобразование.

Вторая пара функций по сути аналогична первой, но имеет чуть более общий характер, так как способна преобразовывать адреса в формате IPv6. Первый аргумент этих функций, af, задает адресное семейство: AF_INET для IPv4 и AF_INET6 для IPv6. Буфер, на который указывает аргумент dst функции inet_pton() (в него помещается результат преобразования - IP-адрес в числовой двоичной форме с сетевым порядком байт), должен иметь длину не менее 32 бит для адресов IPv4 и 128 бит для IPv6.

Аргумент src функции inet_ntop(), возвращающей текстовое представление, указывает на буфер с IP-адресом в числовой форме с сетевым порядком байт. Аргумент size задает длину выходного буфера, на него указывает аргумент dst. Подходящими значениями, в зависимости от адресного семейства, могут служить INET_ADDRSTRLEN или INET6_ADDRSTRLEN.



Выше было отмечено, что преобразование значений типов uint16_t и uint32_t из хостового порядка байт в сетевой выполняется посредством функций htons() и htonl(); функции ntohs() и ntohl() осуществляют обратную операцию (см. пример 11.6).

#include <arpa/inet.h> uint32_t htonl (uint32_t hostlong); uint16_t htons (uint16_t hostshort); uint32_t ntohl (uint32_t netlong); uint16_t ntohs (uint16_t netshort);

Листинг 11.6. Описание функций преобразования целочисленных значений из хостового порядка байт в сетевой и наоборот.

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

#include <stdio.h> #include <netdb.h> #include <arpa/inet.h>

int main (void) { struct addrinfo hints = {AI_CANONNAME, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; struct addrinfo *addr_res;

if (getaddrinfo ("www", "http", &hints, &addr_res) != 0) { perror ("GETADDRINFO"); } else { printf ("Результаты для сервиса http\n"); /* Пройдем по списку возвращенных структур */ do { printf ("Адрес сокета: Порт: %d IP-адрес: %s\n", ntohs (((struct sockaddr_in *) addr_res->ai_addr)->sin_port), inet_ntoa (((struct sockaddr_in *) addr_res->ai_addr)->sin_addr)); printf ("Официальное имя хоста: %s\n", addr_res->ai_canonname); } while ((addr_res = addr_res->ai_next) != NULL); }

return 0; }

Листинг 11.7. Пример программы, использующей функцию getaddrinfo().

Результаты для сервиса http Адрес сокета: Порт: 80 IP-адрес: 193.232.173.1 Официальное имя хоста: t01

Листинг 11.8. Возможный результат работы программы, использующей функцию getaddrinfo().

Завершая изложение серии технических моментов, укажем, что полезным дополнением к функциям getaddrinfo() и getnameinfo() является функция gai_strerror() (см. пример 11.9). Она возвращает текстовую цепочку, расшифровывающую коды ошибок, перечисленные в заголовочном файле <netdb.h>.


Стандарт POSIX- 2001 специфицирует следующие коды ошибок, имена которых говорят сами за себя: EAI_AGAIN, EAI_BADFLAGS, EAI_FAIL, EAI_FAMILY, EAI_MEMORY, EAI_NONAME, EAI_OVERFLOW, EAI_SERVICE, EAI_SOCKTYPE, EAI_SYSTEM.

#include <netdb.h> const char *gai_strerror (int ecode);

Листинг 11.9. Описание функции gai_strerror().

Если немного модифицировать приведенную выше программу (в пример 11.10 показан измененный фрагмент, где имя сервиса - "HTTP" - задано большими буквами), то в стандартный протокол с помощью функции gai_strerror() будет выдано содержательное диагностическое сообщение (см. пример 11.11). Отметим, что выдача функции perror() в данном случае невразумительна.

. . . int res;

if ((res = getaddrinfo ("www", "HTTP", &hints, &addr_res)) != 0) { perror ("GETADDRINFO"); fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); } else { printf ("Результаты для сервиса HTTP\n"); . . .

Листинг 11.10. Модифицированный фрагмент программы, использующей функции getaddrinfo() и gai_strerror().

GETADDRINFO: No such file or directory GETADDRINFO: Servname not supported for ai_socktype

Листинг 11.11. Диагностические сообщения от функций perror() и gai_strerror(), выданные по результатам работы функции getaddrinfo().

Наряду с базой данных хостов (узлов сети) поддерживается база данных сетей с аналогичной логикой работы и набором функций (см. пример 11.12).

#include <netdb.h> void setnetent (int stayopen); struct netent *getnetent (void); struct netent *getnetbyaddr (uint32_t net, int type); struct netent *getnetbyname (const char *name); void endnetent (void);

Листинг 11.12. Описание функций доступа к базе данных сетей.

Функция getnetent() обслуживает последовательный доступ к базе, getnetbyaddr() осуществляет поиск по адресному семейству (аргумент type) и номеру net сети, а getnetbyname() выбирает сеть с заданным (официальным) именем. Структура типа netent, указатель на которую возвращается в качестве результата этих функций, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля.



char *n_name; /* Официальное имя сети */

char **n_aliases; /* Массив указателей на альтернативные */ /* имена сети, завершаемый пустым указателем */

int n_addrtype; /* Адресное семейство (тип адресов) сети */

uint32_t n_net; /* Номер сети (в хостовом порядке байт) */

Точно такой же программный интерфейс предоставляет база данных сетевых протоколов (см. пример 11.13).

#include <netdb.h> void setprotoent (int stayopen); struct protoent *getprotoent (void); struct protoent *getprotobyname (const char *name); struct protoent *getprotobynumber (int proto); void endprotoent (void);

Листинг 11.13. Описание функций доступа к базе данных сетевых протоколов.

Структура типа protoent содержит по крайней мере следующие поля.

char *p_name; /* Официальное имя протокола */

char **p_aliases; /* Массив указателей на альтернативные */ /* имена протокола, завершаемый пустым */ /* указателем */

int p_proto; /* Номер протокола */

В пример 11.14 показан пример программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов. пример 11.15 содержит фрагмент возможных результатов работы этой программы.

#include <stdio.h> #include <netdb.h>

int main (void) { struct protoent *pht; char *pct; int i;

setprotoent (1);

while ((pht = getprotoent ()) != NULL) { printf ("Официальное имя протокола: %s\n", pht->p_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->p_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер протокола: %d\n\n", pht->p_proto); }

if ((pht = getprotobyname ("ipv6")) != NULL) { printf ("Номер протокола ipv6: %d\n\n", pht->p_proto); } else { fprintf (stderr, "Протокол ip в базе не найден\n"); }

if ((pht = getprotobyname ("IPV6")) != NULL) { printf ("Номер протокола IPV6: %d\n\n", pht->p_proto); } else { fprintf (stderr, "Протокол IPV6 в базе не найден\n"); }

endprotoent ();



return 0; }

Листинг 11.14. Пример программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов.

Официальное имя протокола: ip Альтернативные имена: IP Номер протокола: 0

Официальное имя протокола: icmp Альтернативные имена: ICMP Номер протокола: 1

. . .

Официальное имя протокола: tcp Альтернативные имена: TCP Номер протокола: 6

. . .

Официальное имя протокола: udp Альтернативные имена: UDP Номер протокола: 17

. . .

Официальное имя протокола: ipv6 Альтернативные имена: IPv6 Номер протокола: 41

. . .

Официальное имя протокола: ipv6-crypt Альтернативные имена: IPv6-Crypt Номер протокола: 50

. . .

Официальное имя протокола: visa Альтернативные имена: VISA Номер протокола: 70

. . .

Официальное имя протокола: iso-ip Альтернативные имена: ISO-IP Номер протокола: 80

. . .

Официальное имя протокола: sprite-rpc Альтернативные имена: Sprite-RPC Номер протокола: 90

. . .

Официальное имя протокола: ipx-in-ip Альтернативные имена: IPX-in-IP Номер протокола: 111

. . .

Официальное имя протокола: fc Альтернативные имена: FC Номер протокола: 133

Номер протокола ipv6: 41

Протокол IPV6 в базе не найден

Листинг 11.15. Фрагмент возможных результатов работы программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов.

Еще одно проявление той же логики работы - база данных сетевых сервисов (см. пример 11.16).

#include <netdb.h> void setservent (int stayopen); struct servent *getservent (void); struct servent *getservbyname (const char *name, const char *proto); struct servent *getservbyport (int port, const char *proto); void endservent (void);

Листинг 11.16. Описание функций доступа к базе данных сетевых сервисов.

Обратим внимание на то, что в данном случае можно указывать второй аргумент поиска - имя протокола. Впрочем, значение аргумента proto может быть пустым указателем, и тогда поиск производится только по имени сервиса (функция getservbyname()) или номеру порта ( getservbyport()), который должен быть задан с сетевым порядком байт.



Структура типа servent содержит по крайней мере следующие поля.

char *s_name; /* Официальное имя сервиса */

char **s_aliases; /* Массив указателей на альтернативные */ /* имена сервиса, завершаемый пустым */ /* указателем */

int s_port; /* Номер порта, соответствующий сервису */ /* (в сетевом порядке байт) */

char *s_proto; /* Имя протокола для взаимодействия с */ /* сервисом */

В пример 11.17 приведен пример программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт. В пример 11.18 показан фрагмент возможных результатов работы этой программы.

#include <stdio.h> #include <netdb.h>

int main (void) { struct servent *pht; char *pct; int i;

setservent (1);

while ((pht = getservent ()) != NULL) { printf ("Официальное имя сервиса: %s\n", pht->s_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port)); printf ("Имя протокола: %s\n\n", pht->s_proto); }

if ((pht = getservbyport (htons ((in_port_t) 21), "udp")) != NULL) { printf ("Официальное имя сервиса: %s\n", pht->s_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port)); printf ("Имя протокола: %s\n\n", pht->s_proto); } else { perror ("GETSERVBYPORT"); }

if ((pht = getservbyport (htons ((in_port_t) 21), (char *) NULL)) != NULL) { printf ("Официальное имя сервиса: %s\n", pht->s_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port)); printf ("Имя протокола: %s\n\n", pht->s_proto); } else { perror ("GETSERVBYPORT"); }



endservent ();

return 0; }

Листинг 11.17. Пример программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт.

. . .

Официальное имя сервиса: ftp-data Альтернативные имена: Номер порта: 20 Имя протокола: tcp

Официальное имя сервиса: ftp-data Альтернативные имена: Номер порта: 20 Имя протокола: udp

Официальное имя сервиса: ftp Альтернативные имена: Номер порта: 21 Имя протокола: tcp

Официальное имя сервиса: ftp

Альтернативные имена: fsp fspd Номер порта: 21 Имя протокола: udp

. . .

Официальное имя сервиса: kerberos Альтернативные имена: kerberos5 krb5 Номер порта: 88 Имя протокола: tcp

Официальное имя сервиса: kerberos Альтернативные имена: kerberos5 krb5 Номер порта: 88 Имя протокола: udp

. . .

Официальное имя сервиса: auth Альтернативные имена: authentication tap ident Номер порта: 113 Имя протокола: tcp

Официальное имя сервиса: auth Альтернативные имена: authentication tap ident Номер порта: 113 Имя протокола: udp

. . .

Официальное имя сервиса: printer Альтернативные имена: spooler Номер порта: 515 Имя протокола: tcp

Официальное имя сервиса: printer Альтернативные имена: spooler Номер порта: 515 Имя протокола: udp

. . .

Официальное имя сервиса: fido Альтернативные имена: Номер порта: 60179 Имя протокола: tcp

Официальное имя сервиса: fido Альтернативные имена: Номер порта: 60179 Имя протокола: udp

Официальное имя сервиса: ftp Альтернативные имена: fsp fspd Номер порта: 21 Имя протокола: udp

Официальное имя сервиса: ftp Альтернативные имена: Номер порта: 21 Имя протокола: tcp

Листинг 11.18. Фрагмент возможных результатов работы программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт.

Отметим, что при поиске по ключу возвращается первый подходящий элемент базы данных. По этой причине, когда не был задан протокол (второе обращение к функции getservbyport()), в качестве результата был возвращен элемент с протоколом tcp.


Основные понятия и объекты


Стандарт POSIX-2001 определяет сеть как совокупность взаимосвязанных хостов. Тем самым предполагается, что сетевая инфраструктура остается скрытой от приложений, которым предоставляются высокоуровневые средства взаимодействия в распределенной среде.

Под сетевым адресом понимается видимый в пределах сети идентификатор, используемый для обозначения оконечных точек сети. Адреса есть у определенных оконечных точек хостов, могут они быть и у хостов в целом.

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

Процесс присвоения сетевого адреса оконечной точке называется связыванием, или привязкой, а обратное действие - освобождением, или отменой привязки.

Обычно оконечной точкой служит аппаратный сетевой интерфейс, посредством которого данные передаются и принимаются, однако с таким интерфейсом, как шлейфовый (loopback), никакой аппаратуры не ассоциировано.

Поддерживается база данных маршрутизации, используемая при выборе сетевого интерфейса для передачи порции данных (сетевого пакета).

Данные передаются по сети в виде последовательности октетов (восьмибитных беззнаковых величин). Если некоторый элемент данных (например, адрес или номер порта) состоит более чем из восьми бит, для его передачи и хранения требуется несколько октетов. Сетевым называется порядок октетов (байт), при котором первый (с наименьшим адресом) октет содержит старшие (наиболее значимые) биты.

Последовательности октетов - неудобный объект обработки на хостах, где предпочтительнее аппаратно поддерживаемые типы, в особенности целочисленные. Значения этих типов обычно хранятся с другим порядком байт, называемым хостовым, поэтому вполне возможно, что старшего бита не окажется в первом байте и вообще будет использоваться некое неочевидное распределение бит по байтам.

Для преобразования значений типов uint16_t и uint32_t из хостового порядка байт в сетевой служат функции htons() и htonl(); функции ntohs() и ntohl() осуществляют обратную операцию.


При взаимодействии процессов оконечными точками служат сокеты, они трактуются стандартом POSIX-2001 как отдельный тип файлов.

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

Основные описания, относящиеся к сокетам, сосредоточены в заголовочном файле <sys/socket.h>. Фигурирует в нем и упомянутая выше структура sockaddr для адреса сокета, которая должна содержать по крайней мере следующие поля.

sa_family_t sa_family; /* Адресное семейство */ char sa_data []; /* Адрес сокета (данные переменной длины) */

Адресное семейство соответствует конкретной среде взаимодействия. Стандарт POSIX-2001 определяет три таких семейства.

AF_UNIX

Адресное семейство UNIX поддерживает межпроцессное взаимодействие в пределах одной системы. Формально это можно считать вырожденным случаем сетевого взаимодействия. Описания, специфичные для данного семейства, содержатся в заголовочном файле <sys/un.h>.

AF_INET

Адресное семейство, поддерживающее взаимодействие по протоколам   IPv4. Специфичные для него описания располагаются в заголовочном файле <netinet/in.h>.

AF_INET6

Взаимодействие по протоколам IPv6 (необязательная возможность). За счет поддержки адресов IPv6, отображенных на IPv4, обеспечивается совместимость с приложениями, использующими IPv4. Применяемые эти адресным семейством описания распределены по заголовочным файлам <netinet/in.h>, <arpa/inet.h> и <netdb.h>.

В пределах каждого адресного семейства могут существовать сокеты нескольких типов. В стандарте POSIX-2001 их четыре.

SOCK_STREAM

Сокеты данного типа поддерживают надежные, упорядоченные, полнодуплексные потоки октетов   в режиме с установлением соединения.

SOCK_SEQPACKET

Аналог SOCK_STREAM с дополнительным сохранением границ между записями.



При взаимодействии процессов оконечными точками служат сокеты, они трактуются стандартом POSIX-2001 как отдельный тип файлов.

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

Основные описания, относящиеся к сокетам, сосредоточены в заголовочном файле <sys/socket.h>. Фигурирует в нем и упомянутая выше структура sockaddr для адреса сокета, которая должна содержать по крайней мере следующие поля.

sa_family_t sa_family; /* Адресное семейство */ char sa_data []; /* Адрес сокета (данные переменной длины) */

Адресное семейство соответствует конкретной среде взаимодействия. Стандарт POSIX-2001 определяет три таких семейства.

AF_UNIX

Адресное семейство UNIX поддерживает межпроцессное взаимодействие в пределах одной системы. Формально это можно считать вырожденным случаем сетевого взаимодействия. Описания, специфичные для данного семейства, содержатся в заголовочном файле <sys/un.h>.

AF_INET

Адресное семейство, поддерживающее взаимодействие по протоколам   IPv4. Специфичные для него описания располагаются в заголовочном файле <netinet/in.h>.

AF_INET6

Взаимодействие по протоколам IPv6 (необязательная возможность). За счет поддержки адресов IPv6, отображенных на IPv4, обеспечивается совместимость с приложениями, использующими IPv4. Применяемые эти адресным семейством описания распределены по заголовочным файлам <netinet/in.h>, <arpa/inet.h> и <netdb.h>.

В пределах каждого адресного семейства могут существовать сокеты нескольких типов. В стандарте POSIX-2001 их четыре.

SOCK_STREAM

Сокеты данного типа поддерживают надежные, упорядоченные, полнодуплексные потоки октетов   в режиме с установлением соединения.

SOCK_SEQPACKET

Аналог SOCK_STREAM с дополнительным сохранением границ между записями.


Описание функций последовательного доступа


#include <netdb.h> void sethostent (int stayopen); struct hostent *gethostent (void); void endhostent (void);
Листинг 11.1. Описание функций последовательного доступа к сетевой базе данных о хостах - узлах сети.
Закрыть окно




#include <stdio.h> #include <netdb.h>
int main (void) { struct hostent *pht; char *pct; int i, j;
sethostent (1);
while ((pht = gethostent ()) != NULL) { printf (" Официальное имя хоста: %s\n", pht->h_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->h_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Тип адреса хоста: %d\n", pht->h_addrtype); printf ("Длина адреса хоста: %d\n", pht->h_length); printf ("Сетевые адреса хоста:\n"); for (i = 0; (pct = pht->h_addr_list [i]) != NULL; i++) { for (j = 0; j < pht->h_length; j++) { printf (" %d", (unsigned char) pct [j]); } printf ("\n"); } }
endhostent ();
return 0; }
Листинг 11.2. Пример программы, осуществляющей последовательный доступ к сетевой базе данных о хостах - узлах сети.
Закрыть окно




Официальное имя хоста: localhost Альтернативные имена: Тип адреса хоста: 2 Длина адреса хоста: 4 Сетевые адреса хоста: 127 0 0 1
. . . Официальное имя хоста: t01 Альтернативные имена: niisi.msk.ru t01.niisi.msk.ru mail mailhost loghost server server3 server3.systud.msk.su www www.systud.msk.su t01.systud.msk.su Тип адреса хоста: 2 Длина адреса хоста: 4 Сетевые адреса хоста: 193 232 173 1
. . .
Официальное имя хоста: t17 Альтернативные имена: galatenko Тип адреса хоста: 2 Длина адреса хоста: 4 Сетевые адреса хоста: 193 232 173 17
. . .
Листинг 11.3. Фрагмент возможных результатов работы программы, осуществляющей последовательный доступ к сетевой базе данных о хостах - узлах сети.
Закрыть окно




#include <sys/socket.h> #include <netdb.h>
void freeaddrinfo (struct addrinfo *ai);
int getaddrinfo (const char *restrict nodename, const char * restrict servname, const struct addrinfo *restrict hints, struct addrinfo **restrict res);
int getnameinfo (const struct sockaddr *restrict sa, socklen_t salen, char *restrict node, socklen_t nodelen, char *restrict service, socklen_t servicelen, int flags);
Листинг 11.4. Описание функций freeaddrinfo(), getaddrinfo(), getnameinfo().
Закрыть окно




#include <arpa/inet.h> in_addr_t inet_addr (const char *cp); char *inet_ntoa (struct in_addr in); int inet_pton ( int af, const char *restrict src, void *restrict dst); const char *inet_ntop (int af, const void *restrict src, char *restrict dst, socklen_t size);
Листинг 11.5. Описание функций преобразования IP-адресов из текстового представления в числовое и наоборот.
Закрыть окно




#include <arpa/inet.h> uint32_t htonl (uint32_t hostlong); uint16_t htons (uint16_t hostshort); uint32_t ntohl (uint32_t netlong); uint16_t ntohs (uint16_t netshort);
Листинг 11.6. Описание функций преобразования целочисленных значений из хостового порядка байт в сетевой и наоборот.
Закрыть окно




#include <stdio.h> #include <netdb.h> #include <arpa/inet.h>
int main (void) { struct addrinfo hints = {AI_CANONNAME, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; struct addrinfo *addr_res;
if (getaddrinfo ("www", "http", &hints, &addr_res) != 0) { perror ("GETADDRINFO"); } else { printf ("Результаты для сервиса http\n"); /* Пройдем по списку возвращенных структур */ do { printf ("Адрес сокета: Порт: %d IP-адрес: %s\n", ntohs (((struct sockaddr_in *) addr_res->ai_addr)->sin_port), inet_ntoa (((struct sockaddr_in *) addr_res->ai_addr)->sin_addr)); printf ("Официальное имя хоста: %s\n", addr_res->ai_canonname); } while ((addr_res = addr_res->ai_next) != NULL); }
return 0; }
Листинг 11.7. Пример программы, использующей функцию getaddrinfo().
Закрыть окно




Результаты для сервиса http Адрес сокета: Порт: 80 IP-адрес: 193.232.173.1 Официальное имя хоста: t01
Листинг 11.8. Возможный результат работы программы, использующей функцию getaddrinfo().
Закрыть окно




#include <netdb.h> const char *gai_strerror (int ecode);
Листинг 11.9. Описание функции gai_strerror().
Закрыть окно




. . . int res;
if ((res = getaddrinfo ("www", "HTTP", &hints, &addr_res)) != 0) { perror ("GETADDRINFO"); fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); } else { printf ("Результаты для сервиса HTTP\n"); . . .
Листинг 11.10. Модифицированный фрагмент программы, использующей функции getaddrinfo() и gai_strerror().
Закрыть окно




GETADDRINFO: No such file or directory GETADDRINFO: Servname not supported for ai_socktype
Листинг 11.11. Диагностические сообщения от функций perror() и gai_strerror(), выданные по результатам работы функции getaddrinfo().
Закрыть окно




#include <netdb.h> void setnetent (int stayopen); struct netent *getnetent (void); struct netent *getnetbyaddr ( uint32_t net, int type); struct netent *getnetbyname (const char *name); void endnetent (void);
Листинг 11.12. Описание функций доступа к базе данных сетей.
Закрыть окно




#include <netdb.h> void setprotoent (int stayopen); struct protoent *getprotoent (void); struct protoent *getprotobyname (const char *name); struct protoent *getprotobynumber (int proto); void endprotoent (void);
Листинг 11.13. Описание функций доступа к базе данных сетевых протоколов.
Закрыть окно




#include <stdio.h> #include <netdb.h>
int main (void) { struct protoent *pht; char *pct; int i;
setprotoent (1);
while ((pht = getprotoent ()) != NULL) { printf (" Официальное имя протокола: %s\n", pht->p_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->p_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер протокола: %d\n\n", pht->p_proto); }
if ((pht = getprotobyname ("ipv6")) != NULL) { printf ("Номер протокола ipv6: %d\n\n", pht->p_proto); } else { fprintf (stderr, "Протокол ip в базе не найден\n"); }
if ((pht = getprotobyname ("IPV6")) != NULL) { printf ("Номер протокола IPV6: %d\n\n", pht->p_proto); } else { fprintf (stderr, "Протокол IPV6 в базе не найден\n"); }
endprotoent ();
return 0; }
Листинг 11.14. Пример программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов.
Закрыть окно




Официальное имя протокола: ip Альтернативные имена: IP Номер протокола: 0
Официальное имя протокола: icmp Альтернативные имена: ICMP Номер протокола: 1
. . .
Официальное имя протокола: tcp Альтернативные имена: TCP Номер протокола: 6
. . .
Официальное имя протокола: udp Альтернативные имена: UDP Номер протокола: 17
. . .
Официальное имя протокола: ipv6 Альтернативные имена: IPv6 Номер протокола: 41
. . .
Официальное имя протокола: ipv6-crypt Альтернативные имена: IPv6-Crypt Номер протокола: 50
. . .
Официальное имя протокола: visa Альтернативные имена: VISA Номер протокола: 70
. . .
Официальное имя протокола: iso-ip Альтернативные имена: ISO-IP Номер протокола: 80
. . .
Официальное имя протокола: sprite-rpc Альтернативные имена: Sprite-RPC Номер протокола: 90
. . .
Официальное имя протокола: ipx-in-ip Альтернативные имена: IPX-in-IP Номер протокола: 111
. . .
Официальное имя протокола: fc Альтернативные имена: FC Номер протокола: 133
Номер протокола ipv6: 41
Протокол IPV6 в базе не найден
Листинг 11.15. Фрагмент возможных результатов работы программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов.
Закрыть окно




#include <netdb.h> void setservent (int stayopen); struct servent *getservent (void); struct servent *getservbyname (const char *name, const char *proto); struct servent *getservbyport ( int port, const char *proto); void endservent (void);
Листинг 11.16. Описание функций доступа к базе данных сетевых сервисов.
Закрыть окно




#include <stdio.h> #include <netdb.h>
int main (void) { struct servent *pht; char *pct; int i;
setservent (1);
while ((pht = getservent ()) != NULL) { printf (" Официальное имя сервиса: %s\n", pht->s_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port)); printf ("Имя протокола: %s\n\n", pht->s_proto); }
if ((pht = getservbyport (htons ((in_port_t) 21), "udp")) != NULL) { printf ("Официальное имя сервиса: %s\n", pht->s_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port)); printf ("Имя протокола: %s\n\n", pht->s_proto); } else { perror ("GETSERVBYPORT"); }
if ((pht = getservbyport (htons ((in_port_t) 21), (char *) NULL)) != NULL) { printf ("Официальное имя сервиса: %s\n", pht->s_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port)); printf ("Имя протокола: %s\n\n", pht->s_proto); } else { perror ("GETSERVBYPORT"); }
endservent ();
return 0; }
Листинг 11.17. Пример программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт.
Закрыть окно




. . .
Официальное имя сервиса: ftp- data Альтернативные имена: Номер порта: 20 Имя протокола: tcp
Официальное имя сервиса: ftp-data Альтернативные имена: Номер порта: 20 Имя протокола: udp
Официальное имя сервиса: ftp Альтернативные имена: Номер порта: 21 Имя протокола: tcp
Официальное имя сервиса: ftp
Альтернативные имена: fsp fspd Номер порта: 21 Имя протокола: udp
. . .
Официальное имя сервиса: kerberos Альтернативные имена: kerberos5 krb5 Номер порта: 88 Имя протокола: tcp
Официальное имя сервиса: kerberos Альтернативные имена: kerberos5 krb5 Номер порта: 88 Имя протокола: udp
. . .
Официальное имя сервиса: auth Альтернативные имена: authentication tap ident Номер порта: 113 Имя протокола: tcp
Официальное имя сервиса: auth Альтернативные имена: authentication tap ident Номер порта: 113 Имя протокола: udp
. . .
Официальное имя сервиса: printer Альтернативные имена: spooler Номер порта: 515 Имя протокола: tcp
Официальное имя сервиса: printer Альтернативные имена: spooler Номер порта: 515 Имя протокола: udp
. . .
Официальное имя сервиса: fido Альтернативные имена: Номер порта: 60179 Имя протокола: tcp
Официальное имя сервиса: fido Альтернативные имена: Номер порта: 60179 Имя протокола: udp
Официальное имя сервиса: ftp Альтернативные имена: fsp fspd Номер порта: 21 Имя протокола: udp
Официальное имя сервиса: ftp Альтернативные имена: Номер порта: 21 Имя протокола: tcp
Листинг 11.18. Фрагмент возможных результатов работы программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт.
Закрыть окно




#include <sys/socket.h> int socket ( int af, int type, int protocol); int socketpair (int af, int type, int protocol, int sds [2]);
Листинг 11.19. Описание функций socket() и socketpair().
Закрыть окно




#include <sys/socket.h> int bind ( int sd, const struct sockaddr *address, socklen_t address_len);
Листинг 11.20. Описание функции bind().
Закрыть окно




#include <sys/socket.h> int getsockname ( int sd, struct sockaddr *restrict address, socklen_t *restrict address_len);
Листинг 11.21. Описание функции getsockname().
Закрыть окно




#include <sys/socket.h> int listen ( int sd, int backlog);
Листинг 11.22. Описание функции listen().
Закрыть окно




#include <sys/socket.h> int accept ( int sd, struct sockaddr *restrict address, socklen_t *restrict address_len);
Листинг 11.23. Описание функции accept().
Закрыть окно




#include <sys/socket.h> int connect ( int sd, const struct sockaddr *address, socklen_t address_len);
Листинг 11.24. Описание функции connect().
Закрыть окно




#include <sys/select.h> int pselect (int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set * restrict errorfds, const struct timespec *restrict timeout, const sigset_t *restrict sigmask); int select (int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout); void FD_CLR (int fd, fd_set *fdset); int FD_ISSET (int fd, fd_set *fdset); void FD_SET (int fd, fd_set *fdset); void FD_ZERO (fd_set *fdset);
Листинг 11.25. Описание функций семейства select*().
Закрыть окно




#include <sys/socket.h> int getsockopt ( int sd, int level, int option_name, void *restrict option_value, socklen_t *restrict option_len); int setsockopt (int sd, int level, int option_name, const void *option_value, socklen_t option_len);
Листинг 11.26. Описание функций getsockopt() и setsockopt().
Закрыть окно




#include <sys/socket.h> int getpeername ( int sd, struct sockaddr *restrict address, socklen_t *restrict address_len);
Листинг 11.27. Описание функции getpeername().
Закрыть окно




#include <sys/socket.h> ssize_t recvfrom (int sd, void * restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len); ssize_t recv (int sd, void *buffer, size_t length, int flags); ssize_t recvmsg (int sd, struct msghdr *message, int flags); ssize_t sendto (int sd, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len); ssize_t send (int sd, const void *buffer, size_t length, int flags); ssize_t sendmsg (int sd, const struct msghdr *message, int flags);
Листинг 11.28. Описание функций обмена данными через сокет.
Закрыть окно




#include <sys/socket.h> int shutdown ( int sd, int how);
Листинг 11.29. Описание функции shutdown().
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа копирует строки со стандартного ввода на стандартный вывод, */ /* "прокачивая" их через пару сокетов. */ /* Используются функции ввода/вывода нижнего уровня */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/socket.h> #include <sys/wait.h>
#define MY_PROMPT "Вводите строки\n" #define MY_MSG "Вы ввели: "
int main (void) { int sds [2]; char buf [1]; int new_line = 1; /* Признак того, что надо выдать сообщение MY_MSG */ /* перед отображением очередной строки */
/* Создадим пару соединенных безымянных сокетов */ if (socketpair (AF_UNIX, SOCK_STREAM, 0, sds) < 0) { perror ("SOCKETPAIR"); exit (1); }
switch (fork ()) { case -1: perror ("FORK"); exit (2); case 0: /* Чтение из сокета sds [0] и выдачу на стандартный вывод */ /* реализуем в порожденном процессе */ while (read (sds [0], buf, 1) == 1) { if (write (STDOUT_FILENO, buf, 1) != 1) { perror ("WRITE TO STDOUT"); break; } } exit (0); }
/* Чтение со стандартного ввода и запись в сокет sds [1] */ /* возложим на родительский процесс */ if (write (sds [1], MY_PROMPT, sizeof (MY_PROMPT) - 1) != sizeof (MY_PROMPT) - 1) { perror ("WRITE TO SOCKET-1"); }
while (read (STDIN_FILENO, buf, 1) == 1) { /* Перед отображением очередной строки */ /* нужно выдать сообщение MY_MSG */ if (new_line) { if (write (sds [1], MY_MSG, sizeof (MY_MSG) - 1) != sizeof (MY_MSG) - 1) { perror ("WRITE TO SOCKET-2"); break; } } if (write (sds [1], buf, 1) != 1) { perror ("WRITE TO SOCKET-3"); break; } new_line = (buf [0] == '\n'); } shutdown (sds [1], SHUT_WR);
(void) wait (NULL); return (0); }
Листинг 11.30. Пример программы, использующей сокеты адресного семейства AF_UNIX.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса, читающего строки со стандартного ввода */ /* и посылающего их в виде датаграмм другому процессу */ /* (будем называть его сервером), */ /* который должно выдать их на свой стандартный вывод. */ /* Имя серверного хоста - аргумент командной строки */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h> #include <netdb.h> #include <sys/socket.h> #include <string.h>
#define MY_PROMPT "Вводите строки\n"
int main (int argc, char *argv []) { int sd; /* Дескриптор передающего сокета */ char line [LINE_MAX]; /* Буфер для копируемых строк */ /* Структура - аргумент sendmsg */ struct msghdr msg = {NULL, 0, NULL, 0, NULL, 0, 0}; struct iovec iovbuf; /* Структура для сборки отправляемых данных */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {0, AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ int msg_len; /* Длина очередной введенной строки, */ /* включая завершающий нулевой байт */
if (argc != 2) { fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]); return (1); }
/* Создадим сокет, через который будем отправлять */ /* прочитанные строки */ if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { perror ("SOCKET"); return (2); }
/* Выясним целевой адрес для датаграмм */ /* Воспользуемся портом для сервиса spooler */ if ((res = getaddrinfo (argv [1], "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (3); }
/* Заполним структуру msghdr */ msg.msg_name = addr_res->ai_addr; msg.msg_namelen = addr_res->ai_addrlen; msg.msg_iov = &iovbuf; msg.msg_iovlen = 1; iovbuf.iov_base = line;
/* Цикл чтения строк со стандартного ввода */ /* и отправки их через сокет в виде датаграмм */ fputs (MY_PROMPT, stdout); while (fgets (line, sizeof (line), stdin) != NULL) { msg_len = strlen (line) + 1; iovbuf.iov_len = msg_len; if (sendmsg (sd, &msg, 0) != msg_len) { perror ("SENDMSG"); break; } }
return (0); }
Листинг 11.31. Пример программы, использующей сокеты адресного семейства AF_INET для отправки датаграмм.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса (будем называть его сервером), */ /* читающего сообщения (строки) из датаграммного сокета */ /* и выдающего их на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h> #include <netdb.h> #include <sys/socket.h>
#define MY_MSG "Вы ввели: "
int main (void) { int sd; /* Дескриптор приемного сокета */ char line [LINE_MAX]; /* Буфер для копируемых строк */ /* Структура - аргумент recvmsg */ struct msghdr msg = {NULL, 0, NULL, 0, NULL, 0, 0}; struct iovec iovbuf; /* Структура для разнесения принимаемых данных */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */
/* Создадим сокет, через который будем принимать */ /* прочитанные строки */ if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { perror ("SOCKET"); return (1); }
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */ if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (2); } if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("BIND"); return (3); }
/* Можно освободить память, которую запрашивала функция getaddrinfo() */ freeaddrinfo (addr_res);
/* Заполним структуру msghdr */ msg.msg_iov = &iovbuf; msg.msg_iovlen = 1; iovbuf.iov_base = line; iovbuf.iov_len = sizeof (line);
/* Цикл приема и выдачи строк */ while (1) { if (recvmsg (sd, &msg, 0) < 0) { perror ("RECVMSG"); break; } fputs (MY_MSG, stdout); fputs (line, stdout); }
return (0); }
Листинг 11.32. Пример программы, использующей сокеты адресного семейства AF_INET для приема датаграмм.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса, читающего строки со стандартного ввода */ /* и посылающего их в виде датаграмм другому процессу */ /* (будем называть его сервером), */ /* который должно выдать их на свой стандартный вывод. */ /* Имя серверного хоста - аргумент командной строки */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h> #include <netdb.h> #include <sys/socket.h>
#define MY_PROMPT "Вводите строки\n"
int main (int argc, char *argv []) { int sd; /* Дескриптор передающего сокета */ FILE *ss; /* Поток, соответствующий передающему сокету */ char line [LINE_MAX]; /* Буфер для копируемых строк */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {0, AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */
if (argc != 2) { fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]); return (1); }
/* Создадим сокет, через который будем отправлять */ /* прочитанные строки */ if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { perror ("SOCKET"); return (2); }
/* Выясним целевой адрес для датаграмм */ /* Воспользуемся портом для сервиса spooler */ if ((res = getaddrinfo (argv [1], "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (3); }
/* Воспользуемся функцией connect() для достижения двух целей: */ /* фиксации целевого адреса и привязки к некоему локальному */ if (connect (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("CONNECT"); return (4); }
/* Сформирует поток по дескриптору сокета */ if ((ss = fdopen (sd, "w")) == NULL) { perror ("FDOPEN"); return (5); } /* Отменим буферизацию для этого потока */ setbuf (ss, NULL);
/* Цикл чтения строк со стандартного ввода */ /* и отправки их через сокет в виде датаграмм */ fputs (MY_PROMPT, stdout); while (fgets (line, sizeof (line), stdin) != NULL) { fputs (line, ss); }
return (0); }
Листинг 11.33. Модифицированный вариант программы, использующей сокеты адресного семейства AF_INET для отправки датаграмм.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса (будем называть его сервером), */ /* читающего сообщения из датаграммного сокета */ /* и копирующего их на стандартный вывод */ /* с указанием адреса, откуда они поступили */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h> #include <netdb.h> #include <sys/socket.h> #include <arpa/inet.h>
int main (void) { int sd; /* Дескриптор приемного сокета */ /* Буфер для принимаемых сообщений */ /* Оставлено место для вставки нулевого байта */ char lbuf [BUFSIZ + 1]; /* Структура - аргумент recvmsg */ struct msghdr msg = {NULL, 0, NULL, 0, NULL, 0, 0}; struct iovec iovbuf; /* Структура для разнесения принимаемых данных */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ /* Структура для исходного адреса датаграмм */ struct sockaddr_in sai; ssize_t lmsg; /* Длина принятой датаграммы */
/* Создадим сокет, через который будем принимать */ /* прочитанные строки */ if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { perror ("SOCKET"); return (1); }
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */ if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (1); } if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("BIND"); return (2); }
/* Можно освободить память, которую запрашивала функция getaddrinfo() */ freeaddrinfo (addr_res);
/* Заполним структуру msghdr */ msg.msg_name = &sai; msg.msg_namelen = sizeof (struct sockaddr_in); msg.msg_iov = &iovbuf; msg.msg_iovlen = 1; iovbuf.iov_base = lbuf; /* Оставим место для вставки нулевого байта */ iovbuf.iov_len = sizeof (lbuf) - 1;
/* Цикл приема и выдачи строк */ while (1) { if ((lmsg = recvmsg (sd, &msg, 0)) < 0) { perror ("RECVMSG"); break; } printf ("Вы ввели и отправили с адреса %s, порт %d :", inet_ntoa (((struct sockaddr_in *) msg.msg_name)->sin_addr), ntohs (((struct sockaddr_in *) msg.msg_name)->sin_port)); if (msg.msg_flags & MSG_TRUNC) { printf ("Датаграмма была урезана\n"); } lbuf [lmsg] = 0; fputs (lbuf, stdout); }
return (0); }
Листинг 11.34. Модифицированный вариант программы, использующей сокеты адресного семейства AF_INET для приема датаграмм.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса, читающего строки со стандартного ввода */ /* и посылающего их в виде потока другому процессу */ /* (будем называть его сервером), */ /* который должно выдать строки на свой стандартный вывод. */ /* Имя серверного хоста - аргумент командной строки */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h> #include <netdb.h> #include <sys/socket.h>
#define MY_PROMPT "Вводите строки\n"
int main (int argc, char *argv []) { int sd; /* Дескриптор передающего сокета */ FILE *ss; /* Поток, соответствующий передающему сокету */ char line [LINE_MAX]; /* Буфер для копируемых строк */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {0, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */
if (argc != 2) { fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]); return (1); }
/* Создадим сокет, через который будем отправлять */ /* прочитанные строки */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (2); }
/* Выясним целевой адрес для соединения */ /* Воспользуемся портом для сервиса spooler */ if ((res = getaddrinfo (argv [1], "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (3); }
/* Воспользуемся функцией connect() для достижения двух целей: */ /* установления соединения и привязки к некоему локальному адресу */ if (connect (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("CONNECT"); return (4); }
/* Сформируем поток по дескриптору сокета */ if ((ss = fdopen (sd, "w")) == NULL) { perror ("FDOPEN"); return (5); } /* Отменим буферизацию для этого потока */ setbuf (ss, NULL);
/* Цикл чтения строк со стандартного ввода */ /* и отправки их через сокет в виде потока */ fputs (MY_PROMPT, stdout); while (fgets (line, sizeof (line), stdin) != NULL) { fputs (line, ss); } shutdown (sd, SHUT_WR);
return (0); }
Листинг 11.35. Пример программы, использующей режим с установлением соединения и сокеты типа SOCK_STREAM для пересылки строк.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса (будем называть его демоном), */ /* принимающего запросы на установления соединения */ /* и запускающего процессы для их обслуживания */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <netdb.h> #include <sys/socket.h> #include <sys/wait.h>
int main (void) { int sd; /* Дескриптор слушающего сокета */ int ad; /* Дескриптор приемного сокета */ /* Буфер для принимаемых строк */ struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */
/* Создадим слушающий сокет */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (1); }
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */ if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (1); } if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("BIND"); return (2); }
/* Можно освободить память, которую запрашивала функция getaddrinfo() */ freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */ if (listen (sd, SOMAXCONN) < 0) { perror ("LISTEN"); return (3); }
/* Цикл приема соединений и запуска обслуживающих процессов */ while (1) { /* Примем соединение. */ /* Адрес партнера по общению нас в данном случае не интересует */ if ((ad = accept (sd, NULL, NULL)) < 0) { perror ("ACCEPT"); return (4); }
/* Запустим обслуживающий процесс */ switch (fork ()) { case -1: perror ("FORK"); return (5); case 0: /* Сделаем сокет ad стандартным вводом */ (void) close (STDIN_FILENO); (void) fcntl (ad, F_DUPFD, STDIN_FILENO); (void) close (ad); /* Сменим программу процесса на обслуживающую */ if (execl ("./gsce", "gsce", (char *) NULL) < 0) { perror ("EXECL"); return (6); } }
/* В родительском процессе дескриптор принятого соединения нужно закрыть */ (void) close (ad);
/* Обслужим завершившиеся порожденные процессы */ while (waitpid ((pid_t) (-1), NULL, WNOHANG) > 0) ; }
return (0); }
Листинг 11.36. Пример программы, принимающей запросы на установление соединения.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа обслуживающего процесса, */ /* выдающего принимаемые строки на стандартный вывод. */ /* Дескриптор приемного сокета передан */ /* в качестве стандартного ввода */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdio.h> #include <sys/socket.h> #include <arpa/inet.h>
int main (void) { char line [LINE_MAX]; /* Буфер для принимаемых строк */ /* Структура для записи адреса */ struct sockaddr_in sai; /* Длина адреса */ socklen_t sai_len = sizeof (struct sockaddr_in);
/* Опросим адрес партнера по общению (передающего сокета) */ if (getpeername (STDIN_FILENO, (struct sockaddr *) &sai, &sai_len) < 0) { perror ("GETPEERNAME"); return (1); }
/* Цикл чтения строк из сокета */ /* и выдачи их на стандартный вывод */ while (fgets (line, sizeof (line), stdin) != NULL) { printf ("Вы ввели и отправили с адреса %s, порт %d :", inet_ntoa (sai.sin_addr), ntohs (sai.sin_port)); fputs (line, stdout); }
/* Закрытие соединения */ shutdown (STDIN_FILENO, SHUT_RD);
return (0); }
Листинг 11.37. Пример программы, обрабатывающей данные, поступающие через сокет типа SOCK_STREAM.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса (будем называть его демоном), */ /* принимающего запросы на установления соединения */ /* и запускающего процессы для их обслуживания */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <netdb.h> #include <sys/socket.h> #include <sys/wait.h> #include <signal.h> #include <errno.h>
/* Функция обработки сигнала SIGCHLD */ static void chldied (int dummy) { /* Вдруг число завершившихся потомков отлично от единицы? */ while (waitpid ((pid_t) (-1), NULL, WNOHANG) > 0) ; }
int main (void) {
int sd; /* Дескриптор слушающего сокета */ int ad; /* Дескриптор приемного сокета */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ /* Структура для задания реакции на сигнал SIGCHLD */ struct sigaction sact;
/* Создадим слушающий сокет */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (1); }
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */ if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (1); } if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("BIND"); return (2); }
/* Можно освободить память, которую запрашивала функция getaddrinfo() */ freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */ if (listen (sd, SOMAXCONN) < 0) { perror ("LISTEN"); return (3); }
/* Установим обработку сигнала о завершении потомка */ sact.sa_handler = chldied; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGCHLD, &sact, (struct sigaction *) NULL);
/* Цикл приема соединений и запуска обслуживающих процессов */ while (1) { /* Примем соединение с учетом того, что ожидание может быть прервано */ /* доставкой обрабатываемого сигнала. */ /* Адрес партнера по общению в данном случае нас не интересует */ while ((ad = accept (sd, NULL, NULL)) < 0) { if (errno != EINTR) { perror ("ACCEPT"); return (4); } }
/* Запустим обслуживающий процесс */ switch (fork ()) { case -1: perror ("FORK"); return (5); case 0: /* Сделаем сокет ad стандартным вводом */ (void) close (STDIN_FILENO); (void) fcntl (ad, F_DUPFD, STDIN_FILENO); (void) close (ad); /* Сменим программу процесса на обслуживающую */ if (execl ("./gsce", "gsce", (char *) NULL) < 0) { perror ("EXECL"); return (6); } }
/* В родительском процессе дескриптор принятого соединения нужно закрыть */ (void) close (ad); }
return (0); }
Листинг 11.38. Пример программы, принимающей запросы на установление соединения с учетом того, что ожидание может быть прервано доставкой обрабатываемого сигнала.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса (будем называть его сервером), */ /* принимающего запросы на установления соединения */ /* и обслуживающего их с использованием select() */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdio.h> #include <netdb.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/select.h>
/* Структура для хранения дескрипторов приемных сокетов */ /* и ассоциированной информации */ #define MY_MSG_LN 128 struct sads { int ad; FILE *sd; char my_msg [MY_MSG_LN]; };
/* Максимальное число параллельно обслуживаемых запросов */ #define MY_MAX_QS FD_SETSIZE
/* Функция для освобождения элемента массива */ /* дескрипторов приемных сокетов */ static void free_elem (struct sads *sadsp) { shutdown (sadsp->ad, SHUT_RD); close (sadsp->ad); sadsp->ad = -1; }
int main (void) { int sd; /* Дескриптор слушающего сокета */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ fd_set rdset; /* Набор дескрипторов для чтения */ /* Структура для записи адреса */ struct sockaddr_in sai; socklen_t sai_len; /* Длина адреса */ char line [LINE_MAX]; /* Буфер для принимаемых строк */ /* Массов для хранения дескрипторов приемных сокетов */ /* и ассоциированной информации. */ /* Последний элемент - фиктивный, */ /* нужен для упрощения поиска свободного */ struct sads sadsarr [MY_MAX_QS + 1]; int sads_max; /* Верхняя граница дескрипторов, проверяемых select() */ int i;
/* Создадим слушающий сокет */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (1); }
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */ if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (1); } if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("BIND"); return (2); }
/* Можно освободить память, которую запрашивала функция getaddrinfo() */ freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */ if (listen (sd, SOMAXCONN) < 0) { perror ("LISTEN"); return (3); }
/* Инициализируем массив sadsarr. */ /* -1 в поле ad означает, что элемент свободен */ for (i = 0; i < (MY_MAX_QS + 1); i++) { sadsarr [i].ad = -1; }
/* Цикл приема соединений и обслуживания запросов */ while (1) { /* Подготовим наборы дескрипторов для select() */ FD_ZERO (&rdset); FD_SET (sd, &rdset); sads_max = sd + 1; for (i = 0; i < MY_MAX_QS; i++) { if (sadsarr [i].ad >= 0) { FD_SET (sadsarr [i].ad, &rdset); if ((sadsarr [i].ad + 1) > sads_max) { sads_max = sadsarr [i].ad + 1; } } }
/* Подождем запроса на установление соединения */ /* или готовности данных для чтения. */ /* Время ожидания зададим как бесконечное */ if (select (sads_max, &rdset, NULL, NULL, NULL) == -1) { perror ("PSELECT"); return (4); }
/* Посмотрим, есть ли запросы, ждущие установления соединения */ if (FD_ISSET (sd, &rdset)) { /* Примем запрос на установление соединения, */ /* если есть свободный элемент массива дескрипторов. */ /* Последний элемент считаем фиктивным; он всегда свободен */ i = -1; while (sadsarr [++i].ad >= 0) ; if (i < MY_MAX_QS) { /* Свободный элемент нашелся */ sai_len = sizeof (struct sockaddr_in); if ((sadsarr [i].ad = accept (sd, (struct sockaddr *) &sai, &sai_len)) == -1) { perror ("ACCEPT"); continue; } /* Сформируем сообщение, выдаваемое перед принятой строкой */ (void) sprintf (sadsarr [i].my_msg, "Вы ввели и отправили с адреса %s, порт %d :", inet_ntoa (sai.sin_addr), ntohs (sai.sin_port)); /* Сформируем поток по дескриптору сокета */ /* и отменим буферизацию для этого потока */ if ((sadsarr [i].sd = fdopen (sadsarr [i].ad, "r")) == NULL) { perror ("FDOPEN"); free_elem (&sadsarr [i]); continue; } setbuf (sadsarr [i].sd, NULL); } }
/* Посмотрим, есть ли данные, готовые для чтения */ for (i = 0; i < MY_MAX_QS; i++) { if ((sadsarr [i].ad >= 0) & FD_ISSET (sadsarr [i].ad, &rdset)) { /* Есть данные, готовые для чтения, */ /* или установлен признак конца файла */ if (fgets (line, sizeof (line), sadsarr [i].sd) != NULL) { /* Выведем полученную строку */ fputs (sadsarr [i].my_msg, stdout); fputs (line, stdout); } else { /* Отправитель закрыл соединение. */ /* Закроем его и мы */ fclose (sadsarr [i].sd); free_elem (&sadsarr [i]); } } } }
return (0); }
Листинг 11.39. Пример программы, мультиплексирующей ввод через сокеты с помощью функции select().
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа вычисляет несколько первых строк треугольника Паскаля */ /* и отправляет их через сокет сервису spooler. */ /* Имя целевого хоста - аргумент командной строки. */ /* Передаваемые данные снабжаются однобайтными маркерами типа */ /* и кратности и преобразуются к сетевому порядку байт */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdio.h> #include <netdb.h> #include <sys/socket.h>
/* Количество вычисляемых строк треугольника Паскаля */ #define T_SIZE 16
/* Маркеры типов передаваемых данных */ #define T_UCHAR 1 #define T_UINT16 2 #define T_UINT32 4
#define T_HDR "\nТреугольник Паскаля:"
int main (int argc, char *argv []) { uint32_t tp [T_SIZE]; /* Массив для хранения текущей строки треугольника */ unsigned char mtl; /* Переменная для хранения маркеров типа и кратности */ uint32_t ntelem; /* Текущий элемент строки в сетевом порядке байт */ int sd; /* Дескриптор передающего сокета */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {0, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ int i, j;
if (argc != 2) { fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]); return (1); }
/* Создадим передающий сокет и установим соединение */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (2); }
if ((res = getaddrinfo (argv [1], "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (3); }
if (connect (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("CONNECT"); return (4); }
/* Инициализируем массив для хранения текущей строки треугольника Паскаля, */ /* чтобы далее все элементы можно было считать и передавать единообразно */ tp [0] = 1; for (i = 1; i < T_SIZE; i++) { tp [i] = 0; }
/* Передадим заголовок */ mtl = T_UCHAR; if (write (sd, &mtl, 1) != 1) { perror ("WRITE-1"); return (5); } mtl = sizeof (T_HDR) - 1; if (write (sd, &mtl, 1) != 1) { perror ("WRITE-2"); return (6); } if (write (sd, T_HDR, mtl) != mtl) { perror ("WRITE-3"); return (7); }
/* Вычислим и передадим строки треугольника Паскаля */ for (i = 0; i < T_SIZE; i++) { /* Элементы очередной строки нужно считать от конца к началу */ /* Элемент tp [0] пересчитывать не нужно */ for (j = i; j > 0; j--) { tp [j] += tp [j - 1]; } /* Вывод строки треугольника в сокет */ mtl = T_UINT32; if (write (sd, &mtl, 1) != 1) { perror ("WRITE-4"); return (8); } mtl = i + 1; if (write (sd, &mtl, 1) != 1) { perror ("WRITE-5"); return (9); } /* Преобразование элементов строки к сетевому порядку байт и вывод */ for (j = 0; j <= i; j++) { ntelem = htonl (tp [j]); if (write (sd, &ntelem, sizeof (ntelem)) != sizeof (ntelem)) { perror ("WRITE-6"); return (10); } } } shutdown (sd, SHUT_WR);
return (close (sd)); }
Листинг 11.40. Пример программы, передающей через сокеты целочисленные данные.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса (будем называть его наивным сервером), */ /* принимающего запросы на установления соединения */ /* и осуществляющего вывод поступающих числовых данных */ /* без порождения процессов и мультиплексирования */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdio.h> #include <netdb.h> #include <sys/socket.h> #include <arpa/inet.h>
/* Маркеры типов передаваемых данных */ #define T_UCHAR 1 #define T_UINT16 2 #define T_UINT32 4
int main (void) { int sd; /* Дескриптор слушающего сокета */ int ad; /* Дескриптор приемного сокета */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ /* Структура для записи адреса */ struct sockaddr_in sai; socklen_t sai_len; /* Длина адреса */ char mt; /* Маркер типа поступающих данных */ char ml; /* Маркер кратности поступающих данных */ char bufc; /* Буфер для приема символьных данных */ uint16_t buf16; /* Буфер для приема 16-разрядных целых */ uint32_t buf32; /* Буфер для приема 16-разрядных целых */
/* Создадим слушающий сокет */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (1); }
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */ if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (1); } if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("BIND"); return (2); }
/* Можно освободить память, которую запрашивала функция getaddrinfo() */ freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */ if (listen (sd, SOMAXCONN) < 0) { perror ("LISTEN"); return (3); }
/* Цикл приема соединений и обслуживания запросов на вывод */ while (1) { /* Примем соединение */ sai_len = sizeof (struct sockaddr_in); if ((ad = accept (sd, (struct sockaddr *) &sai, &sai_len)) < 0) { perror ("ACCEPT"); continue; }
/* Цикл приема поступающих данных и их вывода */ printf ("Получено с адреса %s, порт %d :", inet_ntoa (sai.sin_addr), ntohs (sai.sin_port)); while (read (ad, &mt, 1) == 1) { /* Есть очередная порция данных, начинающаяся с типа. */ /* Прочитаем кратность, потом сами данные */ if (read (ad, &ml, 1) != 1) { perror ("READ-1"); return (4); } while (ml-- > 0) { switch (mt) { case T_UCHAR: if (read (ad, &bufc, sizeof (bufc)) != sizeof (bufc)) { perror ("READ-2"); return (5); } printf ("%c", bufc); continue; case T_UINT16: if (read (ad, &buf16, sizeof (buf16)) != sizeof (buf16)) { perror ("READ-3"); return (6); } printf (" %d", ntohs (buf16)); continue; case T_UINT32: if (read (ad, &buf32, sizeof (buf32)) != sizeof (buf32)) { perror ("READ-4"); return (7); } printf (" %d", ntohl (buf32)); continue; } } /* Вывод порции завершим переводом строки */ printf ("\n"); }
/* Конец обслуживания соединения */ (void) close (ad); }
return (0); }
Листинг 11.41. Пример программы, принимающей через сокеты целочисленные данные.
Закрыть окно



Примеры программ работы с сокетами


Приводимые далее примеры строятся вокруг задачи копирования данных в распределенной программной конфигурации со стандартного ввода одного процесса на стандартный вывод другого, удаленного. Однако начнем мы с локального случая, обслуживаемого сокетами адресного семейства AF_UNIX (см. пример 11.30).

Листинг 11.30. Пример программы, использующей сокеты адресного семейства AF_UNIX. (html, txt)

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

Решим теперь ту же задачу для действительно распределенной конфигурации. Наше приложение будет состоять из двух процессов, выполняющихся на разных хостах. Первый процесс (см. пример 11.31) читает строки со стандартного ввода и отправляет их в виде датаграмм другому, который принимает датаграммы и выдает их содержимое на стандартный вывод (см. пример 11.32).

Листинг 11.31. Пример программы, использующей сокеты адресного семейства AF_INET для отправки датаграмм. (html, txt)

Листинг 11.32. Пример программы, использующей сокеты адресного семейства AF_INET для приема датаграмм. (html, txt)

Обратим внимание на несколько моментов. Адреса сокетов (целевого и приемного) получены при помощи функции getaddrinfo() с сервисом "spooler" в качестве аргумента. (Если на хосте этот сервис реально используется, для данного примера придется подыскать другой, свободный порт.) С практической точки зрения более правильным было бы пополнить базу данных сетевых сервисов новым элементом, специально предназначенным для представленного приложения, однако подобные административные действия находятся вне рамок стандарта POSIX и, следовательно, нами не рассматриваются. (Менее правильно, на наш взгляд, формировать адрес сокета покомпонентно, выбирая порт, по сути, случайным образом, поскольку это ведет к неявному, бессистемному, неконтролируемому пополнению базы сервисов.)

Для успешного вызова функции bind() процесс должен обладать соответствующими привилегиями.
Это может оказаться существенным для запуска программы, показанной в пример 11.32.
Чтобы получить от getaddrinfo() адрес, пригодный для использования в качестве аргумента ориентированных на прием функций (listen(), bind() для приемного сокета), необходимо указать флаг AI_PASSIVE во входной для getaddrinfo() структуре addrinfo (аргумент hints в описании функции getaddrinfo()). В таком случае для IP-адреса будет выдано значение INADDR_ANY, которое трактуется в адресном семействе AF_INET как адрес локального хоста, успешно сопоставляющийся как со шлейфовым (127.0.0.1), так и с реальным сетевыми адресами. В результате через этот сокет можно будет принимать датаграммы, посланные и с локального, и с удаленного хостов.
Цикл приема данных сервером сделан бесконечным, поскольку у последовательности датаграмм нет естественного признака конца.
Для передающего сокета не выполнялась привязка к локальному адресу, а в серверном процессе не запрашивался исходный адрес поступающих датаграмм - подход, возможно, слишком экономный, но непротиворечивый. Заметим, однако, что у представленного приложения есть неиспользованный идейный запас, состоящий в том, что сервер способен принимать датаграммы не только из одного, но и из нескольких источников. Чтобы задействовать этот потенциал, внесем в приложения модификации двух видов.
В программе, посылающей датаграммы, выполним привязку сокета к свободному локальному адресу, воспользовавшись для этого функцией connect() и, для оправдания затраченных усилий, изменим способ отправки: вместо универсальной sendmsg() задействуем даже не более простую функцию send(), а ее вполне привычный аналог write(), скрытый под личиной fputs().
Какой режим буферизации выбрать для потока, сформированного по открытому файловому дескриптору сокета, - дело вкуса. Читателю рекомендуется попробовать разные варианты, каждый из которых имеет свои "за" и "против". Второй вид модификаций касается серверного процесса. Поскольку теперь у датаграмм появился исходный адрес, его можно выяснить и выдать, а заодно опросить выходной флаг MSG_TRUNC, чтобы убедиться, что при пересылке в виде датаграмм копируемые строки не были урезаны.


Кроме того, изменение способа формирования отправляемых датаграмм привело к тому, что теперь их содержимое не завершается нулевым байтом; следовательно, способ вывода принимаемых данных также нуждается в модификации (в нашем случае - в дописывании нулевого байта).
Модифицированные варианты программ представлены в листингах пример 11.33 и пример 11.34.
Листинг 11.33. Модифицированный вариант программы, использующей сокеты адресного семейства AF_INET для отправки датаграмм. (html, txt)
Листинг 11.34. Модифицированный вариант программы, использующей сокеты адресного семейства AF_INET для приема датаграмм. (html, txt)
Читателю предлагается оценить степень надежности (точнее, ненадежности) доставки датаграмм, перенеправив стандартный ввод читающего строки процесса, в какой-либо большой текстовый файл. Кроме того, любопытно параллельно запустить несколько таких процессов и проследить за очередностью датаграмм, поступающих серверу.

Опрос и изменение данных о времени, ассоциированных с файлами


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

Для изменения ассоциированных с файлами данных о времени служит функция utime() (см. листинг 12.25), оперирующая в терминах секунд от начала отсчета.

#include <utime.h> int utime (const char *path, const struct utimbuf *times);

Листинг 12.25. Описание функции utime(). (html, txt)

Если значение аргумента times равно пустому указателю, используется текущее время. В этом случае для успешного изменения данных о времени действующий идентификатор процесса должен соответствовать владельцу файла либо процесс должен иметь права на запись в файл или соответствующие привилегии.

Если значение times отлично от NULL, оно трактуется как указатель на структуру типа utimbuf, которая, согласно стандарту POSIX-2001, содержит по крайней мере следующие поля:

time_t actime; /* Время последнего доступа */

time_t modtime; /* Время последнего изменения */

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

В случае успешного завершения функция utime() возвращает нулевой результат и, кроме того, переустанавливает время последнего изменения статуса файла.

Приведем пример программы, изменяющей время последнего доступа к файлу (см. листинг 12.26).

Листинг 12.26. Пример программы, изменяющей время последнего доступа к файлу. (html, txt)

Чтобы изменить только одно из двух ассоциированных с файлом времен, необходимо с помощью функции stat() получить информацию о файле, перенести неизменяемое значение из структуры типа stat в utimbuf, установить требуемым образом второй элемент структуры типа utimbuf и только после этого обратиться к функции utime().

Читателю предлагается убедиться, что приведенная программа работает должным образом и что опция -u служебной программы ls предписывает оперировать со временем последнего доступа вместо времени последнего изменения.


if (argc != 2) { fprintf (stderr, "Использование: %s файл\n", argv [0]); return (1); }

if (stat (argv [1], &st_buf) != 0) { perror ("STAT"); return (2); }

ut_buf.actime = time (NULL); ut_buf.modtime = st_buf.st_mtime;

if (utime (argv [1], &ut_buf) != 0) { perror ("UTIME"); return (3); }

return (0); }

Листинг 12.26. Пример программы, изменяющей время последнего доступа к файлу.

Чтобы изменить только одно из двух ассоциированных с файлом времен, необходимо с помощью функции stat() получить информацию о файле, перенести неизменяемое значение из структуры типа stat в utimbuf, установить требуемым образом второй элемент структуры типа utimbuf и только после этого обратиться к функции utime().

Читателю предлагается убедиться, что приведенная программа работает должным образом и что опция -u служебной программы ls предписывает оперировать со временем последнего доступа вместо времени последнего изменения.


Опрос и установка показаний часов реального времени


Простейшим средством опроса и/или изменения текущих даты и времени является служебная программа date:

date [-u] [+формат] date [-u] ммддччмм[[вв]гг]

В первой форме утилита date выдает на стандартный вывод дату и время (по умолчанию - текущие), в соответствии с заданным форматом. Во второй форме date позволяет установить системные дату и время.

При наличии опции -u работа ведется без учета часового пояса, во всемирном времени (как если бы значением переменной окружения TZ была цепочка "UTC0"). Без этой опции учитывается значение TZ или, если таковое не установлено или пусто, принимается во внимание системное подразумеваемое значение.

Если в командной строке date задан аргумент, начинающийся со знака +, форматом вывода даты и времени управляет пользователь. Формат включает спецификаторы преобразований и прочие символы (последние выводятся без изменений). Спецификаторы преобразований начинаются со знака % и замещаются при выводе соответствующим значением.

Таблица 12.1. Спецификаторы преобразований

СпецификаторОписание
%aСокращенное название дня недели.
%AПолное название дня недели.
%b

Сокращенное название месяца.

%BПолное название месяца.
%cПринятое в данной языково-культурной среде представление даты и времени.
%CДве первые цифры четырехзначного номера года [00, 99].
%dНомер дня в месяце [01, 31].
%DДата в формате мм/дд/гг.
%eНомер дня в месяце [1, 31] в двухсимвольном поле с дополнением, при необходимости, пробела.
%hТо же, что %b.
%HНомер часа [00, 23].
%IНомер часа [01, 12].
%jНомер дня в году [001, 366].
%mНомер месяца [01, 12].
%M

Минуты [00, 59].

%nПеревод строки
%S

Секунды [00, 60].

%tТабуляция
%TВремя в формате чч:мм:сс.
%uНомер дня недели [1, 7] (единица соответствует понедельнику).
%UНомер недели в году [00, 53]. Первым днем недели считается воскресенье. Все дни нового года, предшествующие первому воскресенью, относятся к нулевой неделе.
%VНомер недели в году [01, 53]. Первым днем недели считается понедельник. Если первое января приходится на пятницу, субботу или воскресенье, конец недели формально относят к прошлому году, а первой считают следующую неделю.
%wНомер дня недели [0, 6] (ноль соответствует воскресенью).
%WНомер недели в году [00, 53]. Первым днем недели считается понедельник. Все дни нового года, предшествующие первому понедельнику, относятся к нулевой неделе.
%xПринятое в данной языково-культурной среде представление даты.
%XПринятое в данной языково-культурной среде представление времени.
%yДве младшие цифры номера года [00, 99].
%YЧетырехзначный номер года.
%ZИмя часового пояса.
%%Знак процента.
<
Чтобы установить текущие дату и время, необходимо, чтобы система допускала данное действие, а у процесса были соответствующие привилегии.

Во второй форме вызова утилиты date первая пара букв м обозначает номер месяца, дд - номер дня в месяце, чч - часы, мм - минуты, ввгг - год. Если две первые цифры года опущены, то диапазон [69, 99] трактуется как [1969, 1999], а [00, 68] - как [2000, 2068]. Если год не указан, имеется в виду текущий.

Приведем несколько примеров употребления служебной программы date.

Вывод в подразумеваемом формате, используемом командой date без аргументов, может выглядеть так:

Fri Dec 26 17:48:02 MSK 2003

Опция -u повлияет на вывод следующим образом:

Fri Dec 26 14:48:05 UTC 2003

Команда

date '+Дата: %d.%m.%Y%nВремя: %H:%M:%S'

выдаст примерно следующее:

Дата: 26.12.2003 Время: 17:49:15

Для сдвига показаний системных часов на один час вперед можно воспользоваться shell-процедурой, показанной в листинге 12.1 (предполагается, что ее выполняют днем при наличии соответствующих привилегий).

moda=`date +%m%d` ho=$((`date +%H` + 1)) mi=`date +%M` date ${moda}${ho}${mi}

Листинг 12.1. Пример использования служебной программы date. (html, txt)



С функцией time() ассоциирована функция difftime(), вычисляющая ( в виде значения типа double) разность в секундах между двумя моментами времени (time1 - time0, см. листинг 12.3).

#include <time.h> double difftime (time_t time1, time_t time0);

Листинг 12.3. Описание функции difftime().

Узнать текущее время с большей точностью позволяет функция gettimeofday() (см. листинг 12.4).

#include <sys/time.h> int gettimeofday (struct timeval *restrict tp, void *restrict tzp);

Листинг 12.4. Описание функции gettimeofday().

Согласно стандарту POSIX-2001, описанная в заголовочном файле <sys/time.h> структура timeval содержит по крайней мере следующие поля.

time_t tv_sec; /* Секунды */ suseconds_t tv_usec; /* Микросекунды */

Функция gettimeofday() записывает текущее время, выраженное в секундах и микросекундах от начала отсчета, в структуру типа timeval, на которую указывает аргумент tp. Указатель tzp должен быть пустым.

Впрочем, микросекунды - это еще на предел точности, а опрос системных часов - не предел общности. В необязательную часть стандарта POSIX-2001, регламентирующую работу с таймерами, входят функции, позволяющие опросить и установить показания заданных часов, а также узнать их разрешающую способность (см. листинг 12.5).

#include <time.h> int clock_getres (clockid_t clock_id, struct timespec *res); int clock_gettime (clockid_t clock_id, struct timespec *tp); int clock_settime (clockid_t clock_id, const struct timespec *tp);

Листинг 12.5. Описание функций опроса характеристик и установки часов.

Структура timespec отличается от timeval тем, что вместо микросекунд хранит наносекунды:

time_t tv_sec; /* Секунды */ long tv_nsec; /* Наносекунды */

Функция clock_getres() позволяет опросить разрешающую способность часов с заданным идентификатором и поместить результат в структуру, на которую указывает аргумент res.

Функция clock_gettime() записывает в аналогичную структуру текущие показания заданных часов.

Наконец, функция clock_settime() устанавливает заданные часы на основании значений полей структуры типа timespec, на которую указывает аргумент tp.


Опрос показаний часов процессорного времени


Базовым средством для работы с часами процессорного времени является функция clock() (см. листинг 12.15). Она возвращает в качестве результата процессорное время, затраченное процессом с некоего момента, зависящего от реализации и связанного только с его (процесса) запуском. В случае ошибки результат равен (clock_t) (-1).

#include <time.h> clock_t clock (void);

Листинг 12.15. Описание функции clock(). (html, txt)

Чтобы перевести время, возвращаемое функцией clock(), в секунды, его следует поделить на константу CLOCKS_PER_SEC, которая определена в заголовочном файле <time.h> равной одному миллиону, хотя разрешающая способность часов процессорного времени в конкретной реализации может и отличаться от микросекунды.

Отметим, что если значения типа clock_t представлены как 32-разрядные целые со знаком, то менее чем через 36 минут (процессорного времени) наступит переполнение.

В листинге 12.16 показан пример использования функции clock(). Поскольку начало отсчета для часов процессорного времени не специфицировано, их показания опрашиваются в начале и конце измеряемого промежутка, а результатом измерения затраченного процессорного времени служит разность этих показаний.

Листинг 12.16. Пример программы, использующей функции опроса показаний часов реального и процессорного времени. (html, txt)

Возможные результаты работы этой программы показаны в листинге 12.17. Процессорное время получилось больше астрономического из-за ошибок округления.

Начало выполнения цикла Процессорное время выполнения цикла: 15.19 сек. Астрономическое время выполнения цикла: 15 сек.

Листинг 12.17. Возможные результаты работы программы, использующей функции опроса показаний часов реального и процессорного времени. (html, txt)

Для измерения времени выполнения простой команды можно воспользоваться служебной программой time (не входящей, правда, в обязательную часть стандарта POSIX-2001):

time [-p] команда [аргумент ...]

Она выдает в стандартный протокол астрономическое и процессорное время, прошедшее от запуска до завершения команды с указанными аргументами.
#include <sys/times.h> clock_t times (struct tms *buffer);

Листинг 12.19. Описание функции times(). (html, txt)

В качестве результата функция times() возвращает астрономическое время, прошедшее от некоторого момента в прошлом (например, от загрузки системы), но основные, содержательные данные, относящиеся к вызывающему процессу и его потомкам, помещаются в структуру типа tms, которая описана в заголовочном файле <sys/times.h> и, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля:

clock_t tms_utime; /* Процессорное время, затраченное */ /* вызывающим процессом */

clock_t tms_stime; /* Процессорное время, затраченное системой */ /* на обслуживание вызывающего процесса */

clock_t tms_cutime; /* Процессорное время, затраченное /*завершившимися порожденными процессами */

clock_t tms_cstime; /* Процессорное время, затраченное системой */ /* на обслуживание завершившихся */ /* порожденных процессов */

Значения времени завершившихся порожденных процессов (tms_utime и tms_cutime, а также tms_stime и tms_cstime) добавляются, соответственно, к элементам tms_cutime и tms_cstime структуры родительского процесса при возврате из функций wait() или waitpid(). Это происходит рекурсивно: если порожденный процесс ожидал завершения своих потомков, то в упомянутых элементах структуры накопятся данные о времени выполнения поддерева процессов.

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

Листинг 12.20. Пример программы, использующей функцию times(). (html, txt)

Если перенаправить стандартный ввод в какой-либо текстовый файл заметных размеров, а стандартный вывод - в другой файл, можно получить результаты, подобные тем, что показаны в листинге 12.21.

Листинг 12.21. Возможные результаты работы программы, использующей функцию times(). (html, txt)



Прямое манипулирование часами процессорного времени возможно после обращения к функции clock_getcpuclockid() (см. листинг 12.22), позволяющей выяснить их идентификатор. Оговоримся, однако, что эта функция не входит в обязательную часть стандарта POSIX-2001 (она относится к продвинутым средствам реального времени).

#include <time.h> int clock_getcpuclockid (pid_t pid, clockid_t *clock_id);

Листинг 12.22. Описание функции clock_getcpuclockid(). (html, txt)

Идентификатор часов процессорного времени, обслуживающих заданный аргументом pid процесс, возвращается по адресу, специфицированному аргументом clock_id. Если значение pid равно нулю, имеется в виду вызывающий процесс. Признаком успешного завершения является нулевой результат; в противном случае возвращается код ошибки.

В листинге 12.23 показан пример употребления функции clock_getcpuclockid() и манипулирования часами процессорного времени. Возможные результаты выполнения этой программы - в листинге 12.24.

Листинг 12.23. Пример программы, манипулирующей часами процессорного времени. (html, txt)

Листинг 12.24. Возможные результаты работы программы, манипулирующей часами процессорного времени. (html, txt)

Обратим внимание на два момента. Перед началом измеряемого участка программы на часах процессорного времени были установлены нулевые показания, что, разумеется, не влияет на приоритет процесса и его планирование. Конечно, можно было обойтись без переустановки, запомнив показания часов в начале и конце измеряемого промежутка и произведя затем их вычитание, но технически это чуть сложнее; кроме того, нам хотелось применить функцию clock_settime(). Второй заслуживающий отдельного упоминания момент состоит в отсутствии необходимости выяснять идентификатор часов процессорного времени вызывающего процесса с помощью функции clock_getcpuclockid(): можно воспользоваться именованной константой CLOCK_PROCESS_CPUTIME_ID.



Астрономическое время выполнения цикла: 15 сек.

Листинг 12.17. Возможные результаты работы программы, использующей функции опроса показаний часов реального и процессорного времени.

Для измерения времени выполнения простой команды можно воспользоваться служебной программой time (не входящей, правда, в обязательную часть стандарта POSIX-2001):

time [-p] команда [аргумент ...]

Она выдает в стандартный протокол астрономическое и процессорное время, прошедшее от запуска до завершения команды с указанными аргументами. Если задана опция -p, время выдается в секундах в виде вещественных чисел. Процессорное время подразделяется на пользовательское (затраченное самой командой) и системное (ушедшее на оказание системных услуг команде). Если предположить, что выполнимый файл приведенной выше программы называется my_tcex, то командная строка

time -p my_tcex

выдаст в стандартный протокол примерно следующее (см. листинг 12.18):

Начало выполнения цикла Процессорное время выполнения цикла: 15.2 сек. Астрономическое время выполнения цикла: 15 сек. real 15.20 user 15.20 sys 0.00

Листинг 12.18. Возможные результаты измерения времени работы программы, использующей функции опроса показаний часов реального и процессорного времени.

Если нужно измерить время выполнения конвейера или списка команд, их можно поместить в файл, превратив тем самым в простую команду, или воспользоваться конструкцией вида

time sh -c 'составная команда'

Оружием более крупного калибра является специальная встроенная в shell команда

times

Она выдает на стандартный вывод процессорное время, затраченное командным интерпретатором и порожденными им процессами. Например, после выполнения команд

my_tcex & my_tcex times на стандартный вывод может быть выдано: 0m0.010s 0m0.000s 0m30.410s 0m0.000s

Можно видеть, что сам командный интерпретатор практически не занимал процессор, а два порожденных им процесса в сумме потребили чуть меньше тридцати с половиной секунд; время системных услуг в обоих случаях оказалось пренебрежимо малым.



Реализация описанных выше утилит time и times опирается на функцию times() (см. листинг 12.19). Она опрашивает данные о времени выполнения вызывающего процесса и порожденных процессов, завершение которых ожидалось с помощью функций wait() или waitpid(). В отличие от clock(), функция times() измеряет время в тактах часов, и переполнение 32-разрядного представления значений типа clock_t происходит не через полчаса, а примерно в течение года (если секунда делится на 60 - 100 тактов). Соответственно, для перевода результатов работы times() в секунды их нужно делить на sysconf (_SC_CLK_TCK), а не на CLOCKS_PER_SEC.

#include <sys/times.h> clock_t times (struct tms *buffer);

Листинг 12.19. Описание функции times().

В качестве результата функция times() возвращает астрономическое время, прошедшее от некоторого момента в прошлом (например, от загрузки системы), но основные, содержательные данные, относящиеся к вызывающему процессу и его потомкам, помещаются в структуру типа tms, которая описана в заголовочном файле <sys/times.h> и, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля:

clock_t tms_utime; /* Процессорное время, затраченное */ /* вызывающим процессом */

clock_t tms_stime; /* Процессорное время, затраченное системой */ /* на обслуживание вызывающего процесса */

clock_t tms_cutime; /* Процессорное время, затраченное /*завершившимися порожденными процессами */

clock_t tms_cstime; /* Процессорное время, затраченное системой */ /* на обслуживание завершившихся */ /* порожденных процессов */

Значения времени завершившихся порожденных процессов (tms_utime и tms_cutime, а также tms_stime и tms_cstime) добавляются, соответственно, к элементам tms_cutime и tms_cstime структуры родительского процесса при возврате из функций wait() или waitpid(). Это происходит рекурсивно: если порожденный процесс ожидал завершения своих потомков, то в упомянутых элементах структуры накопятся данные о времени выполнения поддерева процессов.

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


листинг 12.20).

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа копирует строки со стандартного ввода на стандартный вывод, */ /* "прокачивая" их через пару сокетов. */ /* Используются функции ввода/вывода нижнего уровня. */ /* Измеряется астрономическое и процессорное время */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/socket.h> #include <sys/wait.h> #include <sys/times.h>

#define MY_PROMPT "Вводите строки\n" #define MY_MSG "Вы ввели: "

int main (void) { int sds [2]; char buf [1]; int new_line = 1; /* Признак того, что надо выдать сообщение MY_MSG */ /* перед отображением очередной строки */ clock_t st_ct; /* Данные об астрономическом и процессорном времени */ struct tms st_cpt; /* в начале работы программы */ clock_t en_ct; /* Данные об астрономическом и процессорном времени */ struct tms en_cpt; /* в конце работы программы */ long tck_p_sec; /* Количество тактов часов в секунде */

/* Опросим данные о времени начала выполнения */ if ((st_ct = times (&st_cpt)) == (clock_t) (-1)) { perror ("TIMES-1"); return (1); }

/* Создадим пару соединенных безымянных сокетов */ if (socketpair (AF_UNIX, SOCK_STREAM, 0, sds) < 0) { perror ("SOCKETPAIR"); return (2); }

switch (fork ()) { case -1: perror ("FORK"); return (3); case 0: /* Чтение из сокета sds [0] и выдачу на стандартный вывод */ /* реализуем в порожденном процессе */ while (read (sds [0], buf, 1) == 1) { if (write (STDOUT_FILENO, buf, 1) != 1) { perror ("WRITE TO STDOUT"); break; } } exit (0); }

/* Чтение со стандартного ввода и запись в сокет sds [1] */ /* возложим на родительский процесс */ if (write (sds [1], MY_PROMPT, sizeof (MY_PROMPT) - 1) != sizeof (MY_PROMPT) - 1) { perror ("WRITE TO SOCKET-1"); }

while (read (STDIN_FILENO, buf, 1) == 1) { /* Перед отображением очередной строки */ /* нужно выдать сообщение MY_MSG */ if (new_line) { if (write (sds [1], MY_MSG, sizeof (MY_MSG) - 1) != sizeof (MY_MSG) - 1) { perror ("WRITE TO SOCKET-2"); break; } } if (write (sds [1], buf, 1) != 1) { perror ("WRITE TO SOCKET-3"); break; } new_line = (buf [0] == '\n'); } shutdown (sds [1], SHUT_WR);



(void) wait (NULL);

/* Опросим данные о времени конца выполнения, */ /* вычислим и выдадим результаты измерений */ if ((en_ct = times (&en_cpt)) == (clock_t) (-1)) { perror ("TIMES-2"); return (4); }

tck_p_sec = sysconf (_SC_CLK_TCK); fprintf (stderr, "Тактов в секунде: %ld\n", tck_p_sec); fprintf (stderr, "Астрономическое время работы программы: %g сек.\n", (double) (en_ct - st_ct) / tck_p_sec); fprintf (stderr, "Процессорное время, затраченное процессом: %g сек.\n", (double) (en_cpt.tms_utime - st_cpt.tms_utime) / tck_p_sec); fprintf (stderr, "Процессорное время, затраченное системой: %g сек.\n", (double) (en_cpt.tms_stime - st_cpt.tms_stime) / tck_p_sec); fprintf (stderr, "Аналогичные данные для порожденных процессов:\n"); fprintf (stderr, "%g сек.\n%g сек.\n", (double) (en_cpt.tms_cutime - st_cpt.tms_cutime) / tck_p_sec, (double) (en_cpt.tms_cstime - st_cpt.tms_cstime) / tck_p_sec);

return (0); }

Листинг 12.20. Пример программы, использующей функцию times().

Если перенаправить стандартный ввод в какой- либо текстовый файл заметных размеров, а стандартный вывод - в другой файл, можно получить результаты, подобные тем, что показаны в листинге 12.21.

Тактов в секунде: 100 Астрономическое время работы программы: 1.19 сек. Процессорное время, затраченное процессом: 0.02 сек. Процессорное время, затраченное системой: 0.08 сек. Аналогичные данные для порожденных процессов: 0.09 сек. 1 сек.

Листинг 12.21. Возможные результаты работы программы, использующей функцию times().

Прямое манипулирование часами процессорного времени возможно после обращения к функции clock_getcpuclockid() (см. листинг 12.22), позволяющей выяснить их идентификатор. Оговоримся, однако, что эта функция не входит в обязательную часть стандарта POSIX-2001 (она относится к продвинутым средствам реального времени).

#include <time.h> int clock_getcpuclockid (pid_t pid, clockid_t *clock_id);

Листинг 12.22. Описание функции clock_getcpuclockid().



Идентификатор часов процессорного времени, обслуживающих заданный аргументом pid процесс, возвращается по адресу, специфицированному аргументом clock_id. Если значение pid равно нулю, имеется в виду вызывающий процесс. Признаком успешного завершения является нулевой результат; в противном случае возвращается код ошибки.

В листинге 12.23 показан пример употребления функции clock_getcpuclockid() и манипулирования часами процессорного времени. Возможные результаты выполнения этой программы - в листинге 12.24.

#include <stdio.h> #include <time.h> #include <sys/types.h> #include <errno.h>

int main (void) { clockid_t clk_id; /* Идентификатор часов процессорного времени */ struct timespec tmsp; double s = 0; double d = 1; int i;

if ((errno = clock_getcpuclockid ((pid_t) 0, &clk_id)) != 0) { perror ("CLOCK_GETCPUCLOCKID"); return (1); }

if (clock_getres (clk_id, &tmsp) == -1) { perror ("CLOCK_GETRES"); return (2); } printf ("Разрешающая способность часов процессорного времени: %ld нсек.\n", tmsp.tv_nsec);

/* Измерим процессорное время выполнения цикла. */ fprintf (stderr, "Начало выполнения цикла\n"); tmsp.tv_sec = tmsp.tv_nsec = 0; if (clock_settime (clk_id, &tmsp) == -1) { perror ("CLOCK_SETTIME"); return (3); } for (i = 1; i <= 100000000; i++) { s += d / i; d = -d; } if (clock_gettime (clk_id, &tmsp) == -1) { perror ("CLOCK_GETTIME"); return (4); } fprintf (stderr, "Процессорное время выполнения цикла: %ld сек. %ld нсек.\n", tmsp.tv_sec, tmsp.tv_nsec);

return (0); }

Листинг 12.23. Пример программы, манипулирующей часами процессорного времени.

Разрешающая способность часов процессорного времени: 2 нсек. Начало выполнения цикла Процессорное время выполнения цикла: 15 сек. 350313054 нсек.

Листинг 12.24. Возможные результаты работы программы, манипулирующей часами процессорного времени.

Обратим внимание на два момента. Перед началом измеряемого участка программы на часах процессорного времени были установлены нулевые показания, что, разумеется, не влияет на приоритет процесса и его планирование.Конечно, можно было обойтись без переустановки, запомнив показания часов в начале и конце измеряемого промежутка и произведя затем их вычитание, но технически это чуть сложнее; кроме того, нам хотелось применить функцию clock_settime(). Второй заслуживающий отдельного упоминания момент состоит в отсутствии необходимости выяснять идентификатор часов процессорного времени вызывающего процесса с помощью функции clock_getcpuclockid(): можно воспользоваться именованной константой CLOCK_PROCESS_CPUTIME_ID.


Основные понятия и объекты


Согласно стандарту POSIX, за начало отсчета (астрономического) времени принимается ноль часов, ноль минут, ноль секунд первого января 1970-го года универсального координированного времени (далее для краткости именуемого также всемирным).

Всемирным называют поясное время нулевого часового пояса, которое представляет собой местное среднее солнечное время гринвичского меридиана.

Московское время опережает всемирное на три часа. Имеется в виду так называемое декретное московское время, которое не следует путать с поясным.

Поясное время Москвы, лежащей во втором часовом поясе, опережает всемирное на два часа.

Московское летнее время опережает всемирное на 4 часа.

За стандартную единицу измерения астрономического времени в POSIX-2001 принята секунда. Пусть некоторый момент времени задан в терминах секунд (значение tm_sec), минут (tm_min), часов (tm_hour), а также номером дня в году (tm_yday) и номером года минус 1900 (tm_year). Тогда число секунд всемирного времени, прошедшее на этот момент от начала отсчета, вычисляется по формуле

tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 + (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 - ((tm_year-1)/100)* 86400 + ((tm_year+299)/400)*86400

(три последних слагаемых предназначены для учета високосных лет).

Учет разного рода поправок к всемирному времени (таких, например, как вставка дополнительной секунды в начале 1996 года) зависит от реализации.

Для представления данных о моментах времени в программах на языке C обычно используется структура tm, описанная во включаемом файле <time.h> и содержащая, согласно стандарту POSIX-2001, по крайней мере следующие поля.

int tm_sec; /* Секунды [0,60] */ int tm_min; /* Минуты [0,59] */ int tm_hour; /* Часы [0,23] */ int tm_mday; /* Номер дня в месяце [1,31] */ int tm_mon; /* Номер месяца в году [0,11] */ int tm_year; /* Номер года минус 1900 */ int tm_wday; /* Номер дня недели [0,6] */ /* (воскресенью соответствует ноль) */ int tm_yday; /* Номер дня в году [0,365] */ int tm_isdst; /* Признак учета летнего времени */


Диапазон [0, 60] для поля tm_sec позволяет справляться с производимой время от времени вставкой дополнительной секунды.

Значение поля tm_year задается со знаком, что позволяет представлять годы до 1900.

Значение признака tm_isdst должно быть положительным, если действует летнее время, нулевым, если летнее время не действует, или отрицательным, если про летнее время ничего не известно.

Часами называется программный или аппаратный объект, предназначенный для измерения видимого или истинного хода времени.

Показания часов можно опросить и установить (в допустимых для часов пределах).

Разрешающей способностью часов называется минимальный промежуток времени, который может быть этими часами измерен.

Под скачком часов понимается разность между двумя последовательными, различными с точки зрения приложения (использующего стандартные средства) показаниями часов.

Часы называются монотонными, если их нельзя установить стандартными средствами и они не могут иметь отрицательных скачков.

Такт часов - это зависящие от реализации промежутки времени, на которые дробится каждая секунда.

Реальным (или астрономическим) называется время, измеренное по системным часам безотносительно к тому, какой процесс (поток управления) выполняется.

Под временем выполнения (процессорным временем) понимается время, затрачиваемое на выполнение процесса (потока управления), включая работающие от его имени системные сервисы.

Время выполнения конкретного процесса или потока управления измеряется часами процессорного времени.

Под мониторингом времени выполнения понимается оперативное отслеживание процессорного времени, затрачиваемого процессом (потоком управления).

Виртуальное время процесса - время, измеряемое системными часами, пока процесс выполняется.

Таймер - это механизм, способный известить процесс (поток управления) об истечении заданного временного промежутка (подобный таймер называется интервальным) или о достижении (или превышении) часами заданных показаний (абсолютный таймер). Соответствующее событие называется срабатыванием таймера.



Диапазон [0, 60] для поля tm_sec позволяет справляться с производимой время от времени вставкой дополнительной секунды.

Значение поля tm_year задается со знаком, что позволяет представлять годы до 1900.

Значение признака tm_isdst должно быть положительным, если действует летнее время, нулевым, если летнее время не действует, или отрицательным, если про летнее время ничего не известно.

Часами называется программный или аппаратный объект, предназначенный для измерения видимого или истинного хода времени.

Показания часов можно опросить и установить (в допустимых для часов пределах).

Разрешающей способностью часов называется минимальный промежуток времени, который может быть этими часами измерен.

Под скачком часов понимается разность между двумя последовательными, различными с точки зрения приложения (использующего стандартные средства) показаниями часов.

Часы называются монотонными, если их нельзя установить стандартными средствами и они не могут иметь отрицательных скачков.

Такт часов - это зависящие от реализации промежутки времени, на которые дробится каждая секунда.

Реальным (или астрономическим) называется время, измеренное по системным часам безотносительно к тому, какой процесс (поток управления) выполняется.

Под временем выполнения (процессорным временем) понимается время, затрачиваемое на выполнение процесса (потока управления), включая работающие от его имени системные сервисы.

Время выполнения конкретного процесса или потока управления измеряется часами процессорного времени.

Под мониторингом времени выполнения понимается оперативное отслеживание процессорного времени, затрачиваемого процессом (потоком управления).

Виртуальное время процесса - время, измеряемое системными часами, пока процесс выполняется.

Таймер - это механизм, способный известить процесс (поток управления) об истечении заданного временного промежутка (подобный таймер называется интервальным) или о достижении (или превышении) часами заданных показаний (абсолютный таймер). Соответствующее событие называется срабатыванием таймера.


Преобразование данных о времени


Стандартом POSIX-2001 предусмотрено несколько способов представления данных о времени. Выше были описаны структура tm и тип time_t. Кроме того, время может записываться в виде цепочки символов. (Есть еще структуры timeval и timespec, но они в данном контексте играют подчиненную роль, лишь уточняя значения типа time_t.)

Для выполнения преобразований между разными представлениями данных о времени служат описываемые далее функции (см. также рис. 12.1).


Рис. 12.1.  Функции для выполнения преобразований между разными представлениями данных о времени.

Функции gmtime() и localtime() (см. листинг 12.8) преобразуют значения типа time_t в структуру типа tm. Соотношение между временем в секундах от начала отсчета и значениями полей структуры типа tm дается в приведенной выше формуле. Кроме того, функция localtime() учитывает данные о часовом поясе и сезонных поправках.

#include <time.h> struct tm *gmtime (const time_t *tloc); struct tm *localtime (const time_t *tloc)

Листинг 12.8. Описание функций gmtime() и localtime(). (html, txt)

Для учета данных о часовом поясе и сезонных поправках используются внешние переменные tzname, timezone, daylight, значения которых функция tzset() устанавливает по переменной окружения TZ (см. листинг 12.9).

#include <time.h> extern char *tzname[2]; extern long timezone; extern int daylight; void tzset (void);

Листинг 12.9. Описание функции tzset() и ассоциированных внешних переменных. (html, txt)

Элементам массива tzname присваиваются имена местного часового пояса в стандартном (tzname [0]) и "летнем" (tzname [1]) вариантах. Значение переменной timezone устанавливается равным разности в секундах между всемирным и местным поясным временем. Переменной daylight присваивается отличное от нуля значение, если для местного часового пояса предусмотрен переход на летнее время.

Отметим, что в общем случае значение переменой окружения TZ устроено довольно сложным образом:

Станд_поясСмещение[Лет_пояс[Смещение] [,Нач_лет[/Время],Кон_лет[/Время]]]


Здесь Станд_пояс и Лет_пояс - имена, присваиваемые элементам массива tzname [], Смещение - разность между всемирным и местным поясным временем (в виде чч[:мм[:сс]]), Нач_лет и Кон_лет, соответственно, даты начала и окончания действия летнего времени (обычно их задают в виде Mмм.н.д - месяц, неделя, день), время - время перехода (по умолчанию - два часа ночи). Можно видеть, что данных для вычисления местного времени оказывается вполне достаточно.

Функцию mktime() (см. листинг 12.10) можно считать обратной по отношению к localtime(). Она преобразует местное время, заданное в виде структуры типа tm, в значение типа time_t, т. е. в секунды от начала отсчета по всемирному времени.

#include <time.h> time_t mktime (struct tm *tmptr);

Листинг 12.10. Описание функции mktime(). (html, txt)

При входе в функцию mktime() значения полей tm_wday и tm_yday структуры tm, на которую указывает аргумент tmptr, игнорируются; при выходе они устанавливаются должным образом. Значения других полей также приводятся к стандартным для них диапазонам (при входе это условие может не выполняться).

Другим весьма мощным средством преобразования местного времени из структурного в текстовое представление является функция strftime() (см. листинг 12.11). Как и служебная программа date, она преобразует дату и время в соответствии с заданным форматом, только исходными данными служит не текущий момент времени, а структура типа tm, на которую указывает аргумент tmptr, и результат направляется не на стандартный вывод, а в буфер, заданный указателем s и длиной maxsize.

#include <time.h> size_t strftime (char *restrict s, size_t maxsize, const char *restrict format, const struct tm *restrict tmptr);

Листинг 12.11. Описание функции strftime(). (html, txt)

По сравнению с date, функция strftime() содержит несколько дополнительных спецификаторов форматных преобразований. Перечислим наиболее употребительные из них.

Таблица 12.1. Спецификаторы преобразованийСпецификаторОписание
%FПредставление даты в соответствии со стандартом ISO 8601:2000: эквивалент последовательности спецификаторов %Y-%m-%d.
%R

Часы и минуты (%H:%M) в 24-часовом представлении.
%zСмещение относительно всемирного времени, представленное по стандарту ISO 8601:2000: +ччмм или -ччмм (положительные значения соответствуют часовым поясам к востоку от Гринвича).
<


Функция strftime() возвращает число байт, помещенное в буфер (без учета завершающего нулевого байта). Если буфер оказался мал, возвращается ноль.

На роль обратной по отношению к strftime() могут претендовать сразу две функции: strptime() и getdate() (см. листинг 12.12).

#include <time.h> char *strptime (const char *restrict s, const char *restrict format, struct tm *restrict tmptr); struct tm *getdate (const char *s);

Листинг 12.12. Описание функций strptime() и getdate(). (html, txt)

Функция strptime() напоминает sscanf(): она сканирует цепочку символов, на которую указывает аргумент s, в соответствии с заданным форматом, включающим описанные выше спецификаторы преобразований, а также, быть может, пробельные и обычные символы, и помещает извлеченные значения в структуру типа tm по указателю tmptr. В качестве результата возвращается указатель на первый несканированный символ или NULL в случае неудачи.

Функция getdate(), по сути аналогичная strptime(), использует для разбора входной цепочки s форматы, содержащиеся в файле, чье полное маршрутное имя задано переменной окружения DATEMSK (для интерпретации выбирается первый подходящий формат). Если дата и время специфицированы не полностью (например, задан только день недели), как исходные берутся данные о первом подходящем моменте времени, начиная с текущего. Если в формате присутствует спецификатор %Z, выходная структура инициализируется текущим временем в сканируемом часовом поясе. В противном случае применяется местное время.

С помощью внешней переменной (или макроса) getdate_err функция getdate() возвращает коды ошибок.

Приведем пример использования описанных функций преобразования данных о времени (см. листинг 12.13).

Листинг 12.13. Пример программы, использующей функции преобразования данных о времени. (html, txt)

Возможные результаты выполнения этой программы показаны в листинге 12.14.

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

Последовательность инструкций языка C

char dtbuf []; time_t st; (void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (&st));

является стандартной для получения текущего времени в текстовом виде. (Разумеется, вместо "%c" допустим другой спецификатор преобразования.) Нужно помнить только, что функции gmtime() и localtime() возвращают указатели на статические структуры, содержимое которых, возможно, перезаписывается при каждом вызове, поэтому, если оно понадобится в дальнейшем, его следует скопировать в собственные объекты.



(void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (&st)); printf ("Текущее местное время: %s\n", dtbuf);

/* Узнаем, каким днем недели будет 01 января 2038 года */ stm.tm_year = 2038 - 1900; stm.tm_mon = 1 - 1; stm.tm_mday = 1; stm.tm_hour = 0; stm.tm_min = 0; stm.tm_sec = 0; stm.tm_isdst = -1; if ((st = mktime (&stm)) == (time_t) (-1)) { perror ("MKTIME"); } else { (void) strftime (dtbuf, sizeof (dtbuf), "%A", &stm); printf ("День недели 01 января 2038 года: %s\n", dtbuf); printf ("Число секунд от начала отсчета в начале 2038 года (шест.): %x\n", (unsigned int) st); }

/* Узнаем, когда наступит переполнение значений типа time_t, */ /* представленных как 32-разрядное целое со знаком */ st = (time_t) 0x7fffffff; (void) strftime (dtbuf, sizeof (dtbuf), "%c", gmtime (&st)); printf ("Всемирное время конца 32-разрядного отсчета: %s\n", dtbuf);

/* Преобразуем эту дату в формат ISO 8601:2000 */ if (strptime (dtbuf, "%c", &stm) == NULL) { perror ("STRPTIME"); } else { (void) strftime (dtbuf, sizeof (dtbuf), "%F", &stm); printf ("Дата конца 32-разрядного отсчета в формате ISO 8601:2000: %s\n", dtbuf); }

return (0); }

Листинг 12.13. Пример программы, использующей функции преобразования данных о времени.

Возможные результаты выполнения этой программы показаны в листинге 12.14.

Текущее всемирное время: Sat Jan 3 13:54:02 2004 Текущее местное время: Sat Jan 3 16:54:02 2004 День недели 01 января 2038 года: Friday Число секунд от начала отсчета в начале 2038 года (шест.): 7fe7ed50 Всемирное время конца 32-разрядного отсчета: Tue Jan 19 03:14:07 2038 Дата конца 32-разрядного отсчета в формате ISO 8601:2000: 2038-01-19

Листинг 12.14. Возможные результаты работы программы, использующей функции преобразования данных о времени.

Последовательность инструкций языка C

char dtbuf []; time_t st; (void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (&st));

является стандартной для получения текущего времени в текстовом виде. (Разумеется, вместо "%c" допустим другой спецификатор преобразования.) Нужно помнить только, что функции gmtime() и localtime() возвращают указатели на статические структуры, содержимое которых, возможно, перезаписывается при каждом вызове, поэтому, если оно понадобится в дальнейшем, его следует скопировать в собственные объекты.


Пример использования служебной


moda=`date +%m%d` ho=$((`date +%H` + 1)) mi=`date +%M` date ${moda}${ho}${mi}
Листинг 12.1. Пример использования служебной программы date.
Закрыть окно




#include <time.h> time_t time (time_t *tloc);
Листинг 12.2. Описание функции time().
Закрыть окно




#include <time.h> double difftime ( time_t time1, time_t time0);
Листинг 12.3. Описание функции difftime().
Закрыть окно




#include <sys/time.h> int gettimeofday (struct timeval * restrict tp, void *restrict tzp);
Листинг 12.4. Описание функции gettimeofday().
Закрыть окно




#include <time.h> int clock_getres (clockid_t clock_id, struct timespec *res); int clock_gettime (clockid_t clock_id, struct timespec *tp); int clock_settime ( clockid_t clock_id, const struct timespec *tp);
Листинг 12.5. Описание функций опроса характеристик и установки часов.
Закрыть окно




#include <stdio.h> #include <time.h> #include <sys/time.h>
int main (void) { struct timespec tmsp; struct timeval tmvl; time_t st; double s = 0; double d = 1; int i;
if (clock_getres (CLOCK_REALTIME, &tmsp) == -1) { perror ("CLOCK_GETRES"); return (1); } printf ("Разрешающая способность общесистемных часов: %ld нсек.\n", tmsp.tv_nsec);
if (clock_gettime (CLOCK_REALTIME, &tmsp) == -1) { perror ("CLOCK_GETTIME"); return (2); } printf ("Текущее время по общесистемным часам: %ld сек. %ld нсек.\n", tmsp.tv_sec, tmsp.tv_nsec);
(void) gettimeofday (&tmvl, NULL); printf ("Текущее время, функция gettimeofday(): %ld сек. %ld мксек.\n", tmvl.tv_sec, tmvl.tv_usec);
(void) time (&st); for (i = 1; i <= 100000000; i++) { s += d / i; d = -d; } printf ("Время выполнения цикла: %g сек.\n", difftime (time (NULL), st));
return (0); }
Листинг 12.6. Пример программы, использующей функции опроса показаний часов реального времени.
Закрыть окно




Разрешающая способность общесистемных часов: 10000000 нсек. Текущее время по общесистемным часам: 1072534678 сек. 296598000 нсек. Текущее время, функция gettimeofday(): 1072534678 сек. 296637 мксек. Время выполнения цикла: 15 сек.
Листинг 12.7. Возможные результаты работы программы, использующей функции опроса показаний часов реального времени.
Закрыть окно




#include <time.h> struct tm *gmtime (const time_t *tloc); struct tm *localtime (const time_t *tloc)
Листинг 12.8. Описание функций gmtime() и localtime().
Закрыть окно




#include <time.h> extern char *tzname[2]; extern long timezone; extern int daylight; void tzset (void);
Листинг 12.9. Описание функции tzset() и ассоциированных внешних переменных.
Закрыть окно




#include <time.h> time_t mktime (struct tm *tmptr);
Листинг 12.10. Описание функции mktime().
Закрыть окно




#include <time.h> size_t strftime (char * restrict s, size_t maxsize, const char *restrict format, const struct tm *restrict tmptr);
Листинг 12.11. Описание функции strftime().
Закрыть окно




#include <time.h> char *strptime (const char * restrict s, const char *restrict format, struct tm *restrict tmptr); struct tm *getdate (const char *s);
Листинг 12.12. Описание функций strptime() и getdate().
Закрыть окно




#include <stdio.h> #include <time.h> #include <limits.h>
int main (void) { char dtbuf [LINE_MAX]; /* Буфер для данных о времени */ time_t st; struct tm stm;
(void) time (&st);
(void) strftime (dtbuf, sizeof (dtbuf), "%c", gmtime (&st)); printf ("Текущее всемирное время: %s\n", dtbuf);
(void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (&st)); printf ("Текущее местное время: %s\n", dtbuf);
/* Узнаем, каким днем недели будет 01 января 2038 года */ stm.tm_year = 2038 - 1900; stm.tm_mon = 1 - 1; stm.tm_mday = 1; stm.tm_hour = 0; stm.tm_min = 0; stm.tm_sec = 0; stm.tm_isdst = -1; if ((st = mktime (&stm)) == (time_t) (-1)) { perror ("MKTIME"); } else { (void) strftime (dtbuf, sizeof (dtbuf), "%A", &stm); printf ("День недели 01 января 2038 года: %s\n", dtbuf); printf ("Число секунд от начала отсчета в начале 2038 года (шест.): %x\n", (unsigned int) st); }
/* Узнаем, когда наступит переполнение значений типа time_t, */ /* представленных как 32-разрядное целое со знаком */ st = (time_t) 0x7fffffff; (void) strftime (dtbuf, sizeof (dtbuf), "%c", gmtime (&st)); printf ("Всемирное время конца 32-разрядного отсчета: %s\n", dtbuf);
/* Преобразуем эту дату в формат ISO 8601:2000 */ if (strptime (dtbuf, "%c", &stm) == NULL) { perror ("STRPTIME"); } else { (void) strftime (dtbuf, sizeof (dtbuf), "%F", &stm); printf ("Дата конца 32-разрядного отсчета в формате ISO 8601:2000: %s\n", dtbuf); }
return (0); }
Листинг 12.13. Пример программы, использующей функции преобразования данных о времени.
Закрыть окно




Текущее всемирное время: Sat Jan 3 13:54:02 2004 Текущее местное время: Sat Jan 3 16:54:02 2004 День недели 01 января 2038 года: Friday Число секунд от начала отсчета в начале 2038 года (шест.): 7fe7ed50 Всемирное время конца 32-разрядного отсчета: Tue Jan 19 03:14:07 2038 Дата конца 32-разрядного отсчета в формате ISO 8601:2000: 2038-01-19
Листинг 12.14. Возможные результаты работы программы, использующей функции преобразования данных о времени.
Закрыть окно




#include <time.h> clock_t clock (void);
Листинг 12.15. Описание функции clock().
Закрыть окно




#include <stdio.h> #include <time.h>
int main (void) { time_t st; clock_t ct; double s = 0; double d = 1; int i;
fprintf (stderr, "Начало выполнения цикла\n"); (void) time (&st); ct = clock; for (i = 1; i <= 100000000; i++) { s += d / i; d = -d; } fprintf (stderr, "Процессорное время выполнения цикла: %g сек.\n", (double) (clock - ct) / CLOCKS_PER_SEC); fprintf (stderr, "Астрономическое время выполнения цикла: %g сек.\n", difftime (time (NULL), st));
return (0); }
Листинг 12.16. Пример программы, использующей функции опроса показаний часов реального и процессорного времени.
Закрыть окно




Начало выполнения цикла Процессорное время выполнения цикла: 15.19 сек. Астрономическое время выполнения цикла: 15 сек.
Листинг 12.17. Возможные результаты работы программы, использующей функции опроса показаний часов реального и процессорного времени.
Закрыть окно




Начало выполнения цикла Процессорное время выполнения цикла: 15.2 сек. Астрономическое время выполнения цикла: 15 сек. real 15.20 user 15.20 sys 0.00
Листинг 12.18. Возможные результаты измерения времени работы программы, использующей функции опроса показаний часов реального и процессорного времени.
Закрыть окно




#include <sys/times.h> clock_t times (struct tms *buffer);
Листинг 12.19. Описание функции times().
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа копирует строки со стандартного ввода на стандартный вывод, */ /* "прокачивая" их через пару сокетов. */ /* Используются функции ввода/вывода нижнего уровня. */ /* Измеряется астрономическое и процессорное время */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/socket.h> #include <sys/wait.h> #include <sys/times.h>
#define MY_PROMPT "Вводите строки\n" #define MY_MSG "Вы ввели: "
int main (void) { int sds [2]; char buf [1]; int new_line = 1; /* Признак того, что надо выдать сообщение MY_MSG */ /* перед отображением очередной строки */ clock_t st_ct; /* Данные об астрономическом и процессорном времени */ struct tms st_cpt; /* в начале работы программы */ clock_t en_ct; /* Данные об астрономическом и процессорном времени */ struct tms en_cpt; /* в конце работы программы */ long tck_p_sec; /* Количество тактов часов в секунде */
/* Опросим данные о времени начала выполнения */ if ((st_ct = times (&st_cpt)) == (clock_t) (-1)) { perror ("TIMES-1"); return (1); }
/* Создадим пару соединенных безымянных сокетов */ if (socketpair (AF_UNIX, SOCK_STREAM, 0, sds) < 0) { perror ("SOCKETPAIR"); return (2); }
switch (fork ()) { case -1: perror ("FORK"); return (3); case 0: /* Чтение из сокета sds [0] и выдачу на стандартный вывод */ /* реализуем в порожденном процессе */ while (read (sds [0], buf, 1) == 1) { if (write (STDOUT_FILENO, buf, 1) != 1) { perror ("WRITE TO STDOUT"); break; } } exit (0); }
/* Чтение со стандартного ввода и запись в сокет sds [1] */ /* возложим на родительский процесс */ if (write (sds [1], MY_PROMPT, sizeof (MY_PROMPT) - 1) != sizeof (MY_PROMPT) - 1) { perror ("WRITE TO SOCKET-1"); }
while (read (STDIN_FILENO, buf, 1) == 1) { /* Перед отображением очередной строки */ /* нужно выдать сообщение MY_MSG */ if (new_line) { if (write (sds [1], MY_MSG, sizeof (MY_MSG) - 1) != sizeof (MY_MSG) - 1) { perror ("WRITE TO SOCKET-2"); break; } } if (write (sds [1], buf, 1) != 1) { perror ("WRITE TO SOCKET-3"); break; } new_line = (buf [0] == '\n'); } shutdown (sds [1], SHUT_WR);
(void) wait (NULL);
/* Опросим данные о времени конца выполнения, */ /* вычислим и выдадим результаты измерений */ if ((en_ct = times (&en_cpt)) == (clock_t) (-1)) { perror ("TIMES-2"); return (4); }
tck_p_sec = sysconf (_SC_CLK_TCK); fprintf (stderr, "Тактов в секунде: %ld\n", tck_p_sec); fprintf (stderr, "Астрономическое время работы программы: %g сек.\n", (double) (en_ct - st_ct) / tck_p_sec); fprintf (stderr, "Процессорное время, затраченное процессом: %g сек.\n", (double) (en_cpt.tms_utime - st_cpt.tms_utime) / tck_p_sec); fprintf (stderr, "Процессорное время, затраченное системой: %g сек.\n", (double) (en_cpt.tms_stime - st_cpt.tms_stime) / tck_p_sec); fprintf (stderr, "Аналогичные данные для порожденных процессов:\n"); fprintf (stderr, "%g сек.\n%g сек.\n", (double) (en_cpt.tms_cutime - st_cpt.tms_cutime) / tck_p_sec, (double) (en_cpt.tms_cstime - st_cpt.tms_cstime) / tck_p_sec);
return (0); }
Листинг 12.20. Пример программы, использующей функцию times().
Закрыть окно




Тактов в секунде: 100 Астрономическое время работы программы: 1.19 сек. Процессорное время, затраченное процессом: 0.02 сек. Процессорное время, затраченное системой: 0.08 сек. Аналогичные данные для порожденных процессов: 0.09 сек. 1 сек.
Листинг 12.21. Возможные результаты работы программы, использующей функцию times().
Закрыть окно




#include <time.h> int clock_getcpuclockid ( pid_t pid, clockid_t *clock_id);
Листинг 12.22. Описание функции clock_getcpuclockid().
Закрыть окно




#include <stdio.h> #include <time.h> #include <sys/types.h> #include <errno.h>
int main (void) { clockid_t clk_id; /* Идентификатор часов процессорного времени */ struct timespec tmsp; double s = 0; double d = 1; int i;
if ((errno = clock_getcpuclockid ((pid_t) 0, &clk_id)) != 0) { perror ("CLOCK_GETCPUCLOCKID"); return (1); }
if (clock_getres (clk_id, &tmsp) == -1) { perror ("CLOCK_GETRES"); return (2); } printf (" Разрешающая способность часов процессорного времени: %ld нсек.\n", tmsp.tv_nsec);
/* Измерим процессорное время выполнения цикла. */ fprintf (stderr, "Начало выполнения цикла\n"); tmsp.tv_sec = tmsp.tv_nsec = 0; if (clock_settime (clk_id, &tmsp) == -1) { perror ("CLOCK_SETTIME"); return (3); } for (i = 1; i <= 100000000; i++) { s += d / i; d = -d; } if (clock_gettime (clk_id, &tmsp) == -1) { perror ("CLOCK_GETTIME"); return (4); } fprintf (stderr, "Процессорное время выполнения цикла: %ld сек. %ld нсек.\n", tmsp.tv_sec, tmsp.tv_nsec);
return (0); }
Листинг 12.23. Пример программы, манипулирующей часами процессорного времени.
Закрыть окно




Разрешающая способность часов процессорного времени: 2 нсек. Начало выполнения цикла Процессорное время выполнения цикла: 15 сек. 350313054 нсек.
Листинг 12.24. Возможные результаты работы программы, манипулирующей часами процессорного времени.
Закрыть окно




#include <utime.h> int utime (const char *path, const struct utimbuf *times);
Листинг 12.25. Описание функции utime().
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа устанавливает время последнего доступа к файлу - */ /* аргументу командной строки, равное текущему времени */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h> #include <sys/stat.h> #include <time.h> #include <utime.h>
int main (int argc, char *argv []) { struct stat st_buf; /* Буфер для опроса данных о файле */ struct utimbuf ut_buf; /* Буфер для формирования изменяемых данных о файле */
if (argc != 2) { fprintf (stderr, "Использование: %s файл\n", argv [0]); return (1); }
if (stat (argv [1], &st_buf) != 0) { perror ("STAT"); return (2); }
ut_buf.actime = time (NULL); ut_buf.modtime = st_buf.st_mtime;
if (utime (argv [1], &ut_buf) != 0) { perror ("UTIME"); return (3); }
return (0); }
Листинг 12.26. Пример программы, изменяющей время последнего доступа к файлу.
Закрыть окно




#include <unistd.h> unsigned sleep (unsigned seconds);
Листинг 12.27. Описание функции sleep().
Закрыть окно




#include <time.h> int nanosleep ( const struct timespec *rqtp, struct timespec *rmtp);
Листинг 12.28. Описание функции nanosleep().
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа отслеживает изменение размера файла, */ /* заданного как аргумент командной строки */ /* * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdio.h> #include <sys/stat.h> #include <time.h> #include <limits.h>
static time_t lt_mod = 0; /* Время последнего изменения файла */ static struct stat st_buf; /* Данные о файле */
/* Функция начальной обработки файла */ static void proc_init (const char *path) { fprintf (stderr, "Данные о размере файла %s\n", path); fprintf (stderr, "Время изменения Размер\n"); }
/* Функция проверки, нужно ли обрабатывать файл */ /* Результат > 0 - нужно */ /* 0 - не нужно */ /* &lt; 0 - ошибка */ static int check_mod (const char *path) { if (stat (path, &st_buf) != 0) { perror ("STAT"); return (-1); }
if (st_buf.st_mtime != lt_mod) { /* Файл изменился и, следовательно, нуждается в обработке */ lt_mod = st_buf.st_mtime; return (1); }
return 0; }
/* Функция обработки файла. */ /* Выведем время последнего изменения и текущий размер */ static void proc_mod (void) { char dtbuf [LINE_MAX]; /* Буфер для данных о времени */
(void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (&lt_mod)); fprintf (stderr, "%s %ld\n", dtbuf, st_buf.st_size); }
int main (int argc, char *argv []) { int res;
if (argc != 2) { fprintf (stderr, "Использование: %s файл\n", argv [0]); return (1); }
proc_init (argv [1]); while (1) { if ((res = check_mod (argv [1])) > 0) { proc_mod (); } else if (res &lt; 0) { return (res); } sleep (1); }
return 0; }
Листинг 12.29. Пример программы, использующей функцию sleep().
Закрыть окно




Данные о размере файла /var/log/ cron Время изменения Размер Tue Jan 6 12:50:00 2004 11191 Tue Jan 6 13:01:00 2004 11263 Tue Jan 6 13:10:00 2004 11409 Tue Jan 6 13:20:00 2004 11481 . . . Tue Jan 6 13:40:00 2004 11624 . . .
Листинг 12.30. Возможные результаты работы программы, использующей функцию sleep().
Закрыть окно




#define N ... #define MY_BUFSIZ ...
struct timespec tmsp = {0, 20000000}; int fds [2]; /* Файловые дескрипторы канала */ int semid; /* Идентификатор набора семафоров */ struct sembuf sembuf [1]; /* Массив для задания операций над семафором */ char buf [MY_BUFSIZ]; /* Буфер для чтения данных из канала */ int brd; /* Число прочитанных байт */ int i;
. . .
fcntl (fds [0], F_SETFL, O_NONBLOCK); sembuf [0].sem_num = 0; sembuf [0].sem_flg = IPC_NOWAIT; sembuf [0].sem_op = 0;
for (i = 0; i &lt; N; i++) { nanosleep (&tmsp, NULL);
if ((brd = read (fds [0], buf, MY_BUFSIZ) > 0) { /* Обработка прочитанных данных. */ /* Возможно, поступление данных на этом не закончилось */ . . . continue; }
if (semop (semid, sembuf, 1) == 0) { /* Значение семафора обнулено, ждать больше нечего */ . . . if (brd <= 0) { close (fds [0]); break; } } }
. . .
Листинг 12.31. Пример реализации полуактивного ожидания наступления нескольких событий с помощью функции nanosleep().
Закрыть окно




#include <sys/time.h> int getitimer (int timer_id, struct itimerval *cvalue); int setitimer ( int timer_id, const struct itimerval *restrict nvalue, struct itimerval *restrict ovalue);
Листинг 12.32. Описание функций getitimer() и setitimer().
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа выясняет размер такта часов реального времени */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h> #include <sys/time.h>
int main (void) { struct itimerval itvl;
itvl.it_interval.tv_sec = 0; itvl.it_interval.tv_usec = 1; itvl.it_value.tv_sec = 0; itvl.it_value.tv_usec = 0;
if (setitimer (ITIMER_REAL, &itvl, NULL) < 0) { perror ("SETITIMER"); return (1); }
if (getitimer (ITIMER_REAL, &itvl) < 0) { perror ("GETITIMER"); return (2); }
if (itvl.it_interval.tv_usec < 2) { printf ("Не удалось выяснить размер такта часов реального времени\n"); } else { printf ("Размер такта часов реального времени: %ld мксек\n", itvl.it_interval.tv_usec); }
return 0; }
Листинг 12.33. Пример программы, использующей функции getitimer() и setitimer().
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа сравнивает ход реального и виртуального */ /* времени процесса */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/time.h> #include <signal.h>
#define TM_BUF_SIZE 128
/* Массив для хранения показаний часов */ /* реального времени в момент срабатывания */ /* таймера виртуального времени процесса */ static struct timeval rt_vls [TM_BUF_SIZE]; /* Указатель на текущий элемент */ /* массива rt_vls */ static struct timeval *prt_vls = rt_vls;
/* Функция обработки срабатывания таймеров */ /* (сигналы SIGALRM и SIGVTALRM) */ static void proc_itexprtn (int signo) { struct itimerval itmvl = {{0, 0}, {0, 0}};
/* Запомним текущее астрономическое время */ (void) gettimeofday (prt_vls++, NULL); if (signo == SIGALRM) { /* Сработал таймер реального времени. */ /* Разрядим таймер виртуального времени */ /* и завершимся (с выдачей результатов) */ (void) setitimer (ITIMER_VIRTUAL, &itmvl, NULL); exit (0); } }
/* Функция выдачи данных о ходе реального времени */ /* на фоне равномерного течения виртуального времени процесса */ static void print_rt_data (void) { struct timeval *tprt_vls = rt_vls; int i = 0;
printf ("Прошедшее реальное время за один квант виртуального\n"); while (++tprt_vls != prt_vls) { printf (" %3d %10ld мксек\n", ++i, (tprt_vls->tv_sec - (tprt_vls - 1)->tv_sec) * 1000000 + (tprt_vls->tv_usec - (tprt_vls - 1)->tv_usec)); } }
int main (void) { struct itimerval itvl; struct sigaction sact; sigset_t sset;
/* Установим реакцию на сигналы SIGALRM и SIGVTALRM. */ /* Позаботимся, чтобы функция обработки не могла быть прервана */ /* срабатыванием другого таймера */ if ((sigemptyset (&sset) < 0) || (sigaddset (&sset, SIGALRM) < 0) || (sigaddset (&sset, SIGVTALRM) < 0)) { perror ("SIGEMPTYSET or SIGADDSET"); return (1); }
sact.sa_handler = proc_itexprtn; sact.sa_flags = 0; sact.sa_mask = sset; if (sigaction (SIGALRM, &sact, NULL) < 0) { perror ("SIGACTION-SIGALRM"); return (2); } if (sigaction (SIGVTALRM, &sact, NULL) < 0) { perror ("SIGACTION-SIGVTALRM"); return (3); }
/* Зарегистрируем функцию print_rt_data() в atexit() */ if (atexit (print_rt_data) != 0) { perror ("ATEXIT"); return (4); }
/* Взведем таймер реального времени как одноразовый */ itvl.it_interval.tv_sec = 0; itvl.it_interval.tv_usec = 0; itvl.it_value.tv_sec = 10; itvl.it_value.tv_usec = 0; if (setitimer (ITIMER_REAL, &itvl, NULL) < 0) { perror ("SETITIMER-REAL"); return (5); }
/* Установим начало отсчета для данных о реальном времени */ (void) gettimeofday (prt_vls++, NULL);
/* Таймер виртуального времени сделаем периодическим */ itvl.it_interval.tv_sec = 0; itvl.it_interval.tv_usec = 100000; /* 0.1 сек */ itvl.it_value.tv_sec = 0; itvl.it_value.tv_usec = 100000; if (setitimer (ITIMER_VIRTUAL, &itvl, NULL) < 0) { perror ("SETITIMER-VIRTUAL"); return (6); }
/* Убедимся, что пока процесс приостановлен, */ /* его виртуальное время также стоит */ sleep (5);
/* Нагрузим процессор вычислениями, которые не должны завершиться */ /* до срабатывания таймера реального времени */ { double s = 0; double d = 1; int i;
for (i = 1; i <= 100000000; i++) { s += d / i; d = -d; } }
return 0; }
Листинг 12.34. Пример программы, использующей интервальные таймеры реального и виртуального времени.
Закрыть окно




Прошедшее реальное время за один квант виртуального 1 5255378 мксек 2 249993 мксек 3 250003 мксек 4 100000 мксек 5 250177 мксек 6 859831 мксек 7 100054 мксек 8 729943 мксек 9 580003 мксек 10 99989 мксек 11 580129 мксек 12 429881 мксек 13 99990 мксек
Листинг 12.35. Возможные результаты работы программы, использующей интервальные таймеры реального и виртуального времени.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * */ /* Программа вызывает функции обработки */ /* и контролирует время их выполнения */ /* с помощью интервального таймера */ /* * * * * * * * * * * * * * * * * * * * */
#include <stdio.h> #include <sys/time.h> #include <signal.h> #include <setjmp.h>
/* Период интервального таймера (в секундах) */ #define IT_PERIOD 1
static jmp_buf buf_env; /* Буфер для функций setjmp и longjmp */
static double s; /* Результат функций обработки данных */
/* Функция обработки срабатывания таймера реального времени */ /* (сигнал SIGALRM) */ static void proc_sigalrm (int dummy) { printf ("Текущий результат текущей функции обработки данных: %g\n", s); longjmp (buf_env, 1); }
/* Первая функция обработки данных */ /* (вычисляет ln (2)) */ static void proc_data_1 (void) { double d = 1; int i;
s = 0; for (i = 1; i <= 100000000; i++) { s += d / i; d = -d; } }
/* Вторая функция обработки данных */ /* (вычисляет sqrt (2)) */ static void proc_data_2 (void) { s = 1; do { s = (s + 2 / s) * 0.5; } while ((s * s - 2) > 0.000000001); }
int main (void) { /* Массив указателей на функции обработки данных */ void (*fptrs []) (void) = {proc_data_1, proc_data_2, NULL}; /* Указатель на указатель */ /* на текущую функцию обработки данных */ void (**tfptr) (void); /* Вспомогательная временная переменная */ void (*tmpfptr) (void); struct itimerval itvl; struct sigaction sact; sigset_t sset; int i;
/* Установим реакцию на сигнал SIGALRM */ if (sigemptyset (&sset) < 0) { perror ("SIGEMPTYSET"); return (1); }
sact.sa_handler = proc_sigalrm; sact.sa_flags = SA_NODEFER; /* Не блокировать SIGALRM */ /* в функции обработки этого сигнала */ sact.sa_mask = sset; if (sigaction (SIGALRM, &sact, NULL) < 0) { perror ("SIGACTION"); return (2); }
/* На всякий случай сделаем таймер реального времени периодическим */ itvl.it_interval.tv_sec = IT_PERIOD; itvl.it_interval.tv_usec = 0;
/* Цикл вызова функций обработки данных. */ /* Выполним его дважды */ for (i = 0; i < 2; i++) { tfptr = fptrs; (void) setjmp (buf_env); /* Сюда вернется управление после срабатывания таймера */ while ((tmpfptr = *tfptr++) != NULL) { /* Даже если предыдущая функция обработки данных */ /* закончилась до того, как сработал таймер, */ /* обеспечим текущей функции полный интервал */ itvl.it_value.tv_sec = IT_PERIOD; itvl.it_value.tv_usec = 0; if (setitimer (ITIMER_REAL, &itvl, NULL) < 0) { perror ("SETITIMER"); return (3); } (*tmpfptr) (); printf ("Результат текущей функции обработки данных: %g\n", s); } }
return 0; }
Листинг 12.36. Пример программы, использующей интервальные таймеры реального времени для управления ходом выполнения программы.
Закрыть окно




Текущий результат текущей функции обработки данных: 0.693147 Результат текущей функции обработки данных: 1.41421 Текущий результат текущей функции обработки данных: 0.693147 Результат текущей функции обработки данных: 1.41421
Листинг 12.37. Возможные результаты работы программы, использующей интервальные таймеры реального времени для управления ходом выполнения программы.
Закрыть окно




if (setitimer (ITIMER_REAL, &itvl, NULL) < 0) { . . . }
. . .
(void) setjmp (buf_env);
Листинг 12.38. Пример некорректной последовательности взведения таймера и инициализации структуры для нелокального перехода.
Закрыть окно




#include <unistd.h> unsigned alarm (unsigned seconds);
Листинг 12.39. Описание функции alarm().
Закрыть окно



Приостановка выполнения на заданное время


Обычным средством ожидания наступления каких-либо событий, не прерывающих выполнения процесса (потока управления), является циклическое чередование приостановки выполнения на заданное время и опроса статуса события.

Функция sleep() (см. листинг 12.27) позволяет приостановить выполнение процесса (потока управления) на заданное число секунд. Если в это время будет доставлен сигнал, который не игнорируется, вызов sleep() завершится досрочно, а результат будет равен "недоспанному" числу секунд. (В случае нормального завершения результат равен нулю, что формально можно считать частным случаем предыдущего.)

#include <unistd.h> unsigned sleep (unsigned seconds);

Листинг 12.27. Описание функции sleep(). (html, txt)

Обратим внимание на то, что аргумент и результат функции sleep() описаны как unsigned. Это значит, что приложения, строго соответствующие стандарту POSIX-2001, не должны передавать sleep() величины, превышающие минимально допустимое значение конфигурационной константы UINT_MAX, которое в стандарте ISO C [5] полагается равным 65535. Использование больших величин, вообще говоря, ограничивает мобильность приложений.

Если вместо функции sleep() воспользоваться ее более современным аналогом nanosleep() (см. листинг 12.28), можно убить сразу двух зайцев: избавиться от обременительного ограничения на максимальную длительность приостановки выполнения и получить возможность задавать эту длительность с существенно более высокой точностью, ограниченной только разрешающей способностью часов реального времени.

#include <time.h> int nanosleep (const struct timespec *rqtp, struct timespec *rmtp);

Листинг 12.28. Описание функции nanosleep(). (html, txt)

Аргумент rqtp функции nanosleep() задает запрашиваемую длительность приостановки. По указателю rmtp, если он не пуст, возвращается "недоспанное" время. В отличие от sleep(), результат nanosleep() равен -1 - как в результате ошибки, так и при "недосыпании", вызванном доставкой сигнала.


(void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (&lt_mod)); fprintf (stderr, "%s %ld\n", dtbuf, st_buf.st_size); }

int main (int argc, char *argv []) { int res;

if (argc != 2) { fprintf (stderr, "Использование: %s файл\n", argv [0]); return (1); }

proc_init (argv [1]); while (1) { if ((res = check_mod (argv [1])) > 0) { proc_mod (); } else if (res &lt; 0) { return (res); } sleep (1); }

return 0; }

Листинг 12.29. Пример программы, использующей функцию sleep().

Возможные результаты работы этой программы показаны в листинге 12.30.

Данные о размере файла /var/log/cron Время изменения Размер Tue Jan 6 12:50:00 2004 11191 Tue Jan 6 13:01:00 2004 11263 Tue Jan 6 13:10:00 2004 11409 Tue Jan 6 13:20:00 2004 11481 . . . Tue Jan 6 13:40:00 2004 11624 . . .

Листинг 12.30. Возможные результаты работы программы, использующей функцию sleep().

Функция nanosleep() позволяет до некоторой степени промоделировать работу функций poll() и select(), реализующих пассивное ожидание готовности данных, в тех ситуациях, когда данные поступают из разнородных источников, которые poll() и select() не обслуживают (не только из файлов, но и из очередей сообщений, как результат опроса значений семафоров и т.п.). В листинге 12.31 показан цикл "полуактивного" (с короткими приостановками) ожидания поступления данных из канала и обнуления значения семафора.

#define N ... #define MY_BUFSIZ ...

struct timespec tmsp = {0, 20000000}; int fds [2]; /* Файловые дескрипторы канала */ int semid; /* Идентификатор набора семафоров */ struct sembuf sembuf [1]; /* Массив для задания операций над семафором */ char buf [MY_BUFSIZ]; /* Буфер для чтения данных из канала */ int brd; /* Число прочитанных байт */ int i;

. . .

fcntl (fds [0], F_SETFL, O_NONBLOCK); sembuf [0].sem_num = 0; sembuf [0].sem_flg = IPC_NOWAIT; sembuf [0].sem_op = 0;

for (i = 0; i &lt; N; i++) { nanosleep (&tmsp, NULL);

if ((brd = read (fds [0], buf, MY_BUFSIZ) > 0) { /* Обработка прочитанных данных. */ /* Возможно, поступление данных на этом не закончилось */ . . .continue; }

if (semop (semid, sembuf, 1) == 0) { /* Значение семафора обнулено, ждать больше нечего */ . . . if (brd <= 0) { close (fds [0]); break; } } }

. . .

Листинг 12.31. Пример реализации полуактивного ожидания наступления нескольких событий с помощью функции nanosleep().

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


Работа с интервальными таймерами


Интервальным таймером называется механизм, способный известить процесс (поток управления) об истечении заданного промежутка времени.

Описываемые далее средства для работы с интервальными таймерами входят в необязательную часть стандарта POSIX-2001, именуемую "X/Open-расширение системного интерфейса" (XSI).

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

ITIMER_REAL

Таймер реального времени. Он ассоциирован с часами CLOCK_REALTIME и, следовательно, его показания уменьшаются в реальном масштабе времени. Когда он срабатывает, процессу доставляется сигнал SIGALRM.

ITIMER_VIRTUAL

Таймер виртуального времени процесса. Его показания уменьшаются в соответствии с течением виртуального времени процесса, т. е. только тогда, когда процесс выполняется. При срабатывании таймера процессу доставляется сигнал SIGVTALRM.

ITIMER_PROF

Таймер профилирования. Показания этого таймера уменьшаются с течением виртуального времени процесса, а также тогда, когда работают запрошенные им системные сервисы. Он предназначен для построения статистическими методами профилей расходования процессом процессорного времени. При его срабатывании процессу доставляется сигнал SIGPROF.

Согласно стандарту POSIX-2001, интервальные таймеры обслуживаются функциями getitimer() и setitimer() (см. листинг 12.32).

#include <sys/time.h> int getitimer (int timer_id, struct itimerval *cvalue); int setitimer (int timer_id, const struct itimerval *restrict nvalue, struct itimerval *restrict ovalue);

Листинг 12.32. Описание функций getitimer() и setitimer(). (html, txt)

Поддерживаемые стандартом значения аргумента timer_id перечислены выше. Другие характеристики интервальных таймеров задаются в структуре типа itimerval, которая должна содержать по крайней мере следующие поля:

struct timeval it_interval; /* Интервал таймера */

struct timeval it_value; /* Текущие показания таймера */ /* (ведется обратный отсчет) */


(Напомним, что структура типа timeval была описана выше. Она содержит по крайней мере два поля, задающие время, соответственно, в секундах и микросекундах.)

Значение поля it_value (если оно отлично от нуля) показывает время, оставшееся до срабатывания таймера. После срабатывания таймер запускается вновь с начальным значением поля it_value, равным it_interval (если последнее отлично от нуля).

Установка нулевого значения в поле it_value, независимо от величины it_interval, снимает таймер со взвода. Если сделать нулевым значение it_interval, таймер будет разряжен после очередного срабатывания. Таким способом можно реализовать таймер с ограниченной периодичностью (в частности, одноразовый).

Функция getitimer() запоминает текущие характеристики таймера с заданным идентификатором в структуре, на которую указывает аргумент cvalue. Функция setitimer() - ее можно назвать многоцелевой - взводит или снимает таймер со взвода, устанавливает новые характеристики, пользуясь значением аргумента nvalue, и сохраняет старые по указателю ovalue (если он отличен от NULL).

Признаком успешного завершения обеих функций является нулевой результат.

Здесь уместно акцентировать внимание на одной тонкости, которую до сих пор мы старательно замалчивали. Поскольку разрешающая способность любых часов конечна, для показаний часов и таймеров можно установить только те значения, которые кратны этой разрешающей способности. Если соответствующие аргументы функций clock_settime(), nanosleep() или setitimer() не удовлетворяют данному условию, производится округление. Для часов используется наилучшее приближение снизу, для длительности приостановки выполнения и таймеров - наилучшее приближение сверху (стандарт POSIX-2001 требует, чтобы длительности приостановки выполнения и промежутка времени до срабатывания таймера были не меньше заказанных).

Последнее обстоятельство позволяет выяснить размер такта часов реального времени довольно неожиданным образом (впрочем, вполне стандартным и мобильным с точки зрения спецификаций POSIX-2001) (см.


листинг 12.33).

Листинг 12.33. Пример программы, использующей функции getitimer() и setitimer(). (html, txt)

Здесь интервальный таймер не взводится (поскольку значение it_value - нулевое) и, соответственно, не срабатывает. Он нужен лишь для того, чтобы операционная система подкорректировала должным образом (с учетом разрешающей способности часов реального времени) значение поля it_interval.tv_usec . В результате корректировки первоначально присвоенная этому полю единица может превратиться, например, в 10000 (микросекунд), что дает тактовую частоту часов реального времени 100 Гц.

Следующая программа (см. листинг 12.34) сравнивает ход реального и виртуального времени процесса, определяя, сколько реального времени прошло за один квант виртуального (в данном случае квант - это 100000 мксек, т. е. 0.1 сек). Колебания показаний, естественно, вызываются нагрузкой на процессор, индуцируемой другими, параллельно работающими процессами. В принципе, целенаправленная организация подобных колебаний и их анализ могут быть использованы для создания так называемых скрытых каналов по времени (см. [7]).

Листинг 12.34. Пример программы, использующей интервальные таймеры реального и виртуального времени. (html, txt)

Результаты работы программы могут выглядеть так, как показано в листинге 12.35. Можно видеть, что, во-первых, доля процессорного времени, которая достается процессу, может существенно колебаться (см., например, отсчеты 4, 6 и 8) и, во-вторых, пока выполнение процесса приостановлено, его виртуальное время также стоит (см. инструкцию sleep (5) в листинге 12.34, отсчет 1 в результатах).

Прошедшее реальное время за один квант виртуального 1 5255378 мксек 2 249993 мксек 3 250003 мксек 4 100000 мксек 5 250177 мксек 6 859831 мксек 7 100054 мксек 8 729943 мксек 9 580003 мксек 10 99989 мксек 11 580129 мксек 12 429881 мксек 13 99990 мксек

Листинг 12.35. Возможные результаты работы программы, использующей интервальные таймеры реального и виртуального времени. (html, txt)


Определение языково-культурной среды


Определение языково-культурной среды помещается в файлы, к описанию формата которых мы и приступаем. Можно считать, что в подобных файлах, называемых далее файлами определения среды, содержится «исходный текст» определения, поскольку, чтобы возыметь действие, они должны быть обработаны утилитой localedef или эквивалентным средством.

Файл определения среды должен содержать определение одной или нескольких категорий, которое может сводиться к директиве copy (см. далее).

Определение категории состоит из заголовка, тела и хвостовика. Заголовок именует категорию; он должен начинаться с символов LC_. Хвостовик строится из слова END и имени, употребленного в качестве заголовка.

Первому заголовку могут предшествовать строки, переопределяющие символ комментария (по умолчанию – #) и управляющий символ (\).

Тело категории состоит из одной или нескольких строк, содержащих идентификатор и, возможно, следующие за ним операнды. В качестве идентификатора может выступать как ключевое слово, именующее отдельный элемент языково-культурной среды, так и элемент алфавитного сравнения символов. Ключевые слова должны быть уникальными в пределах среды и не начинаться с префикса LC_.

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

Символы и элементы сравнения представляются определенными ниже именами. Кроме того, символы могут обозначаться естественным образом или кодами; правда, при этом определение языково-культурной среды теряет в мобильности.

Имена символов заключаются в угловые скобки (например, <A>, <z> и т.п.). Такая конструкция должна в точности совпадать с именем, определенным в файле отображения символов, который специфицирует опция -f утилиты localedef.

Реализация должна поддерживать по крайней мере один набор символов и соответствующий файл отображения. Любой набор должен включать в себя все элементы из так называемого мобильного набора символов, определяемого стандартом POSIX-2001 (их имена перечислены в пример 13.2).


Файл отображения символов имеет довольно простую структуру. Если отвлечься от несущественных деталей, он должен состоять из директив вида

< имя_символа> <код> <комментарий>

Эти директивы заключаются между строками

Эти директивы заключаются между строками

и

END CHARMAP

Перед строкой CHARMAP может располагаться определение имени набора символов, которое задается директивой

< code_set_name> имя

Фрагмент возможного описания набора символов KOI8-R показан в пример 13.1.

Пример 13.1. Фрагмент файла отображения символов для кодировки KOI8-R. (html, txt)

Опишем теперь формат определения категорий языково-культурной среды.

Категория LC_CTYPE определяет классификацию символов, преобразование регистра и другие атрибуты. К ней относятся следующие ключевые слова: upper (прописные буквы; для POSIX-среды – 26 латинских букв верхнего регистра), lower (строчные буквы), alpha (буквы), digit (цифры), alnum (буквы и цифры), space (пробельные символы; для POSIX-среды в их число, помимо пробела, входят перевод строки, т абуляция и т.п.), cntrl (управляющие символы), punct (знаки пунктуации), graph (печатные символы за исключением пробела), print (печатные символы), xdigit (шестнадцатеричные цифры), blank (для POSIX-среды – пробел и табуляция).

С ключевым словом copy задается имя существующей среды, у которой наследуется определение категории. При наличии данной директивы другие ключевые слова недопустимы.

Ключевое слово charclass позволяет задать имена специфичных для определяемой среды классов символов. Каждый такой класс далее должен быть описан в определении категории LC_CTYPE по образу и подобию перечисленных выше стандартных классов.

Ключевое слово toupper открывает определение отображения малых букв в большие. Операнд этого слова представляет собой пару символов, заключенных в скобки и разделенных запятой. Аналогичным образом устроен элемент tolower.

В пример 13.2 показано определение категории LC_CTYPE для POSIX-среды.

Пример 13.2. Определение категории LC_CTYPE для POSIX-среды. (html, txt)



С ключевым словом copy задается имя существующей среды, у которой наследуется определение категории. При наличии данной директивы другие ключевые слова недопустимы.

Ключевое слово charclass позволяет задать имена специфичных для определяемой среды классов символов. Каждый такой класс далее должен быть описан в определении категории LC_CTYPE по образу и подобию перечисленных выше стандартных классов.

Ключевое слово toupper открывает определение отображения малых букв в большие. Операнд этого слова представляет собой пару символов, заключенных в скобки и разделенных запятой. Аналогичным образом устроен элемент tolower.

В пример 13.2 показано определение категории LC_CTYPE для POSIX-среды.

LC_CTYPE # POSIX-среда, категория LC_CTYPE # "alpha" по определению есть объединение элементов "upper" и "lower" # "alnum" по определению есть объединение элементов "alpha" и "digit" # "print" по определению есть объединение элементов "alnum", "punct" и <space> # "graph" по определению есть объединение элементов "alnum" и "punct" # upper <A>;<B>;<C>;<D>;<E>;<F>;<G>;<H>;<I>;<J>;<K>;\ <L>;<M>;<N>;<O>;<P>;<Q>;<R>;<S>;<T>;<U>;<V>;<W>;\ <X>;<Y>;<Z> # lower <a>;<b>;<c>;<d>;<e>;<f>;<g>;<h>;<i>;<j>;<k>;\ <l>;<m>;<n>;<o>;<p>;<q>;<r>;<s>;<t>;<u>;<v>;<w>;\ <x>;<y>;<z> # digit <zero>;<one>;<two>;<three>;<four>;<five>;\ <six>;<seven>;<eight>;<nine> # space <tab>;<newline>;<vertical-tab>;<form-feed>;\ <carriage-return>;<space> # cntrl <alert>;<backspace>;<tab>;<newline>;\ <vertical-tab>;<form-feed>;<carriage-return>;\ <NUL>;<SOH>;<STX>;<ETX>;<EOT>;<ENQ>;<ACK>;<SO>;\ <SI>;<DLE>;<DC1>;<DC2>;<DC3>;<DC4>;<NAK>;<SYN>;\ <ETB>;<CAN>;<EM>;<SUB>;<ESC>;<IS4>;<IS3>;<IS2>;\ <IS1>;<DEL> # punct <exclamation-mark>;<quotation-mark>;\ <number-sign>;<dollar-sign>;<percent-sign>;\ <ampersand>;<apostrophe>;<left-parenthesis>;\ <right-parenthesis>;<asterisk>;<plus-sign>;\ <comma>;<hyphen>;<period>;<slash>;<colon>;\ <semicolon>;<less-than-sign>;<equals-sign>;\ <greater-than-sign>;<question-mark>;\ <commercial-at>;<left-square-bracket>;\ <backslash>;<right-square-bracket>;\ <circumflex>;<underscore>;<grave-accent>;\ <left-curly-bracket>;<vertical-line>;\ <right-curly-bracket>;<tilde> # xdigit <zero>;<one>;<two>;<three>;<four>;<five>;\ <six>;<seven>;<eight>;<nine>;<A>;<B>;<C>;<D>;\ <E>;<F>;<a>;<b>;<c>;<d>;<e>;<f> # blank <space>;<tab> # toupper (<a>,<A>);(<b>,<B>);(<c>,<C>);(<d>,<D>);\ (<e>,<E>);(<f>,<F>);(<g>,<G>);(<h>,<H>);\ (<i>,<I>);(<j>,<J>);(<k>,<K>);(<l>,<L>);(<m>,<M>);\ (<n>,<N>);(<o>,<O>);(<p>,<P>);(<q>,<Q>);(<r>,<R>);\ (<s>,<S>);(<t>,<T>);(<u>,<U>);(<v>,<V>);(<w>,<W>);\ (<x>,<X>);(<y>,<Y>);(<z>,<Z>) # tolower (<A>,<a>);(<B>,<b>);(<C>,<c>);(<D>,<d>);\ (<E>,<e>);(<F>,<f>);(<G>,<g>);(<H>,<h>);\ (<I>,<i>);(<J>,<j>);(<K>,<k>);(<L>,<l>);\ (<M>,<m>);(<N>,<n>);(<O>,<o>);(<P>,<p>);\( <Q>,<q>);(<R>,<r>);(<S>,<s>);(<T>,<t>);\ <U>,<u>);(<V>,<v>);(<W>,<w>);(<X>,<x>);(\ <Y>,<y>);(<Z>,<z>) END LC_CTYPE



Пример 13.2. Определение категории LC_CTYPE для POSIX-среды.

Категория LC_COLLATE определяет порядок алфавитного сравнения символов для многочисленных служебных программ (sort, uniq и т.д.), регулярных выражений, а также функций strcoll(), strxfrm() и других.

В алфавитном сравнении могут участвовать односимвольные и многосимвольные элементы. Их порядок задается весами, которых у каждого элемента может быть несколько (не более COLL_WEIGHTS_MAX). Первый вес называется основным. Если при сравнении элементов основные веса совпали, используются соответственные пары дополнительных – до тех пор, пока либо не будет обнаружено неравенство, либо не кончатся веса.

Допускается формирование классов эквивалентности – присвоение одного основного веса нескольким элементам сравнения. Поддерживаются также отображения один-ко-многим (один символ отображается в последовательность элементов сравнения).

Разумеется, сравнение цепочек символов начинается с разбиения на элементы сравнения.

Правила алфавитного сравнения и директивы присвоения весов открываются ключевым словом order_start и завершаются директивой order_end. Подразумеваемым правилом является forward – сравнение от начала к концу цепочки. Возможен и противоположный порядок (backward).

После строки order_start элементы сравнения перечисляются в порядке возрастания. После элемента может быть задана последовательность его весов. Веса задаются как относительные, в виде имен других элементов и определяются позицией этих элементов при перечислении. Специальный вес IGNORE означает, что данный элемент следует игнорировать при сравнении цепочек. Многоточие в качестве элемента сравнения обозначает диапазон от предыдущего до последующего элементов. Элемент UNDEFINED представляет все, что не было указано явно.

В POSIX-среде алфавитный порядок совпадает с упорядоченностью символов в кодировке ASCII.

Категория LC_MONETARY определяет формат денежных величин. Входящие в нее элементы именуются аналогично полям приведенной выше структуры lconv.


Например, после ключевого слова currency_symbol задается цепочка символов, обозначающая местные денежные величины. Если ключевое слово данной категории отсутствует в файле определения среды либо его значением является пустая цепочка или -1, значит, соответствующий элемент в языково-культурной среде неспецифицирован.

В POSIX-среде все элементы этой категории остаются неспецифицированными.

В категории LC_NUMERIC, во многом аналогичной LC_MONETARY, определяются правила и обозначения, используемые применительно к числовым данным. Здесь также действует параллель со структурой lconv.

В пример 13.3 показано определение категории LC_NUMERIC для POSIX-среды. Собственно, специфицируется только десятичная точка.

LC_NUMERIC # POSIX-среда, категория LC_NUMERIC # decimal_point "<period>" thousands_sep "" grouping -1 # END LC_NUMERIC

Пример 13.3. Определение категории LC_NUMERIC для POSIX-среды.

Элементы категории LC_TIME определяют интерпретацию спецификаторов преобразований и, тем самым, поведение служебной программы date, а также функций strftime(), strptime() и некоторых других. В число поддерживаемых ключевых слов входят abday (сокращенные названия дней недели, начиная с воскресенья; элемент соответствует спецификатору %a), day (полные названия дней недели, %A), abmon (сокращенные названия месяцев, %b), mon (полные названия месяцев, %B), d_t_fmt (принятое в данной языково-культурной среде представление даты и времени, %c), d_fmt (принятое в данной среде представление даты, %x), t_fmt (принятое в данной среде представление времени, %X).

Особым образом устроен элемент era, определяющий способ отсчета и отображения лет для каждой эры, поддерживаемой языково-культурной средой. Операнд ключевого слова era состоит из цепочек символов, каждая из которых описывает конкретную эру и имеет следующий формат:

направление:смещение:начальная_дата: конечная_дата:имя_эры:формат_эры

Здесь направление – знак плюс или минус, смещение – ближайший к начальной дате номер года, начальная_дата – цепочка вида гггг/мм/дд (год, месяц и число начала эры), конечная_дата – год, месяц и число конца эры либо цепочки «-*» (конечной датой служит начало отсчета времени) или «+*» (конечной датой служит конец отсчета времени), формат_эры – цепочка, используемая при форматировании номеров года в эре.



Отметим, что точка отсчета может быть как самой ранней, так и самой поздней в эре. Примерами служат две христианские эры – после и до Рождества Христова, соответственно.

В пример 13.4 показано определение категории LC_TIME для POSIX-среды.

LC_TIME # POSIX-среда, категория LC_TIME # # Сокращенные названия дней недели (%a) abday "<S><u><n>";"<M><o><n>";"<T><u><e>";\ "<W><e><d>";"<T><h><u>";"<F><r><i>";"<S><a><t>" # # Полные названия дней недели (%A) day "<S><u><n><d><a><y>";\ "<M><o><n><d><a><y>";\ "<T><u><e><s><d><a><y>";\ "<W><e><d><n><e><s><d><a><y>";\ "<T><h><u><r><s><d><a><y>";\ "<F><r><i><d><a>\<y>";\ "<S><a><t><u><r><d><a><y>" # # Сокращенные названия месяцев (%b) abmon "<J><a><n>";"<F><e><b>";"<M><a><r>";\ "<A><p><r>";"<M><a><y>";"<J><u><n>";\ "<J><u><l>";"<A><u><g>";"<S><e><p>";\ "<O><c><t>";"<N><o><v>";"<D><e><c>" # # Полные названия месяцев (%B) mon "<J><a><n><u><a><r><y>"\ "<F><e><b><r><u><a><r><y>";\ "<M><a><r><c><h>";"<A><p><r><i><l>";\ "<M><a><y>";"<J><u><n><e>";\ "<J><u><l><y>";"<A><u><g><u><s><t>";\ "<S><e><p><t><e><m><b><e><r>";\ "<O><c><t><o><b><e><r>";\ "<N><o><v><e><m><b><e><r>";\ "<D><e><c><e><m><b><e><r>" # # Эквивалент AM/PM (%p) "AM";"PM" am_pm "<A><M>";"<P><M>" # # Принятое в POSIX-среде представление даты и времени (%c) # "%a %b %e %H:%M:%S %Y" d_t_fmt "<percent-sign><a><space><percent-sign>\ <b><space><percent-sign><e><space>\ <percent-sign><H><colon><percent-sign>\ <M><colon><percent-sign><S><space>\ <percent-sign><Y>" # # Принятое в POSIX-среде представление даты (%x) "%m/%d/%y" d_fmt "<percent-sign><m><slash><percent-sign><d>\ <slash><percent-sign><y>" # # Принятое в POSIX-среде представление времени (%X) "%H:%M:%S" t_fmt "<percent-sign><H><colon><percent-sign><M>\ <colon><percent-sign><S>" # # Принятое в POSIX-среде 12-часовое представление времени (%r) "%I:%M:%S %p" t_fmt_ampm "<percent-sign><I><colon><percent-sign\ <M><colon><percent-sign><S><space>\ <percent_sign><p>" # END LC_TIME

Пример 13.4. Определение категории LC_TIME для POSIX-среды.

Категория LC_MESSAGES играет весьма ограниченную роль, определяя положительные и отрицательные ответы. Соответственно, она содержит два элемента – yesexpr и noexpr, значениями которых служат расширенные регулярные выражения. Для POSIX-среды данная категория описывается так, как показано в пример 13.5.

LC_MESSAGES # POSIX-среда, категория LC_MESSAGES # yesexpr "<circumflex><left-square-bracket>\ <y><Y><right-square-bracket>" # noexpr "<circumflex><left-square-bracket>\ <n><N><right-square-bracket>" # END LC_MESSAGES

Пример 13.5. Определение категории LC_MESSAGES для POSIX-среды.


Основные понятия и объекты


Согласно стандарту POSIX-2001, языково-культурная среда – это часть пользовательского окружения, зависящая от языковых и культурных соглашений.

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

Языково-культурная среда формируется из данных, принадлежащих нескольким именованным категориям. Каждая категория управляет определенными аспектами поведения компонентов системы. Имена и назначение категорий соответствуют следующим переменным окружения.

LC_CTYPE

Классификация символов, преобразование регистра (верхний/нижний) символов.

LC_COLLATE

Порядок алфавитного сравнения символов.

LC_MONETARY

Форматирование денежных величин.

LC_NUMERIC

Форматирование числовых (но не денежных) величин.

LC_TIME

Форматы даты и времени.

LC_MESSAGES

Форматы информационных и диагностических сообщений и интерактивных ответов.

Категории подразделяются на более мелкие элементы, средством именования которых служат ключевые слова. Например, в категорию LC_CTYPE входят элементы с именами alpha (буквы), digits (цифры) и т.п.

Как правило, значениями перечисленных переменных окружения являются абсолютные маршрутные имена файлов, содержащих соответствующие определения. Другой вариант (если значение не начинается с символа /) – имя среды.

Расширение XSI стандарта POSIX-2001 уточняет один из возможных форматов имени языково-культурной среды:

язык[_территория][.кодировка][@модификатор]

Примеры: ru_RU.koi8r, ru_UA.

Модификатор позволяет выбрать конкретный вариант данных о среде в пределах отдельной категории (скажем, словарный порядок сравнения в категории LC_COLLATE). Пример: LC_COLLATE=De_DE@dict.

В каждой реализации определены одна или несколько языковокультурных сред.
Поддержка POSIX-среды с именами-синонимами «POSIX» и «C» является обязательной. Кроме того, возможно создание новых сред, если в системе определен конфигурационный параметр _POSIX2_LOCALEDEF.

Стандартом POSIX-2001 предусмотрены две «собирательные» переменные окружения, определяющие сразу все (или почти все) категории языково-культурной среды.

LC_ALL

Значение этой переменной окружения учитывается в первую очередь.

LANG

Значение этой переменной окружения учитывается в последнюю очередь, если не определены значения переменных вида LC_*.

В любой реализации некоторая среда (POSIX или иная) должна быть определена как подразумеваемая, т. е. используемая тогда, когда перечисленные выше переменные окружения не определены или имеют пустые значения. Это можно сделать, например, путем присваивания LANG=POSIX или LANG=C, после чего подразумеваемой станет POSIX-среда.

В структуре lconv, описанной во включаемом файле <locale.h>, содержатся поля, определяющие форматы денежных и числовых величин. Даже беглый взгляд на перечень этих полей позволяет составить представление о богатстве возможностей определения языково-культурной среды.

char *currency_symbol; /* Местное обозначение денежной величины */

char *decimal_point; /* Символ, отделяющий целую часть */ /* числа от дробной */

char frac_digits; /* Количество цифр в дробной */ /* части местных денежных величин */

char *grouping; /* Размеры групп цифр при */ /* форматировании чисел */

char *int_curr_symbol; /* Международное обозначение денежной */ /* величины */

char int_frac_digits; /* Количество цифр в дробной части */ /* международных денежных величин */

char int_n_cs_precedes; /* Признак того, что международное */ /* обозначение предшествует отрицательной */ /* денежной величине, а не следует за ней */

char int_n_sep_by_space; /* Признак того, что международное */ /* обозначение отделяется от отрицательной */ /* денежной величины пробелом */

char int_n_sign_posn; /* Позиция знака минус в международных */ /* обозначенияхотрицательных денежных */ /* величин */



Поддержка POSIX-среды с именами-синонимами «POSIX» и «C» является обязательной. Кроме того, возможно создание новых сред, если в системе определен конфигурационный параметр _POSIX2_LOCALEDEF.

Стандартом POSIX-2001 предусмотрены две «собирательные» переменные окружения, определяющие сразу все (или почти все) категории языково-культурной среды.

LC_ALL

Значение этой переменной окружения учитывается в первую очередь.

LANG

Значение этой переменной окружения учитывается в последнюю очередь, если не определены значения переменных вида LC_*.

В любой реализации некоторая среда (POSIX или иная) должна быть определена как подразумеваемая, т. е. используемая тогда, когда перечисленные выше переменные окружения не определены или имеют пустые значения. Это можно сделать, например, путем присваивания LANG=POSIX или LANG=C, после чего подразумеваемой станет POSIX-среда.

В структуре lconv, описанной во включаемом файле <locale.h>, содержатся поля, определяющие форматы денежных и числовых величин. Даже беглый взгляд на перечень этих полей позволяет составить представление о богатстве возможностей определения языково-культурной среды.

char *currency_symbol; /* Местное обозначение денежной величины */

char *decimal_point; /* Символ, отделяющий целую часть */ /* числа от дробной */

char frac_digits; /* Количество цифр в дробной */ /* части местных денежных величин */

char *grouping; /* Размеры групп цифр при */ /* форматировании чисел */

char *int_curr_symbol; /* Международное обозначение денежной */ /* величины */

char int_frac_digits; /* Количество цифр в дробной части */ /* международных денежных величин */

char int_n_cs_precedes; /* Признак того, что международное */ /* обозначение предшествует отрицательной */ /* денежной величине, а не следует за ней */

char int_n_sep_by_space; /* Признак того, что международное */ /* обозначение отделяется от отрицательной */ /* денежной величины пробелом */

char int_n_sign_posn; /* Позиция знака минус в международных */ /* обозначенияхотрицательных денежных */ /* величин */


xc0 CYRILLIC SMALL LETTER YU


< code_set_name> KOI8-R <comment_char> % <escape_char> / % version: 1.0 % source: RFC1489 via Gabor Kiss <kissg@sztaki.hu> % and Andrey A. Chernov <ache@astral.msk.su> CHARMAP <U0000> /x00 NULL (NUL) <U0001> /x01 START OF HEADING (SOH) <U0002> /x02 START OF TEXT (STX) . . . <U0020> /x20 SPACE <U0021> /x21 EXCLAMATION MARK . . . <U0030> /x30 DIGIT ZERO <U0031> /x31 DIGIT ONE . . . <U0041> /x41 LATIN CAPITAL LETTER A <U0042> /x42 LATIN CAPITAL LETTER B . . . <U0061> /x61 LATIN SMALL LETTER A <U0062> /x62 LATIN SMALL LETTER B . . . <U044E> / xc0 CYRILLIC SMALL LETTER YU <U0430> /xc1 CYRILLIC SMALL LETTER A <U0431> /xc2 CYRILLIC SMALL LETTER BE . . . <U0429> /xfd CYRILLIC CAPITAL LETTER SHCHA <U0427> /xfe CYRILLIC CAPITAL LETTER CHE <U042A> /xff CYRILLIC CAPITAL LETTER HARD SIGN END CHARMAP
Пример 13.1. Фрагмент файла отображения символов для кодировки KOI8-R.
Закрыть окно




LC_CTYPE # POSIX-среда, категория LC_CTYPE # "alpha" по определению есть объединение элементов "upper" и "lower" # "alnum" по определению есть объединение элементов "alpha" и "digit" # "print" по определению есть объединение элементов "alnum", "punct" и <space> # "graph" по определению есть объединение элементов "alnum" и "punct" # upper <A>;<B>;<C>;<D>;<E>;<F>;<G>;<H>;<I>;<J>;<K>;\ <L>;<M>;<N>;<O>;<P>;<Q>;<R>;<S>;<T>;<U>;<V>;<W>;\ <X>;<Y>;<Z> # lower <a>;<b>;<c>;<d>;<e>;<f>;<g>;<h>;<i>;<j>;<k>;\ <l>;<m>;<n>;<o>;<p>;<q>;<r>;<s>;<t>;<u>;<v>;<w>;\ <x>;<y>;<z> # digit <zero>;<one>;<two>;<three>;<four>;<five>;\ <six>;<seven>;<eight>;<nine> # space <tab>;<newline>;<vertical-tab>;<form-feed>;\ <carriage-return>;<space> # cntrl <alert>;<backspace>;<tab>;<newline>;\ <vertical-tab>;<form-feed>;<carriage-return>;\ <NUL>;<SOH>;<STX>;<ETX>;<EOT>;<ENQ>;<ACK>;<SO>;\ <SI>;<DLE>;<DC1>;<DC2>;<DC3>;<DC4>;<NAK>;<SYN>;\ <ETB>;<CAN>;<EM>;<SUB>;<ESC>;<IS4>;<IS3>;<IS2>;\ <IS1>;<DEL> # punct <exclamation-mark>;<quotation-mark>;\ <number-sign>;<dollar-sign>;<percent-sign>;\ <ampersand>;<apostrophe>;<left-parenthesis>;\ <right-parenthesis>;<asterisk>;<plus-sign>;\ <comma>;<hyphen>;<period>;<slash>;<colon>;\ <semicolon>;<less-than-sign>;<equals-sign>;\ <greater-than-sign>;<question-mark>;\ <commercial-at>;<left-square-bracket>;\ <backslash>;<right-square-bracket>;\ <circumflex>;<underscore>;<grave-accent>;\ <left-curly-bracket>;<vertical-line>;\ <right-curly-bracket>;<tilde> # xdigit <zero>;<one>;<two>;<three>;<four>;<five>;\ <six>;<seven>;<eight>;<nine>;<A>;<B>;<C>;<D>;\ <E>;<F>;<a>;<b>;<c>;<d>;<e>;<f> # blank <space>;<tab> # toupper (<a>,<A>);(<b>,<B>);(<c>,<C>);(<d>,<D>);\ (<e>,<E>);(<f>,<F>);(<g>,<G>);(<h>,<H>);\ (<i>,<I>);(<j>,<J>);(<k>,<K>);(<l>,<L>);(<m>,<M>);\ (<n>,<N>);(<o>,<O>);(<p>,<P>);(<q>,<Q>);(<r>,<R>);\ (<s>,<S>);(<t>,<T>);(<u>,<U>);(<v>,<V>);(<w>,<W>);\ (<x>,<X>);(<y>,<Y>);(<z>,<Z>) # tolower (<A>,<a>);(<B>,<b>);(<C>,<c>);(<D>,<d>);\ (<E>,<e>);(<F>,<f>);(<G>,<g>);(<H>,<h>);\ (<I>,<i>);(<J>,<j>);(<K>,<k>);(<L>,<l>);\ (<M>,<m>);(<N>,<n>);(<O>,<o>);(<P>,<p>);\( <Q>,<q>);(<R>,<r>);(<S>,<s>);(<T>,<t>);\ <U>,<u>);(<V>,<v>);(<W>,<w>);(<X>,<x>);(\ <Y>,<y>);(<Z>,<z>) END LC_CTYPE
Пример 13.2. Определение категории LC_CTYPE для POSIX-среды.
Закрыть окно




LC_NUMERIC # POSIX-среда, категория LC_NUMERIC # decimal_point "<period>" thousands_sep "" grouping -1 # END LC_NUMERIC
Пример 13.3. Определение категории LC_NUMERIC для POSIX-среды.
Закрыть окно




LC_TIME # POSIX-среда, категория LC_TIME # # Сокращенные названия дней недели (%a) abday "<S><u><n>";"<M><o><n>";"<T><u><e>";\ "<W><e><d>";"<T><h><u>";"<F><r><i>";"<S><a><t>" # # Полные названия дней недели (%A) day "<S><u><n><d><a><y>";\ "<M><o><n><d><a><y>";\ "<T><u><e><s><d><a><y>";\ "<W><e><d><n><e><s><d><a><y>";\ "<T><h><u><r><s><d><a><y>";\ "<F><r><i><d><a>\<y>";\ "<S><a><t><u><r><d><a><y>" # # Сокращенные названия месяцев (%b) abmon "<J><a><n>";"<F><e><b>";"<M><a><r>";\ "<A><p><r>";"<M><a><y>";"<J><u><n>";\ "<J><u><l>";"<A><u><g>";"<S><e><p>";\ "<O><c><t>";"<N><o><v>";"<D><e><c>" # # Полные названия месяцев (%B) mon "<J><a><n><u><a><r><y>"\ "<F><e><b><r><u><a><r><y>";\ "<M><a><r><c><h>";"<A><p><r><i><l>";\ "<M><a><y>";"<J><u><n><e>";\ "<J><u><l><y>";"<A><u><g><u><s><t>";\ "<S><e><p><t><e><m><b><e><r>";\ "<O><c><t><o><b><e><r>";\ "<N><o><v><e><m><b><e><r>";\ "<D><e><c><e><m><b><e><r>" # # Эквивалент AM/PM (%p) "AM";"PM" am_pm "<A><M>";"<P><M>" # # Принятое в POSIX-среде представление даты и времени (%c) # "%a %b %e %H:%M:%S %Y" d_t_fmt "<percent-sign><a><space><percent-sign>\ <b><space><percent-sign><e><space>\ <percent-sign><H><colon><percent-sign>\ <M><colon><percent-sign><S><space>\ <percent-sign><Y>" # # Принятое в POSIX-среде представление даты (%x) "%m/%d/%y" d_fmt "<percent-sign><m><slash><percent-sign><d>\ <slash><percent-sign><y>" # # Принятое в POSIX-среде представление времени (%X) "%H:%M:%S" t_fmt "<percent-sign><H><colon><percent-sign><M>\ <colon><percent-sign><S>" # # Принятое в POSIX-среде 12-часовое представление времени (%r) "%I:%M:%S %p" t_fmt_ampm "<percent-sign><I><colon><percent-sign\ <M><colon><percent-sign><S><space>\ <percent_sign><p>" # END LC_TIME
Пример 13.4. Определение категории LC_TIME для POSIX-среды.
Закрыть окно




LC_MESSAGES # POSIX-среда, категория LC_MESSAGES # yesexpr "<circumflex><left-square-bracket>\ <y><Y><right-square-bracket>" # noexpr "<circumflex><left-square-bracket>\ <n><N><right-square-bracket>" # END LC_MESSAGES
Пример 13.5. Определение категории LC_MESSAGES для POSIX-среды.
Закрыть окно




LANG=ru_RU.koi8r LC_CTYPE="ru_RU.koi8r" LC_NUMERIC="ru_RU.koi8r" LC_TIME="ru_RU.koi8r" LC_COLLATE="ru_RU.koi8r" LC_MONETARY="ru_RU.koi8r" LC_MESSAGES="ru_RU.koi8r" LC_PAPER="ru_RU.koi8r" LC_NAME="ru_RU.koi8r" LC_ADDRESS="ru_RU.koi8r" LC_TELEPHONE="ru_RU.koi8r" LC_MEASUREMENT="ru_RU.koi8r" LC_IDENTIFICATION="ru_RU.koi8r" LC_ALL=
Пример 13.6. Возможный результат работы служебной программы locale.
Закрыть окно




LC_TIME abday="Вск;Пнд;Втр;Срд;Чтв;Птн;Сбт" day="Воскресенье;Понедельник;Вторник;Среда; Четверг;Пятница;Суббота" abmon="Янв;Фев;Мар;Апр;Май;Июн;Июл;Авг;Сен; Окт;Ноя;Дек" mon="Января;Февраля;Марта;Апреля;Мая;Июня; Июля;Августа;Сентября;Октября;Ноября;Декабря" d_t_fmt="%a %d %b %Y %T" d_fmt="%d.%m.%Y" t_fmt="%T" . . . first_weekday=1 first_workday=1 cal_direction=1 date_fmt="%a %b %e %H:%M:%S %Z %Y" time-codeset="KOI8-R"
Пример 13.7. Фрагмент возможного результата выполнения команды locale -ck LC_TIME.
Закрыть окно




if echo "$response" | grep -Eq "$(locale yesexpr)" then echo " Ответ положительный" else echo "Ответ отрицательный" fi
Пример 13.8. Фрагмент интернационализированного варианта shell-процедуры.
Закрыть окно




#include <locale.h> char *setlocale ( int category, const char *locale);
Пример 13.9. Описание функции setlocale().
Закрыть окно




#include <locale.h> struct lconv *localeconv (void);
Пример 13.10. Описание функции localeconv().
Закрыть окно




#include <stdio.h> #include <locale.h>
/* Функция опрашивает и выводит цепочки символов, */ /* ассоциированные с текущей средой и ее */ /* категориями */
void print_curr_locale_data (void) { /* Массив категорий для setlocale() */ int ctgrs [] = {LC_ALL, LC_COLLATE, LC_CTYPE, LC_MESSAGES, LC_MONETARY, LC_NUMERIC, LC_TIME}; /* Массив имен категорий */ char *ctgrnms [] = {"LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME"}; unsigned int i; printf ("Цепочки символов, ассоциированные в текущей среде с\n"); for (i = 0; i < sizeof (ctgrs) / sizeof (int); i++) { printf (" %s: %s\n", ctgrnms [i], setlocale (ctgrs [i], NULL)); } }
int main (void) { struct lconv *plc; /* Убедимся, что подразумеваемой является */ /* POSIX-среда */ printf ("Текущая языково-культурная среда: подразумеваемая\n"); print_curr_locale_data(); /* Установим местную среду */ (void) setlocale (LC_ALL, ""); /* Опросим и выдадим ее характеристики */ printf ("Текущая языково-культурная среда: местная\n"); print_curr_locale_data(); plc = localeconv (); printf ("Некоторые элементы категорий LC_MONETARY и LC_NUMERIC\n"); printf ("int_curr_symbol: %s\n", plc->int_curr_symbol); printf ("currency_symbol: %s\n", plc->currency_symbol); printf ("mon_decimal_point: %s\n", plc->mon_decimal_point); printf ("decimal_point: %s\n", plc->decimal_point); printf ("thousands_sep: %s\n", plc->thousands_sep); /* Сделаем "денежную" категорию украинской */ if (setlocale (LC_MONETARY, "ru_UA") == NULL) { perror ("SETLOCALE"); return (1); } printf ("Категория LC_MONETARY переустановлена для Украины\n"); print_curr_locale_data(); plc = localeconv (); printf ("Некоторые элементы категории LC_MONETARY\n"); printf ("int_curr_symbol: %s\n", plc->int_curr_symbol); printf ("currency_symbol: %s\n", plc->currency_symbol); return 0; }
Пример 13.11. Пример использования функций setlocale() и localeconv().
Закрыть окно




Текущая языково- культурная среда: подразумеваемая Цепочки символов, ассоциированные в текущей среде с LC_ALL: C LC_COLLATE: C LC_CTYPE: C LC_MESSAGES: C LC_MONETARY: C LC_NUMERIC: C LC_TIME: C Текущая языково-культурная среда: местная Цепочки символов, ассоциированные в текущей среде с LC_ALL: ru_RU.koi8r LC_COLLATE: ru_RU.koi8r LC_CTYPE: ru_RU.koi8r LC_MESSAGES: ru_RU.koi8r LC_MONETARY: ru_RU.koi8r LC_NUMERIC: ru_RU.koi8r LC_TIME: ru_RU.koi8r Некоторые элементы категорий LC_MONETARY и LC_NUMERIC int_curr_symbol: RUR currency_symbol: РУБ mon_decimal_point: . decimal_point: , thousands_sep: . Категория LC_MONETARY переустановлена для Украины Цепочки символов, ассоциированные в текущей среде с LC_ALL: LC_CTYPE=ru_RU.koi8r;LC_NUMERIC=ru_RU.koi8r; LC_TIME=ru_RU.koi8r;LC_COLLATE=ru_RU.koi8r; LC_MONETARY=ru_UA;LC_MESSAGES=ru_RU.koi8r; LC_PAPER=ru_RU.koi8r;LC_NAME=ru_RU.koi8r; LC_ADDRESS=ru_RU.koi8r;LC_TELEPHONE=ru_RU.koi8r; LC_MEASUREMENT=ru_RU.koi8r; LC_IDENTIFICATION=ru_RU.koi8r LC_COLLATE: ru_RU.koi8r LC_CTYPE: ru_RU.koi8r LC_MESSAGES: ru_RU.koi8r LC_MONETARY: ru_UA LC_NUMERIC: ru_RU.koi8r LC_TIME: ru_RU.koi8r Некоторые элементы категории LC_MONETARY int_curr_symbol: UAH currency_symbol: ГР
Пример 13.12. Возможный результат работы программы, использующей функции setlocale() и localeconv().
Закрыть окно




#include <monetary.h> ssize_t strfmon (char * restrict s, size_t maxsize, const char *restrict format, ...);
Пример 13.13. Описание функции strfmon().
Закрыть окно




#include <stdio.h> #include <ctype.h> #include <locale.h> #include <limits.h> #include <monetary.h>
#define MY_VALUE 1234.567
int main (void) { char sbuf [LINE_MAX]; printf ("Текущая языково-культурная среда: подразумеваемая\n"); printf ("isalpha ('Б'): %d\n", isalpha ('Б')); printf ("tolower ('Б'): '%c'\n", tolower ('Б')); printf ("Результат преобразования в денежную величину числа 1234.567\n"); if (strfmon (sbuf, sizeof (sbuf), "Международное обозначение: %i\n", MY_VALUE) == -1) { perror ("STRFMON"); return (1); } fputs (sbuf, stdout); (void) strfmon (sbuf, sizeof (sbuf), "Местное обозначение: %n\n", MY_VALUE); fputs (sbuf, stdout); (void) setlocale (LC_ALL, ""); printf ("Текущая языково-культурная среда: местная\n"); printf ("isalpha ('Б'): %d\n", isalpha ('Б')); printf ("tolower ('Б'): '%c'\n", tolower ('Б')); printf ("Результат преобразования в денежную величину числа 1234.567\n"); (void) strfmon (sbuf, sizeof (sbuf), "Международное обозначение: %i\n", MY_VALUE); fputs (sbuf, stdout); (void) strfmon (sbuf, sizeof (sbuf), "Местное обозначение: %n\n", MY_VALUE); fputs (sbuf, stdout); (void) setlocale (LC_MONETARY, "ru_UA"); printf ("Категория LC_MONETARY переустановлена для Украины\n"); printf ("Результат преобразования в денежную величину числа 1234.567\n"); (void) strfmon (sbuf, sizeof (sbuf), "Международное обозначение: %i\n", MY_VALUE); fputs (sbuf, stdout); (void) strfmon (sbuf, sizeof (sbuf), "Местное обозначение: %n\n", MY_VALUE); fputs (sbuf, stdout); return 0; }
Пример 13.14. Пример программы, работающей в нескольких языково-культурных средах.
Закрыть окно




isalpha ('Б'): 0 tolower ('Б'): 'Б' Результат преобразования в денежную величину числа 1234.567 Международное обозначение: 1234.57 Местное обозначение: 1234.57 Текущая языково-культурная среда: местная isalpha ('Б'): 1024 tolower ('Б'): 'б' Результат преобразования в денежную величину числа 1234.567 Международное обозначение: 1 234.57 RUR Местное обозначение: 1 234. 57 руб Категория LC_MONETARY переустановлена для Украины Результат преобразования в денежную величину числа 1234.567 Международное обозначение: 1 234.57 UAH Местное обозначение: 1 234.57 ГР
Пример 13.15. Текущая языково-культурная среда: подразумеваемая
Закрыть окно




#include <langinfo.h> char *nl_langinfo (nl_item item);
Пример 13.16. Описание функции nl_langinfo().
Закрыть окно




#include <stdio.h> #include <locale.h> #include <langinfo.h> #include <regex.h> #include <limits.h>
int main (void) { regex_t cere; /* Скомпилированные расширенные */ /* регулярные выражения */ regex_t ceren; int reerrcode; /* Код ошибки от regcomp или */ /* regexec */ char reerrbuf [LINE_MAX]; /* Буфер для строк с */ /* сообщениями об ошибках */ char response [LINE_MAX]; /* Буфер для ответа */
/* пользователя */ printf ("Текущая языково-культурная среда: подразумеваемая\n"); printf ("Элемент YESEXPR категории LC_MESSAGES: %s\n", nl_langinfo (YESEXPR)); printf ("Элемент MON_1 категории LC_TIME: %s\n", nl_langinfo (MON_1)); (void) setlocale (LC_ALL, ""); printf ("Текущая языково-культурная среда: местная\n"); printf ("Элемент YESEXPR категории LC_MESSAGES: %s\n", nl_langinfo (YESEXPR)); printf ("Элемент MON_1 категории LC_TIME: %s\n", nl_langinfo (MON_1)); /* Скомпилируем расширенное регулярное */ /* выражение для утвердительного ответа */ if ((reerrcode = regcomp (&cere, nl_langinfo (YESEXPR), REG_EXTENDED | REG_ICASE | REG_NOSUB )) != 0) { (void) regerror (reerrcode, &cere, reerrbuf, sizeof (reerrbuf)); fputs (reerrbuf, stderr); fputc ('\n', stderr); regfree (&cere); return (reerrcode); } /* То же для отрицательного ответа */ if ((reerrcode = regcomp (&ceren, nl_langinfo (NOEXPR), REG_EXTENDED | REG_ICASE | REG_NOSUB )) != 0) { (void) regerror (reerrcode, &ceren, reerrbuf, sizeof (reerrbuf)); fputs (reerrbuf, stderr); fputc ('\n', stderr); regfree (&ceren); return (reerrcode); } fputs ("Вы поддерживаете идею стандартизации программных интерфейсов? ", stdout); fgets (response, sizeof (response), stdin); if (regexec (&cere, response, 0, NULL, 0) == 0) { fputs ("Ответ положительный\n", stdout); } else if (regexec (&ceren, response, 0, NULL, 0) == 0) { fputs ("Ответ отрицательный\n", stdout); } else { fputs ("Ответ уклончивый\n", stdout); } regfree (&cere); regfree (&ceren); return 0; }
Пример 13.17. Пример программы, использующей функцию nl_langinfo().
Закрыть окно




Текущая языково- культурная среда: подразумеваемая Элемент YESEXPR категории LC_MESSAGES: ^[yY] Элемент MON_1 категории LC_TIME: January Текущая языково-культурная среда: местная Элемент YESEXPR категории LC_MESSAGES: ^[ДдYy].* Элемент MON_1 категории LC_TIME: Января Вы поддерживаете идею стандартизации программных интерфейсов? Да Ответ положительный
Пример 13.18. Возможные результаты работы программы, использующей функцию nl_langinfo().
Закрыть окно




#include <string.h> char *strerror (int errnum);
Пример 13.19. Описание функции strerror().
Закрыть окно




#include <stdio.h> #include <locale.h> #include <string.h> #include <errno.h>
int main (void) { fprintf (stderr, "Текущая языково-культурная среда: подразумеваемая\n"); errno = 1; perror ("PERROR, сообщение номер 1"); fprintf (stderr, "STRERROR, сообщение номер 1: %s\n", strerror (1)); (void) setlocale (LC_ALL, ""); fprintf (stderr, "Текущая языково-культурная среда: местная\n"); errno = 1; perror ("PERROR, сообщение номер 1"); fprintf (stderr, "STRERROR, сообщение номер 1: %s\n", strerror (1)); (void) setlocale (LC_MESSAGES, "ru_UA"); fprintf (stderr, " Категория LC_MESSAGES переустановлена для Украины\n"); errno = 1; perror ("PERROR, сообщение номер 1"); fprintf (stderr, "STRERROR, сообщение номер 1: %s\n", strerror (1)); return 0; }
Пример 13.20. Пример программы, выдающей диагностические сообщения в разных языково-культурных средах и разными средствами.
Закрыть окно




#include <nl_types.h> nl_catd catopen (const char *name, int oflag); char *catgets ( nl_catd catd, int set_id, int msg_id, const char *s); int catclose (nl_catd catd);
Пример 13.21. Описание функций catopen(), catgets() и catclose().
Закрыть окно



Создание и опрос характеристик языково-культурной среды


Для создания целевой языково-культурной среды служит утилита localedef:

localedef [-c] [-f файл_отображения_символов] [-i исходный_файл] [-u имя_набора_символов] имя_целевой_среды

Утилита localedef преобразует рассмотренное выше определение категорий языково-культурной среды из исходного формата в целевой, пригодный для использования функциями и служебными программами, поведение которых зависит от значений переменных окружения.

Исходные данные для localedef поступают из файла с именем операнда опции -i или со стандартного ввода.

Операнд имя_целевой_среды определяет имя создаваемой среды. Она может быть как общедоступной, так и приватной, с ограниченным доступом.

Аргументом опции -f является маршрутное имя файла отображения символов. После опции -u можно задать имя нестандартного набора символов.

Опция -c предписывает генерировать выходной файл даже при наличии предупреждений.

Аргумент имя_целевой_среды идентифицирует созданную языково-культурную среду. Если это имя начинается с символа /, оно интерпретируется как маршрутное имя, под которым сохраняется сгенерированное определение целевой среды (формат результатов работы localedef стандартом не специфицируется); в противном случае интерпретация имени зависит от реализации, а созданная среда становится общедоступной.

Для получения информации о языково-культурных средах предназначена служебная программа locale:

locale [-a | -m] locale [-ck] имя ...

Будучи вызванной без аргументов, утилита locale выдает на стандартный вывод сводную информацию о среде, которая состоит из значений соответствующих переменных окружения (описанных выше, а также дополнительных, определенных, быть может, целевой системой). Результат может выглядеть так, как показано в пример 13.6. Кавычки вокруг значений всех переменных окружения, кроме LANG, означают, что они не были установлены явным образом, а определены по значению $LANG.

LANG=ru_RU.koi8r LC_CTYPE="ru_RU.koi8r" LC_NUMERIC="ru_RU.koi8r" LC_TIME="ru_RU.koi8r" LC_COLLATE="ru_RU.koi8r" LC_MONETARY="ru_RU.koi8r" LC_MESSAGES="ru_RU.koi8r" LC_PAPER="ru_RU.koi8r" LC_NAME="ru_RU.koi8r" LC_ADDRESS="ru_RU.koi8r" LC_TELEPHONE="ru_RU.koi8r" LC_MEASUREMENT="ru_RU.koi8r" LC_IDENTIFICATION="ru_RU.koi8r" LC_ALL=


Пример 13.6. Возможный результат работы служебной программы locale. (html, txt)

При наличии аргументов выдается информация о некоторых (если задано ключевое слово) или обо всех (если указано имя категории) элементах категорий.

Опциям служебной программы locale приписан следующий смысл.

-a

Выдать информацию обо всех общедоступных языково-культурных средах (включая POSIX-среду).

-m

Выдать имена доступных файлов отображения символов.

-c

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

-k

Выдавать имена и значения заданных элементов категорий.

Операнд имя может быть именем категории, элемента категории (ключевым словом) или зарезервированным словом charmap. В двух первых случаях выдается информация о поименованной категории или ее элементе, в последнем – имя файла отображения символов, который был использован при создании языково-культурной среды.

Приведем еще два примера употребления утилиты locale. Данные о категории LC_TIME, выдаваемые по команде

locale -ck LC_TIME

могут выглядеть так, как показано в пример 13.7.

LC_TIME abday="Вск;Пнд;Втр;Срд;Чтв;Птн;Сбт" day="Воскресенье;Понедельник;Вторник;Среда; Четверг;Пятница;Суббота" abmon="Янв;Фев;Мар;Апр;Май;Июн;Июл;Авг;Сен; Окт;Ноя;Дек" mon="Января;Февраля;Марта;Апреля;Мая;Июня; Июля;Августа;Сентября;Октября;Ноября;Декабря" d_t_fmt="%a %d %b %Y %T" d_fmt="%d.%m.%Y" t_fmt="%T" . . . first_weekday=1 first_workday=1 cal_direction=1 date_fmt="%a %b %e %H:%M:%S %Z %Y" time-codeset="KOI8-R"

Пример 13.7. Фрагмент возможного результата выполнения команды locale -ck LC_TIME. (html, txt)

Второй пример демонстрирует фрагмент интернационализированного варианта shell-процедуры, анализирующей интерактивный ответ пользователя. Предполагается, что ответ является значением переменной response.

if echo "$response" | grep -Eq "$(locale yesexpr)" then echo "Ответ положительный" else echo "Ответ отрицательный" fi

Пример 13.8. Фрагмент интернационализированного варианта shell-процедуры. (html, txt)



Пример 13.8. Фрагмент интернационализированного варианта shell-процедуры.

Программная установка и опрос характеристик языково-культурной среды

Мы приступаем к рассмотрению функций, предназначенных для работы с языково-культурной средой.

Функция setlocale() (см. пример 13.9) служит для установки и/или опроса всей языково-культурной среды вызывающего процесса или отдельных категорий.

#include <locale.h> char *setlocale (int category, const char *locale);

Пример 13.9. Описание функции setlocale().

Аргумент category задает категорию (LC_COLLATE, LC_CTYPE, LC_MESSAGES, LC_MONETARY, LC_NUMERIC, LC_TIME), а если его значение равно LC_ALL, то и всю среду.

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

"POSIX" или "C"

Специфицирует минимальную языково-культурную среду (POSIX-среду) для C-программ. При отсутствии явных обращений к функции setlocale() такая среда устанавливается по умолчанию при входе в main().

""

Специфицирует зависящую от реализации местную языково-культурную среду, определяемую значениями переменных окружения LC_* и LANG. Интернационализированная программа должна выполнять вызов вида

setlocale (LC_ALL, "");

для настройки на местную среду выполнения.

Если значением аргумента locale служит пустой указатель, функция setlocale() опрашивает текущее окружение и возвращает имя используемой языково-культурной среды или цепочку символов, ассоциированную с выбранной категорией. В противном случае в качестве результата возвращаются те же характеристики, но для новой среды. Результирующая цепочка (скопированная в отведенную прикладной программой область памяти) в сочетании с заданной категорией может быть использована в последующих вызовах функции setlocale() для восстановления соответствующей части языково-культурной среды.

Разумеется, в случае неудачного завершения вызова setlocale() результатом служит пустой указатель.



Пример 13.11. Пример использования функций setlocale() и localeconv().

Возможный результат работы этой программы показан в пример 13.12.

Текущая языково-культурная среда: подразумеваемая Цепочки символов, ассоциированные в текущей среде с LC_ALL: C LC_COLLATE: C LC_CTYPE: C LC_MESSAGES: C LC_MONETARY: C LC_NUMERIC: C LC_TIME: C Текущая языково-культурная среда: местная Цепочки символов, ассоциированные в текущей среде с LC_ALL: ru_RU.koi8r LC_COLLATE: ru_RU.koi8r LC_CTYPE: ru_RU.koi8r LC_MESSAGES: ru_RU.koi8r LC_MONETARY: ru_RU.koi8r LC_NUMERIC: ru_RU.koi8r LC_TIME: ru_RU.koi8r Некоторые элементы категорий LC_MONETARY и LC_NUMERIC int_curr_symbol: RUR currency_symbol: РУБ mon_decimal_point: . decimal_point: , thousands_sep: . Категория LC_MONETARY переустановлена для Украины Цепочки символов, ассоциированные в текущей среде с LC_ALL: LC_CTYPE=ru_RU.koi8r;LC_NUMERIC=ru_RU.koi8r; LC_TIME=ru_RU.koi8r;LC_COLLATE=ru_RU.koi8r; LC_MONETARY=ru_UA;LC_MESSAGES=ru_RU.koi8r; LC_PAPER=ru_RU.koi8r;LC_NAME=ru_RU.koi8r; LC_ADDRESS=ru_RU.koi8r;LC_TELEPHONE=ru_RU.koi8r; LC_MEASUREMENT=ru_RU.koi8r; LC_IDENTIFICATION=ru_RU.koi8r LC_COLLATE: ru_RU.koi8r LC_CTYPE: ru_RU.koi8r LC_MESSAGES: ru_RU.koi8r LC_MONETARY: ru_UA LC_NUMERIC: ru_RU.koi8r LC_TIME: ru_RU.koi8r Некоторые элементы категории LC_MONETARY int_curr_symbol: UAH currency_symbol: ГР

Пример 13.12. Возможный результат работы программы, использующей функции setlocale() и localeconv().

Для преобразования денежных величин в цепочку символов в соответствии с настройками текущей языково-культурной среды можно воспользоваться функцией strfmon() (см. пример 13.13), входящей в XSI-расширение стандарта POSIX-2001.

#include <monetary.h> ssize_t strfmon (char *restrict s, size_t maxsize, const char *restrict format, ...);

Пример 13.13. Описание функции strfmon().

Результат преобразования (длиной не более maxsize байт) помещается в буфер, на который указывает аргумент s. Само преобразование выполняется под управлением аргумента format.


Пример 13.14. Пример программы, работающей в нескольких языково-культурных средах.

Текущая языково-культурная среда: подразумеваемая

isalpha ('Б'): 0 tolower ('Б'): 'Б' Результат преобразования в денежную величину числа 1234.567 Международное обозначение: 1234.57 Местное обозначение: 1234.57 Текущая языково-культурная среда: местная isalpha ('Б'): 1024 tolower ('Б'): 'б' Результат преобразования в денежную величину числа 1234.567 Международное обозначение: 1 234.57 RUR Местное обозначение: 1 234. 57 руб Категория LC_MONETARY переустановлена для Украины Результат преобразования в денежную величину числа 1234.567 Международное обозначение: 1 234.57 UAH Местное обозначение: 1 234.57 ГР

Пример 13.15. Текущая языково-культурная среда: подразумеваемая

Если требуется получить детальную информацию обо всех аспектах языково-культурной среды, можно воспользоваться функцией nl_langinfo() (см. пример 13.16), отнесенной стандартом POSIX-2001 к расширению XSI.

#include <langinfo.h> char *nl_langinfo (nl_item item);

Пример 13.16. Описание функции nl_langinfo().

Аргументом функции nl_langinfo() могут служить именованные константы, определенные в заголовочном файле <langinfo.h>. В общем и целом они соответствуют описанным выше ключевым словам. В качестве результата nl_langinfo() возвращает указатель на цепочку символов, содержащую данные о выбранном элементе среды. Если в текущей среде этот элемент отсутствует, выдаются данные для POSIX-среды.

Пример программы, использующей функцию nl_langinfo(), показан в пример 13.17, возможные результаты ее работы – в пример 13.18.

#include <stdio.h> #include <locale.h> #include <langinfo.h> #include <regex.h> #include <limits.h>

int main (void) { regex_t cere; /* Скомпилированные расширенные */ /* регулярные выражения */ regex_t ceren; int reerrcode; /* Код ошибки от regcomp или */ /* regexec */ char reerrbuf [LINE_MAX]; /* Буфер для строк с */ /* сообщениями об ошибках */ char response [LINE_MAX]; /* Буфер для ответа */



Пример 13.18. Возможные результаты работы программы, использующей функцию nl_langinfo().

К сожалению, обязательная часть стандарта POSIX-2001 содержит лишь минимум средств для настройки на целевую среду выдаваемых диагностических сообщений. По сути он сводится к функции strerror() (см. пример 13.19), которая отображает номера (коды) ошибок в сообщения, зависящие от языково-культурной среды, точнее, от установки категории LC_MESSAGES.

#include <string.h> char *strerror (int errnum);

Пример 13.19. Описание функции strerror().

Обычно в качестве аргумента этой функции используют errno, но номера ошибок могут поступать и из произвольного источника.

В пример 13.20 показан пример программы, выдающей диагностические сообщения в разных средах и разными средствами – с помощью функций perror() и strerror().

#include <stdio.h> #include <locale.h> #include <string.h> #include <errno.h>

int main (void) { fprintf (stderr, "Текущая языково-культурная среда: подразумеваемая\n"); errno = 1; perror ("PERROR, сообщение номер 1"); fprintf (stderr, "STRERROR, сообщение номер 1: %s\n", strerror (1)); (void) setlocale (LC_ALL, ""); fprintf (stderr, "Текущая языково-культурная среда: местная\n"); errno = 1; perror ("PERROR, сообщение номер 1"); fprintf (stderr, "STRERROR, сообщение номер 1: %s\n", strerror (1)); (void) setlocale (LC_MESSAGES, "ru_UA"); fprintf (stderr, "Категория LC_MESSAGES переустановлена для Украины\n"); errno = 1; perror ("PERROR, сообщение номер 1"); fprintf (stderr, "STRERROR, сообщение номер 1: %s\n", strerror (1)); return 0; }

Пример 13.20. Пример программы, выдающей диагностические сообщения в разных языково-культурных средах и разными средствами.


Средства для работы с каталогами сообщений


Развитые средства для работы с каталогами сообщений (диагностических, информационных), выдаваемых приложениями, вынесены в расширение XSI стандарта POSIX-2001. Идея этих средств состоит в том, чтобы в интернационализированных программах фигурировали не сами сообщения, а их идентификаторы в каталоге, который для каждой языково-культурной среды может быть своим.

Стандарт не специфицирует формат каталогов сообщений, но предлагает служебную программу gencat для их генерации по исходному описанию:

gencat каталог файл_сообщений ...

Аргумент каталог задает маршрутное имя, под которым будет сохранен результат генерации.

Исходное описание может состоять из нескольких файлов сообщений, строки которых, помимо прочих, содержат следующие директивы.

$set идентификатор_набора_сообщений комментарий

Задает идентификатор набора для дальнейших сообщений. Идентификатором служит целое число в диапазоне [1, NL_SETMAX]. В пределах одного исходного файла сообщений идентификаторы должны задаваться в порядке возрастания. Подразумеваемым является набор NL_SETD (см. <nl_types.h>).

идентификатор_сообщения текст_сообщения

Идентификатором сообщения (в пределах набора) служит целое число в диапазоне [1, NL_MSGMAX]. В пределах одного набора идентификаторы сообщений задаются в порядке возрастания. Длина текста сообщения должна лежать в диапазоне [0, NL_TEXTMAX].

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

В прикладной программе работа с каталогами сообщений осуществляется посредством функций catopen(), catgets() и catclose() (см. пример 13.21).

#include <nl_types.h> nl_catd catopen (const char *name, int oflag); char *catgets (nl_catd catd, int set_id, int msg_id, const char *s); int catclose (nl_catd catd);

Пример 13.21. Описание функций catopen(), catgets() и catclose(). (html, txt)


Функция catopen() открывает каталог сообщений и возвращает его дескриптор. Когда цепочка символов, на которую указывает аргумент name, содержит /, она трактуется как полное маршрутное имя. В противном случае (если пренебречь деталями) используется маршрут, зависящий от имени и реализации, а также от значения переменной окружения LANG (если аргумент oflag равен нулю) или LC_MESSAGES (если oflag равен NL_CAT_LOCALE).

К сожалению, рекомендации по расположению каталогов сообщений на момент написания данного текста еще не были выработаны. Стандарт POSIX-2001 ограничился замечанием о том, что авторам приложений следует соблюдать осторожность, дабы избежать конфликтов с другими приложениями и стандартными служебными программами.

Дескриптор, выданный функцией catopen(), используется в вызове catgets() для чтения сообщения под номером msg_id из набора с номером set_id. Аргумент s указывает на подразумеваемую цепочку символов, которая будет возвращена catgets() в качестве результата, если запрошенное сообщение не удастся прочитать из каталога.

Функция catclose() закрывает каталог сообщений.


Языково-культурная среда


Согласно стандарту POSIX-2001, языково-культурная среда - это часть пользовательского окружения, зависящая от языковых и культурных соглашений.

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

языково-культурная среда формируется из данных нескольких именованных категорий. Каждая управляет определенными аспектами поведения компонентов системы. Имена и назначение категорий соответствуют следующим переменным окружения: LC_CTYPE (классификация символов, преобразование регистра), LC_COLLATE (порядок алфавитного сравнения символов), LC_MONETARY (форматирование денежных величин), LC_NUMERIC (форматирование числовых величин), LC_TIME (форматы даты и времени), LC_MESSAGES (форматы сообщений и интерактивных ответов).

Категории подразделяются на более мелкие элементы , средством именования которых служат ключевые слова.

В каждой реализации определены одна или несколько языково-культурных сред. Поддержка POSIX-среды с именами-синонимами "POSIX"и "C" является обязательной.

Стандартом POSIX-2001 предусмотрены две переменные окружения, определяющие все категории языково-культурной среды: LC_ALL (значение этой переменной учитывается в первую очередь), lang (ее значение учитывается в последнюю очередь).

Общая логика использования средств интернационализации/локализации состоит в следующем. пользователь, присваивая соответствующие значения описанным выше переменным окружения, идентифицирует свою языково-культурную среду. Приложения могут опросить данные о целевой среде с помощью функции setlocale() и произвести настройку интернационализированных компонентов.

При создании новой языково-культурной среды ее определение в исходном формате помещается в файлы, которые должны быть обработаны утилитой localedef.


Для получения информации о языково- культурных средах предназначена служебная программа locale.

Функция setlocale() служит для установки и/или опроса всей языково-культурной среды вызывающего процесса или отдельных категорий.

Получить детальную информацию о категориях LC_MONETARY и LC_NUMERIC текущей языково-культурной среды поможет функция localeconv().

Для преобразования денежных величин в цепочку символов в соответствии с настройками текущей языково-культурной среды можно воспользоваться функцией strfmon(), входящей в XSI-расширение стандарта POSIX-2001.

Если требуется получить детальную информацию обо всех аспектах языково-культурной среды , следует прибегнуть к функции NL_LANGINFO(), также отнесенной стандартом POSIX-2001 к расширению XSI.

Функция strerror() отображает номера (коды) ошибок в сообщения, зависящие от языково-культурной среды .

Развитые средства для работы с каталогами сообщений (диагностических, информационных), выдаваемых приложениями, вынесены в расширение XSI стандарта POSIX-2001. Идея состоит в том, чтобы в интернационализированных программах фигурировали не сами сообщения, а их идентификаторы в каталоге, который для каждой языково-культурной среды может быть своим.

Стандарт не специфицирует формат каталогов сообщений, но предлагает служебную программу gencat для их генерации по исходному описанию.

В прикладной программе работа с каталогами сообщений осуществляется посредством функций catopen(), catgets() и catclose().

Функция catopen() открывает каталог сообщений и возвращает его дескриптор, который затем используется в вызове catgets() для чтения сообщения.

Функция catclose() закрывает каталог сообщений.

На этом мы завершаем описание базовых средств программирования приложений в стандарте POSIX.


Общий терминальный интерфейс


В стандарте POSIX-2001 терминал или терминальное устройство определяется как символьный специальный файл , удовлетворяющий спецификациям общего терминального интерфейса.

С каждым терминальным устройством ассоциированы очереди ввода и вывода.

Ввод может происходить в каноническом и неканоническом режимах. Канонический режим означает построчную буферизацию ввода системой, а также естественную обработку символов забоя и уничтожения строки.

В неканоническом режиме входные данные не подвергаются препроцессированию системой, а обработка запроса на чтение зависит от двух параметров - min и time.

Некоторые символы играют специальную роль при вводе и/или выводе. Например, символ intr генерирует сигнал прерывания (sigint), посылаемый всем процессам, для которых данный терминал является управляющим.

Служебная программа tty выдает на стандартный вывод имя терминала, открытого в качестве стандартного ввода.

Узнать, ассоциирован ли открытый файловый дескриптор с терминальным устройством, а также получить имя этого устройства можно с помощью функций isatty() и ttyname().

Каждый терминал обладает рядом характеристик, которые можно опросить и/или изменить, используя утилиту stty.

Некоторые, хотя и весьма ограниченные, возможности управления терминалами предоставляет служебная программа tput.

На уровне функций опрос и изменение характеристик терминала разбиты на два семейства: tc*() и cf*(). В первое входят функции tcgetattr() - опрос, tcsetattr() - изменение, tcflow() - приостановка или возобновление терминального ввода/вывода, tcflush() - сброс очереди ввода, tcdrain() - ожидание физического окончания вывода, tcsendbreak() - разрыв соединения, tcgetpgrp() - получение идентификатора ассоциированной с терминалом группы процессов переднего плана, tcsetpgrp() - установка идентификатора группы, tcgetsid() - опрос идентификатора группы процессов лидера сеанса, для которого терминал является управляющим.

Функции семейства cf*() - cfgetispeed(), cfgetospeed(), cfsetispeed(), cfsetospeed() - служат для выборки/изменения данных о скорости терминального ввода/вывода.

Для получения маршрутного имени управляющего терминала служит функция ctermid().



Опрос характеристик хостов


Самую общую информацию о характеристиках хоста позволяют получить служебная программа uname и одноименная функция.

Функция gethostname() возвращает имя хоста.

Значения конфигурационных параметров - важнейшая характеристика хоста, а настройка на конфигурацию целевой системы - обязательный элемент мобильного программирования приложений.

Основная часть статической конфигурационной информации сосредоточена в заголовочном файле <unistd.h>. К числу наиболее важных характеристик принадлежат _POSIX_VERSION (поддерживаемая версия системного интерфейса для языка c стандарта POSIX) и _POSIX2_VERSION (поддерживаемая версия интерфейса к системным сервисам на уровне командного языка и служебных программ).

Отдельная группа констант описывает поддерживаемые необязательные возможности стандарта POSIX-2001. Среди них _POSIX_IPV6 (реализация поддерживает IPv6), _POSIX_REGEXP (реализация поддерживает обработку регулярных выражений), _POSIX_SHELL (реализация поддерживает стандартный командный интерпретатор), _POSIX_V6_ILP32_OFF32 (реализация предоставляет среду компиляции C-программ с 32-битными типами int, long, off_t и такими же указателями) и т.д.

Три константы задают номера файловых дескрипторов для стандартных ввода (STDIN_FILENO со значением 0), вывода (STDOUT_FILENO - 1) и протокола (STDERR_FILENO - 2).

Разного рода лимиты на количество и размеры заданы в заголовочном файле <limits.h>. Стандарт определяет минимально допустимые значения для подобных лимитов, которые должны поддерживаться реализациями и предоставляться приложениям.

Мобильным приложениям не следует полагаться на какие-то конкретные значения лимитов и требовать больше ресурсов, чем предусмотрено минимально допустимыми значениями.

Для опроса значений системных параметров во время выполнения предназначены служебная программа getconf, а также функции sysconf(), confstr(), fpathconf() и pathconf(), первая из которых опрашивает лимитирующие конфигурационные параметры, имеющие числовые значения, вторая возвращает конфигурационные цепочки символов, а две последние - конфигурационные значения, относящиеся к файлам.



Основные идеи курса


Один из общепринятых способов обеспечения мобильности ПО - стандартизация окружения приложений: предоставляемых программных интерфейсов, утилит и т.п. На уровне системных сервисов подобное окружение описывает стандарт POSIX. Стандарт POSIX-2001 включает в себя четыре части:

основные определения (общие для всех частей термины, концепции и интерфейсы);описание прикладного программного c-интерфейса к системным сервисам;описание интерфейса к системным сервисам на уровне командного языка и служебных программ;детальное разъяснение положений стандарта, обоснование принятых решений.

Стандарт POSIX описывает множество базовых, системных сервисов, необходимых для функционирования прикладных программ. Доступ к ним предоставляется посредством интерфейса, специфицированного для языка C, командного языка и общеупотребительных служебных программ.

Основная цель стандарта POSIX - сделать приложения мобильными на уровне исходного языка по отношению к смене аппаратно-программной платформы. О мобильности выполнимых программ и/или объектных файлов речь не идет.

POSIX нейтрален по отношению к системной архитектуре и разрядности процессора. Это очень важный аспект мобильности приложений.

Стандарт POSIX не ограничен рамками Unix-среды. Существуют и другие операционные системы (например, системы реального времени), предоставляющие необходимые сервисы и тем самым поддерживающие выполнение POSIX-совместимых приложений. Можно утверждать, что следование стандарту POSIX облегчает перенос приложений практически на любую сколько-нибудь распространенную операционную платформу.

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


Основная задача курса - рассмотрение приемов и методов использования стандартизованных служебных программ и функций. Не ставилась цель пересказать стандарт, осветив все тонкости реализации ОС, все возможные коды ошибок и т.п. Главное, на наш взгляд, прочувствовать дух стандарта, научиться мобильным образом применять заложенные в нем возможности.

Предполагается, что читатель владеет языком C; мы не рассматривали ни синтаксис, ни хрестоматийные библиотечные функции этого языка. По отношению к стандартному командному языку и его интерпретатору аналогичного предположения не делалось. Эта тема изложена довольно подробно, хотя многие практикующие программисты предпочитают пользоваться другими интерпретаторами.

Мы не противопоставляли программирование на уровне командного языка и языка С. И то, и другое, порознь или совместно, может служить адекватным инструментом реализации приложений. Соответственно, рассматриваемые интерфейсы группировались по их предметной направленности, а не по языку реализации.

Значительное место в курсе и по объему, и по роли отведено примерам программ. Многие положения стандарта (связанные, скажем, с обработкой ошибочных ситуаций) излагаются не в основном тексте, а в этих примерах. По возможности они компилировались и выполнялись на нескольких аппаратно-программных платформах, в той или иной степени претендующих на соответствие стандарту POSIX. Тем не менее, недосмотры, конечно, возможны. Мы будем признательны за все замечания и предложения, относящиеся как к курсу в целом, так и к отдельным примерам программ.


Основные понятия, рассматриваемые в курсе


Важнейшим является понятие соответствия стандарту POSIX. У него есть две стороны: соответствие реализации (операционной системы) и соответствие приложения.

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

Для приложений предусмотрено как строгое соответствие (его главный отличительный признак - ограничение круга используемых возможностей рамками стандарта), так и соответствие с использованием расширений.

Профилем называется набор опций, описывающих необязательные возможности. Соответствие профилю означает соответствие стандарту POSIX и поддержку заданных возможностей. Разумным образом выбранные профили позволяют учитывать потребности представительных классов пользователей и/или приложений.

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

К числу важнейших принадлежат понятия, описывающие поведение реализации в различных ситуациях. Для многих корректных ситуаций поведение может быть неспецифицированным; это значит, что мобильное приложение не должно полагаться на совпадение поведения разных реализаций. Для некорректных ситуаций возможно неопределенное поведение; приложение не только не должно полагаться на определенный характер подобного поведения - оно не должно совершать некорректных действий, вызывающих неопределенное поведение.

Еще один близкий термин - "поведение, зависящее от реализации" - дополнительно означает, что поведение реализации необходимо документировать.

Стандарт POSIX в редакции от 2003 года - весьма обширный, многогранный документ. Для настоящего курса были отобраны следующие основные понятия операционных систем:

пользователь;

файл;

процесс;

терминал;

хост;

узел сети;



Процессы


Согласно стандарту POSIX-2001, процесс - это адресное пространство вместе с выполняемыми в нем потоками управления, а также системными ресурсами , которые этим потокам требуются.

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

Процесс, создавший данный, называется родительским.

С каждым процессом ассоциируется идентификатор создавшего его пользователя. Этот атрибут называется реальным идентификатором пользователя процесса.

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

Поведение процесса определяется исполняемой в его рамках программой.

Для выдачи информации о процессах служит утилита ps.

Опрос идентификаторов процесса, родительского процесса и группы процессов выполняется посредством функций getpid() и getppid() getpgrp().

Для установки идентификатора группы процессов предназначена функция setpgid().

За создание сеанса и установку идентификатора группы процессов отвечает функция setsid().

Опрос реальных и действующих идентификаторов пользователя и группы вызывающего процесса осуществляется с помощью функций getuid(), geteuid(), getgid(), getegid().

Функция getgroups() предназначена для получения идентификаторов дополнительных групп вызывающего процесса.

Переустановить действующий идентификатор пользователя вызывающего процесса позволяют функции setuid() и seteuid(). Аналогичные функции для переустановки идентификаторов группы процесса называются setgid() и setegid().

Опрос и/или изменение маски режима создания файлов вызывающего процесса осуществляет служебная программа umask и одноименная функция.

Новые процессы создаются при помощи функции fork().

Обычно процесс-потомок, используя функцию семейства exec(), подменяет программу, которая определяет поведение процесса, и передает ей управление и список аргументов. К числу функций этого семейства принадлежат execl(), execv(), execle(), execve(), execlp(), execvp().

Родительский процесс реализует ожидание завершения процессов-потомков и получает информацию о статусе завершения от функций семейства wait() - wait() и waitpid().

Процесс может вызвать собственное завершение, обратившись к функциям семейства exit() - exit(), _Exit(), _exit().

Функция atexit() позволяет зарегистрировать функции, которые будут вызываться, если процесс завершается, обращаясь к exit() или возвращаясь из main().

Для терминирования процессов извне предназначена служебная программа kill.



Сетевые средства


Стандарт POSIX-2001 определяет сеть как совокупность взаимосвязанных хостов. Под сетевым адресом понимается видимый в пределах сети идентификатор, используемый для обозначения оконечных точек сети.

Процесс присвоения сетевого адреса оконечной точке называется связыванием, или привязкой, а обратное действие - освобождением , или отменой привязки.

Обычно оконечной точкой служит аппаратный сетевой интерфейс, посредством которого данные передаются и принимаются, однако с таким интерфейсом, как шлейфовый (loopback), никакой аппаратуры не ассоциировано.

Данные передаются по сети в виде последовательности октетов (восьмибитных беззнаковых величин). Если некоторый элемент данных (например, адрес или номер порта) состоит более чем из восьми бит, для его передачи и хранения требуется несколько октетов. Сетевым называется порядок октетов (байт), при котором первый (с наименьшим адресом) октет содержит старшие (наиболее значимые) биты.

При взаимодействии процессов оконечными точками служат сокеты, стандарт POSIX-2001 трактует их как отдельный тип файлов.

Под адресом сокета как (удаленной) оконечной точки понимается структура, включающая идентификатор адресного семейства и специфичную для данного семейства адресную информацию.

Адресное семейство соответствует определенной среде взаимодействия. Стандарт POSIX-2001 определяет три таких семейства: AF_UNIX (межпроцессное взаимодействие в пределах одной системы), AF_INET (взаимодействие по протоколам IPv4), AF_INET6 (взаимодействие по протоколам IPv6 - необязательная возможность).

В пределах каждого адресного семейства могут существовать сокеты нескольких типов. Стандартом POSIX-2001 предусмотрено четыре типа: SOCK_STREAM (надежные, упорядоченные, полнодуплексные потоки октетов в режиме с установлением соединения), SOCK_SEQPACKET (аналог SOCK_STREAM с дополнительным сохранением границ между записями), SOCK_DGRAM (передача данных в виде датаграмм в режиме без установления соединения), SOCK_RAW (аналог SOCK_DGRAM с дополнительной возможностью доступа к протокольным заголовкам и другой информации нижнего уровня - необязательная возможность).


Для каждого адресного семейства каждый тип сокета может поддерживаться одним или несколькими протоколами. В частности, в адресном семействе AF_INET для сокетов типа SOCK_STREAM подразумеваемым является протокол с именем IPPROTO_TCP, а для типа SOCK_DGRAM - IPPROTO_UDP.

Общая логика работы с сокетами состоит в следующем. Сокеты создаются с помощью функции socket(), которой в качестве аргументов передаются адресное семейство, тип сокета и протокол, а в качестве результата получают открытый файловый дескриптор. Затем, посредством функции bind(), сокету присваивают локальный адрес. Если сокет ориентирован на режим с установлением соединения, то его следует пометить как готового принимать соединения, для чего понадобится функция listen(). Реальный прием соединений выполняет функция accept(), создающая для каждого из них новый сокет по образу и подобию "слушающего". В свою очередь, потенциальный партнер по взаимодействию инициирует соединение, прибегнув к функции connect(). (в режиме без установления соединения функция connect() позволяет специфицировать адрес отправляемых через сокет датаграмм.)

Для приема данных, поступивших в сокет, можно воспользоваться универсальной функцией низкоуровневого ввода/вывода read() или специализированным семейством функций recv*(), а для передачи - функцией write() или семейством send*(). Кроме того, посредством функций select() и/или poll() можно опросить наличие данных для приема или возможность отправки очередной порции данных.

Завершается взаимодействие между партнерами обращением к функции shutdown().

Данные о хостах как узлах сети хранятся в сетевой базе, последовательный доступ к которой обслуживается функциями sethostent(), gethostent() и endhostent().

Функция getaddrinfo() позволяет по имени узла сети (хоста) и/или имени сетевого сервиса получить набор адресов сокетов и ассоциированную информацию, что дает возможность создать сокет для обращения к заданному сервису.

Функция freeaddrinfo() носит технический характер: благодаря ей можно освободить память, зарезервированную функцией getaddrinfo().



Функцию getnameinfo() можно считать обратной по отношению к getaddrinfo(). Она позволяет по адресу сокета узнать имя узла и сервиса.

Техническую роль играют и функции преобразования ip-адресов из текстового представления в числовое и наоборот: inet_addr(), inet_ntoa(), inet_pton(), inet_ntop().

Первые две манипулируют только адресами IPv4: inet_addr() преобразует текстовую цепочку в целочисленное значение, пригодное для использования в качестве ip-адреса, inet_ntoa() выполняет обратное преобразование.

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

Для преобразования значений типов uint16_t и uint32_t из хостового порядка байт в сетевой служат функции htons() и htonl(); функции ntohs() и ntohl() осуществляют обратную операцию.

Полезным дополнением к функциям getaddrinfo() и getnameinfo() является функция gai_strerror(), возвращающая текстовую цепочку, которая расшифровывает коды ошибок, перечисленные в заголовочном файле <netdb.h>.

Наряду с базой данных хостов (узлов сети), поддерживается база данных сетей с аналогичной логикой работы и набором функций: setnetent(), getnetent(), getnetbyaddr(), getnetbyname(), endnetent().

Функция getnetent() обслуживает последовательный доступ к базе, getnetbyaddr() осуществляет поиск по адресному семейству и номеру сети, а getnetbyname() выбирает сеть с заданным (официальным) именем.

Точно такой же программный интерфейс предоставляет база данных сетевых протоколов: setprotoent(), getprotoent(), getprotobyname(), getprotobynumber(), endprotoent().

Еще одно проявление той же логики работы - база данных сетевых сервисов: setservent(), getservent(), getservbyname(), getservbyport(), endservent().

Для создания сокетов, помимо функции socket(), может быть использована функция socketpair(), создающая пару сокетов с установленным между ними соединением. Она обычно используется для адресного семейства AF_UNIX; поддержка других семейств не гарантируется.

Опросить присвоенный локальный адрес (его иногда называют именем сокета) можно с помощью функции getsockname().

С сокетами могут быть ассоциированы опции, влияющие на их функционирование. Опросить или изменить значения этих опций помогут функций getsockopt() и setsockopt().

Функция getpeername() позволяет опросить еще одну характеристику - адрес (имя) сокета, с которым установлено соединение.

После привязки сокета к локальному адресу и, возможно, установления соединения и задания значений опций, следует приступать к отправке и/или приему данных через сокет. Для этого служат функции recvfrom(), recv(), recvmsg(), sendto(), send(), sendmsg().


Средства межпроцессного взаимодействия


Средства локального межпроцессного взаимодействия реализуют высокопроизводительную, детерминированную передачу данных между процессами в пределах одной системы.

К числу наиболее простых и в то же время самых употребительных средств межпроцессного взаимодействия принадлежат каналы, представляемые файлами соответствующего типа. Стандарт POSIX-2001 различает именованные и безымянные каналы.

Взаимодействие между процессами через канал может быть установлено следующим образом: один из процессов с помощью функций popen() или pipe() создает канал и передает другому соответствующий открытый файловый дескриптор. После этого процессы обмениваются данными через канал посредством функций read() и write().

По сравнению с pipe() функция popen() является более высокоуровневой. Она делает сразу несколько вещей: порождает процесс, обеспечивает выполнение заданной команды в его рамках, организует канал между вызывающим и порожденным процессами и формирует необходимые потоки для этого канала.

Канал остается открытым до тех пор, пока не будет вызвана функция pclose().

Согласно стандарту POSIX-2001, под сигналом понимается механизм, с помощью которого процесс или поток управления уведомляют о некотором событии, произошедшем в системе, или подвергаются воздействию этого события.

В каждом процессе определены действия, предпринимаемые в ответ на все предусмотренные системой сигналы.

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

С сигналом могут быть ассоциированы действия одного из трех типов.

SIG_DFL

Подразумеваемые действия, зависящие от сигнала. Они описаны в заголовочном файле <signal.h>.

SIG_IGN

Игнорировать сигнал. Доставка сигнала не оказывает воздействия на процесс .

Указатель на функцию

Обработать сигнал, выполнив при его доставке заданную функцию. После завершения функции обработки процесс возобновляет выполнение с точки прерывания.

К средствам генерации сигналов относятся служебная программа и одноименная функция.

Процесс (поток управления) может послать сигнал самому себе с помощью функции raise().


Функция abort() вызывает аварийное завершение процесса.

Опросить и изменить способ обработки сигналов позволяет функция sigaction().

Опросить и изменить способ обработки сигналов можно и на уровне командного интерпретатора, посредством специальной встроенной команды trap.

К техническим аспектам можно отнести работу с наборами сигналов. Ее выполняют инициализирующие набор функции sigemptyset() и sigfillset(), добавляющая сигнал signo к набору set функция sigaddset(),удаляющая сигнал функция sigdelset() и проверяющая вхождение в набор функция sigismember().

Функция sigprocmask() предназначена для опроса и/или изменения маски сигналов процесса, определяющей набор блокируемых сигналов.

Функция sigpending() позволяет выяснить набор блокированных сигналов, ожидающих доставки вызывающему процессу (потоку управления). Дождаться появления подобного сигнала можно с помощью функции sigwait().

Функция pause() поможет дождаться доставки обрабатываемого или терминирующего процесс сигнала.

Функция sigsuspend() заменяет текущую маску сигналов вызывающего процесса, а затем переходит в состояние ожидания. Обычно парой функций sigprocmask() и sigsuspend() обрамляют критические интервалы.

Очереди сообщений, семафоры и разделяемые сегменты памяти отнесены к необязательной части стандарта POSIX-2001, именуемой "X/Open-расширение системного интерфейса"(XSI).

Каждую очередь сообщений, набор семафоров и разделяемый сегмент однозначно идентифицирует положительное целое число, которое возвращается в качестве результатов функций msgget(), semget() и shmget().

При получении идентификаторов средств межпроцессного взаимодействия используется еще одна сущность - ключ, для его генерации предназначена функция ftok().

Опросить статус присутствующих в данный момент в системе (активных) средств межпроцессного взаимодействия позволяет служебная программа ipcs.

Для удаления из системы активных средств межпроцессного взаимодействия предназначена служебная программа ipcrm.

Механизм очередей сообщений позволяет процессам взаимодействовать, обмениваясь данными, которые передаются между процессами дискретными порциями, называемыми сообщениями.


процессы выполняют над сообщениями две основные операции - прием и отправку.

Для работы с очередями сообщений в стандарте POSIX-2001 предусмотрены следующие функции: msgget() - получение идентификатора очереди сообщений, msgctl() - управление очередью сообщений, msgsnd() - отправка сообщения, msgrcv() - прием сообщения.

Согласно определению стандарта POSIX-2001, семафор - это минимальный примитив синхронизации, служащий основой для более сложных механизмов синхронизации, определенных в прикладной программе.

У семафора есть значение, которое представляется целым числом в диапазоне от 0 до 32767.

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

Процессы, обладающие соответствующими правами, могут выполнять также различные управляющие действия над семафорами. Для этого служит функция semctl().

В стандарте POSIX-2001 разделяемый объект памяти определяется как объект, представляющий собой память, которая может быть параллельно отображена в адресное пространство более чем одного процесса.

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

Для получения доступа к разделяемому сегменту его нужно присоединить посредством функции shmat(), т.е. Разместить сегмент в виртуальном пространстве процесса. После присоединения, в соответствии с правами доступа, процессы могут читать данные из сегмента и записывать их (быть может, синхронизируя свои действия с помощью семафоров). Когда разделяемый сегмент становится ненужным, его следует отсоединить с помощью функции shmdt().

Предусмотрена возможность выполнения управляющих действий над разделяемыми сегментами (функция shmctl()).


Средства обработки структурированных данных


Простейшая разновидность структурированных данных - текстовые файлы. В свою очередь, простейшей операцией с файлами является их выдача на стандартный вывод. Для этого служит утилита cat.

Просмотр больших текстовых файлов предпочтительнее осуществлять при помощи служебной программы more.

Для просмотра нетекстовых файлов рекомендуется служебная программа od.

Полезная возможность - взглянуть на начало файла, она реализуется служебной программой head.

"Симметричная" служебная программа - tail - выдает на стандартный вывод конец исходного файла.

Служебная программа pr является фильтром для печати и оформления страниц.

Для подсчета числа символов, слов и строк в файлах служит утилита wc.

Служебная программа sort в зависимости от заданных опций выполняет одно из трех возможных действий:

сортирует строки всех исходных файлов с записью результата в выходной файл;производит слияние всех исходных (предварительно отсортированных) файлов и записывает результат в выходной файл;проверяет, действительно ли отсортирован единственный исходный файл.

Утилита diff сравнивает содержимое исходных файлов и выдает на стандартный вывод список изменений, которые необходимо произвести, чтобы преобразовать один файл в другой.

Если нужно проверить на совпадение два файла, предпочтительнее воспользоваться более простой и быстрой служебной программой cmp.

Еще одним средством выявления различий (и совпадений) текстовых файлов является служебная программа comm.

Для контроля целостности файлов предназначена служебная программа cksum.

Понятие регулярного выражения (РВ) - одно из важнейших для программ обработки текстовых файлов. Согласно стандарту POSIX-2001, регулярное выражение - это шаблон, служащий для выборки определенных цепочек символов из множества подобных цепочек. Говорят, что выбранные цепочки удовлетворяют (успешно сопоставляются с) РВ.

Различают базовые (БРВ) и расширенные (РРВ) регулярные выражения.

Наиболее употребительной служебной программой, использующей механизм регулярных выражений, является grep.



Средства, обслуживающие понятие файла


В трактовке стандарта POSIX понятие файла охватывает все, что может содержать, потреблять и/или поставлять информацию. Файл имеет такие атрибуты, как тип, имя и режим.

В стандарте зафиксированы следующие типы файлов:

обычный файл;

каталог;

канал;символьный специальный файл;блочный специальный файл;

символьная ссылка;

сокет.

Файлы вместе со служебной информацией, хранящейся в объектах, которые называются описателями файлов, объединяются в иерархическую структуру, именуемую файловой системой.

В пределах файловой системы каждый файл имеет уникальный идентификатор (порядковый номер - он же номер описателя файла).

Согласно стандарту, с каждым файлом ассоциирована по крайней мере следующая служебная информация:

режим - объект, содержащий биты режима и тип файла;числовой идентификатор владельца-пользователя;числовой идентификатор владеющей группы.

По отношению к конкретному файлу все пользователи делятся на три категории:

владелец файла;члены владеющей группы;прочие пользователи.

Для каждой из этих категорий режим доступа определяет права на операции с файлом, а именно:

право на чтение;

право на запись;

право на выполнение (для каталогов - право на поиск).

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

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

утилита pwd и функция getcwd() позволяют опросить абсолютное маршрутное имя текущего каталога;утилита ls и функции семейства stat() (stat(), fstat(), lstat()) выдают информацию о файлах;утилита df и функции fstatvfs() и statvfs() обеспечивают получение интегральной информации о файловых системах;

служебная программа du выдает информацию о суммарном объеме пространства, занятого иерархиями файлов;утилита cd и функция chdir() позволяют изменить текущий каталог;утилиты chown и chmod, функции chown(), fchown(), chmod(), fchmod() служат для изменения таких атрибутов файлов, как владелец и режим доступа;утилита touch модифицирует время последнего доступа и/или изменения файла.


Создание обычных файлов обслуживается функцией creat(), создание каталогов - утилитой mkdir и одноименной функцией, создание каналов - утилитой mkfifo и одноименной функцией.

Новые ссылки на файл (жесткие или символьные) создают служебная программа ln, а также функции link() и symlink().

Для удаления файлов служат утилиты rm и rmdir, функции unlink(), rmdir() и remove().

Копирование файлов выполняется служебной программой cp, перемещение - программой mv или функцией rename().

Обход файловой иерархии и систематическую обработку ее элементов осуществляет утилита find.

Одной из форм обхода и обработки файловой иерархии можно считать архивирование. Стандарт POSIX предусматривает для этого служебную программу pax.

В стандарте POSIX-2001 выделены две основные группы функций, обслуживающие операции ввода/вывода:



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

Для открытия файлов и формирования новых описаний открытых файлов, файловых дескрипторов и потоков служат функции нижнего уровня open() и pipe(), а также функции буферизованного ввода/вывода fopen(), fdopen(), freopen().

Для закрытия файлов предназначены функции close() и fclose().

Весьма полезной с практической точки зрения является функция создания и открытия временных файлов tmpfile().

Чтение данных из файла выполняют функции read() и fread(), запись - функции write() и fwrite().

Содержимое символьных ссылок приходится читать особым образом, посредством функции readlink().

Для буферизованного ввода/вывода байт предназначены функции fgetc() и fputc(), строки рекомендуется вводить, вызывая функцию fgets(), а выводить с помощью функций fputs() и puts().

Индикатор текущей позиции файла может быть опрошен или передвинут посредством функции нижнего уровня lseek(), а также функций буферизованного ввода/вывода fseek(), ftell(), ftello(), fgetpos(), fsetpos(), rewind().

Для выполнения разнообразных управляющих операций над открытым файлом ом предназначена функция fcntl().

Особый класс управляющих операций с файлами со своей системой понятий составляют блокировки, хотя они также оформляются как команды функции fcntl().

Функции setbuf(), setvbuf() и fflush() выполняют управляющие операции с буферами потоков.


Средства, обслуживающие понятие пользователя


Операционная система, соответствующая стандарту POSIX, должна поддерживать базу данных пользователей, в которой о каждом из них хранится по крайней мере следующая информация:

имя пользователя;числовой идентификатор пользователя;числовой идентификатор начальной группы;

начальный рабочий каталог;

начальная программа пользователя.

Над базой данных пользователей определены операции поиска по идентификатору или имени пользователя, реализуемые, соответственно, функциями getpwuid() и getpwnam().

Пользователи объединяются в группы; каждый является членом по крайней мере одной группы. Для групп, как и для пользователей, существует база данных, записи которой содержат по крайней мере следующие поля:

имя группы;числовой идентификатор группы;список пользователей, которым разрешено становиться членами данной группы.

Имеются функции для поиска в базе данных групп - getgrgid() и getgrnam().

Опросить ассоциированные с пользователем данные позволяет служебная программа id.

Входное имя текущего пользователя можно узнать также с помощью утилиты logname и функции getlogin().

Для смены текущей группы пользователя предназначена служебная программа newgrp.

Чтобы узнать, какие пользователи в данный момент активны и за какими терминалами они работают в системе, можно воспользоваться служебной программой who.

Утилиты write, talk и mesg позволяют в ограниченной форме организовать взаимодействие между пользователями.

Базовым средством обеспечения почтового взаимодействия, согласно стандарту POSIX-2001, является служебная программа mailx.



Время


Согласно стандарту POSIX, за начало отсчета времени принимается ноль часов, ноль минут, ноль секунд первого января 1970-го года всемирного времени.

Всемирным называют поясное время нулевого часового пояса, которое представляет собой местное среднее солнечное время гринвичского меридиана.

За стандартную единицу измерения астрономического времени в POSIX-2001 принята секунда.

Часами называется программный или аппаратный объект, который может быть использован для измерения видимого или истинного хода времени.

Показания часов можно опросить и, возможно, установить (в допустимых для часов пределах).

Разрешающей способностью часов называется минимальный промежуток времени, измеряемый этими часами.

Тактом часов называются зависящие от реализации промежутки времени, на которые дробится каждая секунда.

Реальным (или астрономическим) называется время, измеренное по системным часам безотносительно к тому, какой процесс (поток управления) выполняется.

Под временем выполнения (процессорным временем) понимается время, затрачиваемое на выполнение процесса (потока управления), включая работающие от его имени системные сервисы.

Часами процессорного времени называются часы, измеряющие время выполнения конкретного процесса или потока управления.

Под виртуальным временем процесса понимается время, измеряемое системными часами, пока процесс выполняется.

таймер - это механизм, способный известить процесс (поток управления) об истечении заданного промежутка времени (интервальный таймер) или о достижении (превышении) часами заданных показаний (абсолютный таймер). Соответствующее событие называется срабатыванием таймера .

Таймером процессорного времени называется таймер, ассоциированный с часами процессорного времени.

Взвести (зарядить) - значит запустить таймер, измеряющий ход времени и позволяющий уведомить процесс о наступлении заданного момента.

Таймер снимается со взвода (разряжается), когда он перестает измерять ход времени.

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


На уровне языка C опрос текущего времени ( в секундах от начала отсчета) выполняет функция time().

Если тип time_t реализован как 32-разрядное целое со знаком, то в 2038-м году наступит переполнение (так называемая проблема 2038-го года).

С функцией time() ассоциирована функция difftime(), вычисляющая (в виде значения типа double) разность в секундах между двумя моментами времени.

Узнать текущее время с большей точностью позволяет функция gettimeofday().

В необязательную часть стандарта POSIX-2001, регламентирующую работу с таймерами, входят функции, позволяющие опросить и установить показания заданных часов, а также узнать их разрешающую способность: clock_gettime(), clock_settime(), clock_getres().

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

Стандартом POSIX-2001 предусмотрено несколько способов представления данных о времени. Для выполнения преобразований между разными представлениями данных о времени служат функции gmtime(), localtime(), mktime(), strftime(), strptime(), getdate().

Для учета данных о часовом поясе и сезонных поправках используются внешние переменные tzname, timezone, daylight, значения которых устанавливает по переменной окружения tz функция tzset().

Базовым средством для работы с часами процессорного времени является функция clock(). Она возвращает в качестве результата процессорное время, затраченное процессом с некоего момента, зависящего от реализации и связанного только с его (процесса) запуском.

Чтобы перевести время, возвращаемое функцией clock(), в секунды, его следует поделить на константу CLOCKS_PER_SEC, которая определена в заголовочном файле <time.h> равной одному миллиону.

Для измерения времени выполнения простой команды можно воспользоваться служебной программой time.

Более развитыми возможностями обладает специальная встроенная в shell команда times, она выдает на стандартный вывод процессорные времена, затраченные командным интерпретатором и порожденными им процессами.



Реализация утилит time и times опирается на функцию times(), опрашивающую данные о времени выполнения вызывающего процесса и порожденных процессов. Функция times() измеряет все времена в тактах часов. Соответственно, для перевода результатов работы times() в секунды их нужно делить на sysconf (_SC_CLK_TCK) , а не на CLOCKS_PER_SEC.

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

Для изменения ассоциированных с файлами данных о времени служит функция utime(), оперирующая в терминах секунд от начала отсчета.

Функция sleep() позволяет приостановить выполнение процесса (потока управления) на заданное число секунд.

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

Описываемые далее средства для работы с интервальными таймерами входят в необязательную часть стандарта POSIX-2001, именуемую "X/Open-расширение системного интерфейса"(XSI).

Реализация должна предоставлять каждому процессу по крайней мере три интервальных таймера, обозначаемых следующими идентификаторами: ITIMER_REAL (таймер реального времени, генерирует сигнал SIGALRM), ITIMER_VIRTUAL (таймер виртуального времени процесса, сигнал SIGVTALRM), ITIMER_PROF (таймер профилирования, сигнал SIGPROF).

Согласно стандарту POSIX-2001, интервальные таймеры обслуживаются функциями getitimer() и setitimer(). Функция getitimer() запоминает текущие характеристики таймера, а setitimer() взводит или снимает таймер со взвода, устанавливая новые характеристики и запоминая старые.

К теме интервальных таймеров логически примыкает функция alarm(), позволяющая вызывающему процессу заказать доставку сигнала sigalrm через заданное число секунд реального времени.