KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программное обеспечение » Уильям Стивенс - UNIX: разработка сетевых приложений

Уильям Стивенс - UNIX: разработка сетевых приложений

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Уильям Стивенс, "UNIX: разработка сетевых приложений" бесплатно, без регистрации.
Перейти на страницу:

Передача ядром 4.4BSD открытого дескриптора через доменный сокет Unix описывается в главе 18 [112].

SVR4 использует другую технологию внутри ядра для передачи открытого дескриптора: команды I_SENDFD и I_RECVFD функции ioctl, описанные в разделе 15.5.1 [110]. Но процесс все же имеет возможность доступа к указанному свойству ядра за счет доменного сокета Unix. В этой книге мы описываем применение доменных сокетов Unix для передачи открытых дескрипторов, поскольку это наиболее переносимая технология программирования: она работает как с Беркли-ядрами, так и с SVR4, в то время как команды I_SENDFD и I_RECVFD функции ioctl работают только в SVR4.

Технология 4.4BSD позволяет передавать множество дескрипторов с помощью одиночной функции sendmsg, в то время как технология SVR4 передает за один раз только один дескриптор. Во всех наших примерах за один раз передается один дескриптор.

Шаги при передаче дескриптора между процессами будут такими:

1. Создание доменного сокета Unix, или потокового сокета, или дейтаграммного сокета.

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

Если процессы не являются родственными, сервер должен создать потоковый доменный сокет Unix, связать его при помощи функции bind с полным именем, тем самым позволяя клиенту соединиться с этим сокетом при помощи функции connect. Затем клиент может отправить запрос серверу для открытия некоторого дескриптора, а сервер может передать дескриптор обратно через доменный сокет Unix. Как альтернатива между клиентом и сервером может также использоваться дейтаграммный доменный сокет Unix, однако преимущества этого способа невелики, к тому же существует возможность игнорирования дейтаграммы. Далее в примерах этой главы мы будем использовать потоковый сокет между клиентом и сервером.

2. Один процесс открывает дескриптор при помощи вызова любой из функций Unix, возвращающей дескриптор, например open, piре, mkfifo, socket или accept. От одного процесса к другому можно передать дескриптор любого типа, поэтому мы называем эту технологию «передачей дескриптора», а не «передачей дескриптора файла».

3. Отправляющий процесс строит структуру msghdr (см. раздел 14.5), содержащую дескриптор, который нужно передать. В POSIX определено, что дескриптор должен отправляться как вспомогательные данные (элемент msg_control структуры msghdr, см. раздел 14.6), но более старые реализации используют элемент msg_accrights. Отправляющий процесс вызывает функцию sendmsg для отправки дескриптора через доменный сокет Unix, созданный на шаге 1. На этом этапе мы говорим, что дескриптор находится «в полете». Даже если отправляющий процесс закроет дескриптор после вызова функции sendmsg, но до вызова принимающим процессом функции recvmsg, дескриптор останется открытым для принимающего процесса. Отправка дескриптора увеличивает счетчик ссылок дескриптора на единицу.

4. Принимающий процесс вызывает функцию recvmsg для получения дескриптора через доменный сокет Unix, созданный на шаге 1. Номер дескриптора в принимающем процессе может отличаться от номера дескриптора в отправляющем процессе. Передача дескриптора — это не передача номера дескриптора. Этот процесс включает создание нового дескриптора в принимающем процессе, который ссылается на ту же запись таблицы файлов в ядре, что и дескриптор, отправленный отправляющим процессом.

Клиент и сервер должны располагать некоторым протоколом уровня приложения, с тем чтобы получатель дескриптора имел информацию о времени его появления. Если получатель вызывает функцию recvmsg, не выделив места в памяти для получения дескриптора, и дескриптор передается как готовый для чтения, то передаваемый дескриптор закрывается [128, с. 518]. Кроме того, нужно избегать установки флага MSG_PEEK в функции recvmsg, если предполагается получение дескриптора, поскольку в этом случае результат непредсказуем.

