Протокол TCP позволяет делать внеканальными данные объемом не больше одного байта за раз. Если отправитель передает новый внеканальный байт до того, как получатель обработал предыдущий, уведомление о ранее посланном байте теряется.
Ограничение размера внеканальных данных одним байтом является свидетельством несоответствия между универсальной внеканальной моделью программного интерфейса сокета и конкретной реализацией режима важности. Последний был затронут в подразделе 57.6.1, когда мы рассматривали формат TCP-сегментов. Чтобы уведомить о наличии важных (внеканальных) данных, протокол TCP устанавливает бит URG в TCP-заголовке и присваивает соответствующему полю указатель на эти данные. Однако TCP не может сообщить о длине байтовой последовательности, вследствие чего объем важных данных считается равным одному байту.
Информацию о важных данных в протоколе TCP можно найти в документе RFC 793.
В ряде систем (в их число не входит Linux) внеканальные данные поддерживаются потоковыми сокетами домена UNIX.
Применение внеканальных данных в наши дни нежелательно и в некоторых обстоятельствах может оказаться ненадежным (см. [Gont & Yourtchenko, 2009]). Альтернативой является использование двух потоковых сокетов. Один из них занимается обычным взаимодействием, а второй отвечает за обмен высокоприоритетной информацией. Для мониторинга обоих каналов приложение может применять одну из методик, описанных в главе 59. Такой подход позволяет устанавливать приоритет для данных, объем которых превышает один байт. Кроме того, его можно задействовать для потоковых сокетов в любом домене (в том числе UNIX).
57.13.2. Системные вызовы sendmsg() и recvmsg()
Наиболее универсальными операциями ввода/вывода для сокетов являются системные вызовы sendmsg() и recvmsg(). Первый вобрал в себя все возможности вызовов write(), send() и sendto(); второй способен заменить вызовы read(), recv() и recvfrom(). Кроме того, sendmsg() и recvmsg() позволяют делать следующее.
• Выполнять векторный ввод/вывод по примеру readv() и writev() (см. раздел 5.7). При использовании вызова sendmsg() для векторного вывода через датаграммный сокет (или вызова writev() в сочетании с подключенным датаграммным сокетом) генерируется единственная датаграмма. Аналогично вызов recvmsg() (и readv()) позволяет выполнить векторный ввод, разбивая единую датаграмму на несколько буферов в пользовательском пространстве.
• Передавать сообщения со вспомогательными (или управляющими) данными, относящимися к определенному домену. Вспомогательные данные могут быть переданы как через потоковые, так и через датаграммные сокеты. Некоторые примеры их использования представлены ниже.
57.13.3. Передача файловых дескрипторов
С помощью вызовов sendmsg() и recvmsg() и сокета в домене UNIX между двумя локальными процессами можно передавать вспомогательные данные, содержащие файловые дескрипторы. Это могут быть дескрипторы любого типа, например те, что возвращаются вызовам open() или pipe(). В качестве примера, имеющего более прямое отношение к этой главе, можно привести следующий сценарий. Главный сервер принимает клиентское соединение, используя слушающий TCP-сокет, и передает полученный дескриптор одному из своих дочерних процессов, входящих в состав серверного пула (см. раздел 56.4), который и ответит на клиентский запрос.
И хотя данную процедуру обычно называют передачей дескрипторов, на самом деле между процессами передается ссылка на один и тот же дескриптор (см. рис. 5.2). Номер файлового дескриптора на принимающей стороне обычно отличается от номера, используемого отправителем.
Пример передачи файловых дескрипторов приводится в файлах scm_rights_send.c и scm_rights_recv.c внутри подкаталога sockets, предоставленных с исходным кодом к данной книге.
57.13.4. Получение учетных данных отправителя
Еще одним примером использования вспомогательных данных является получение учетной информации через сокет домена UNIX. Такая информация состоит из идентификаторов пользователя, группы и процесса-отправителя. Отправляющая сторона может указать реальные, действующие или сохраненные идентификаторы. Это позволяет принимающему процессу аутентифицировать отправителя, находящегося в той же системе. Дополнительные подробности см. на страницах socket(7) и unix(7) руководства.
Передача учетных данных отправителя (в отличие от информации о файле) не предусмотрена стандартом SUSv3. Помимо Linux эту возможность поддерживают некоторые современные реализации BSD (передающие более подробную информацию, чем Linux) и несколько других систем семейства UNIX. Подробности о передаче учетных данных в FreeBSD описаны в книге [Stevens et al., 2004].
В Linux привилегированный процесс может подменять идентификаторы пользователя, группы и процесса, которые передаются в качестве учетных данных; для этого он должен поддерживать возможности CAP_SETUID, CAP_SETGID и, соответственно, CAP_SYS_ADMIN.