Пример передачи дескриптора

Теперь мы представим пример передачи дескриптора. Мы напишем программу под названием mycat, которой в качестве аргумента командной строки передается полное имя файла. Эта программа открывает файл и копирует его в стандартный поток вывода. Но вместо вызова обычной функции Unix open мы вызываем нашу собственную функцию my_open. Эта функция создает потоковый канал и вызывает функции fork и exec для запуска другой программы, открывающей нужный файл. Эта программа должна затем передать дескриптор обратно родительскому процессу по потоковому каналу.

На рис. 15.1 показан первый шаг: наша программа mycat после создания потокового канала при помощи вызова функции socketpair. Мы обозначили два дескриптора, возвращаемых функцией socketpair, как [0] и [1].

Рис. 15.1. Программа mycat после создания потокового канала при использовании функции socketpair

Затем процесс взывает функцию fork, и дочерний процесс вызывает функцию exec для выполнения программы openfile. Родительский процесс закрывает дескриптор [1], а дочерний процесс закрывает дескриптор [0]. (Нет разницы, на каком конце потокового канала происходит закрытие. Дочерний процесс мог бы закрыть [1], а родительский — [0].) При этом получается схема, показанная на рис. 15.2.

Рис. 15.2. Программа mycat после запуска программы openfile

Родительский процесс должен передать программе openfile три фрагмента информации: полное имя открываемого файла, режим открытия (только чтение чтение и запись или только запись) и номер дескриптора, соответствующий его концу потокового канала (который мы обозначили [1]). Мы выбрали такой способ передачи этих трех элементов, как ввод аргументов командной строки при вызове функции exec. Альтернативным способом будет отправка этих элементов в качестве данных по потоковому каналу. Программа отправляет обратно открытый дескриптор по потоковому каналу и завершается. Статус выхода программы сообщает родительскому процессу, смог ли файл открыться, и если нет, то какого типа ошибка произошла.

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

Листинг 15.7. Программа mycat: копирование файла в стандартный поток вывода

//unixdomain/mycat.c

 1 #include "unp.h"


 2 int my_open(const char*, int);


 3 int

 4 main(int argc, char **argv)

 5 {

 6  int fd, n;

 7  char buff[BUFFSIZE];


 8  if (argc != 2)

 9   err_quit("usage: mycat <pathname>");


10  if ((fd = my_open(argv[1], O_RDONLY)) < 0)

11   err_sys("cannot open %s", argv[1]);


12  while ((n = Read(fd, buff, BUFFSIZE)) > 0)

13   Write(STDOUT_FILENO, buff, n);


14  exit(0);

15 }

Если мы заменим вызов функции my_open вызовом функции open, эта простая программа всего лишь скопирует файл в стандартный поток вывода.

Функция my_open, показанная в листинге 15.8, должна выглядеть для вызывающего процесса как обычная функция Unix open. Она получает два аргумента — полное имя и режим открытия (например, O_RDONLY обозначает, что файл доступен только для чтения), открывает файл и возвращает дескриптор.

Листинг 15.8. Функция my_open: открытие файла и возвращение дескриптора

//unixdomain/myopen.c

 1 #include "unp.h"


 2 int

 3 my_open(const char *pathname, int mode)

 4 {

 5  int fd, sockfd[2], status;

 6  pid_t childpid;

 7  char c, argsockfd[10], argmode[10];


 8  Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);


 9  if ((childpid = Fork()) == 0) { /* дочерний процесс */

10   Close(sockfd[0]);

11   snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[1]);

12   snprintf(argmode, sizeof(argmode), "%d", mode);

13   execl("./openfile", "openfile", argsockfd, pathname, argmode,

14    (char*)NULL);

15   err_sys("execl error");

16  }

17  /* родительский процесс - ожидание завершения дочернего процесса */

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*