Беседа с незнакомцем

В данном примере мы рассмотрим, как процесс-клиент A может использовать какой-либо канал связи (локальный канал (pipe), сокет, совместно используемую память, собственный IPC, почтового голубя) с другим процессом-клиентом B для передачи ему пакета данных, содержащего достаточно информации для того, чтобы B смог обратится к процессу-серверу для дублирования файлового дескриптора в своем адресном пространстве.

Графически это можно представить так:

Сценарий таков:

Клиент 1 имеет файловый дескриптор отображенный на Сервер 1 (это может быть, например, файловая система) и коммуникационный канал связи с Клиентом 2 (например, pipe). Клиент 1 хочет, чтобы Клиент 2 использовал тот же самый файловый дескриптор, который есть в распоряжении Клиента 1.

В этом сценарии пакет данных будет представлять собой стандартное сообщение о дублировании файлового дескриптора, которое описано в <sys/iomsg.h>. Сообщение создается Клиентом 1 и содержит все информацию, необходимую Клиенту 2 для установки связи с сервером и получения дубликата файлового дескриптора Клиента 1.

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

/*
Создать пакет данных (сообщение), которое будет послано с
использованием func() для дублирования
файлового дескриптора fdtosend
*/

int send_fd(int fdtosend, int flags, void *arg,
int (*func)(void *arg, void *data, int len)) {
int ret;
io_dup_t dupmsg;

memset(&dupmsg;, 0, sizeof(dupmsg));

dupmsg.i.type = _IO_DUP;
dupmsg.i.combine_len = sizeof(dupmsg.i);

if((ret = ConnectServerInfo(0, fdtosend, &dupmsg.i.info;)) == -1) {
return ret;
}
/* Используем thread_id для передачи идентификатора нашего процесса */
dupmsg.i.info.tid = getpid();

printf(«SFD: client pid: %d\n»
» server nd: 0x%x pid 0x%x chid 0x%x scoid 0x%x coid 0x%x\n»,
dupmsg.i.info.tid,
dupmsg.i.info.nd,
dupmsg.i.info.pid,
dupmsg.i.info.chid,
dupmsg.i.info.scoid,
dupmsg.i.info.coid);

/* Осуществим отправку данных с помощью переданной нам функции */
if(func) {
ret = func(arg, &dupmsg;, sizeof(dupmsg));
} else {
ret = 0;
}

return ret;
}
В этой функции мы делаем следующее: заполняем структуру io_dup_t и отправляем ее принимающему процессу через коммуникационный канал, заданный пользователем. Мы получаем информацию о канале и сервере для файлового дескриптора, переданного в качестве параметра, используя ConnectServerInfo(). Эта функция возвращает почти все, что нам нужно для io_dup_t. Так как мы не передаем никаких дополнительных данных в этом сообщении, мы можем установить тип и combine_len сообщения в стандартные для _IO_DUB значения.

Когда принимающий процесс получит это сообщение, ему нужно будет знать идентификатор пославшего процесса для того, чтобы правильно сгенерировать _IO_DUP запрос к серверу. Так как в в msg_info есть несколько полей, которые не употребляются для передачи информации о сервере (смотрите <sys/neutrino.h>), мы можем использовать идентификатор потока (tid) для хранения pid Клиента 1.

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

/*
Функция получает данные data длиной len, проверяет их на правильность
и пытается получить новый файловый дескриптор.
*/
int receive_fd(void *data, int len) {
io_dup_t *pdupmsg = (io_dup_t *)data;
pid_t otherpid;
int ret, newfd;

/* Простейшая проверка на допустимый тип сообщения */
if(len < sizeof(*pdupmsg) || pdupmsg->i.type != _IO_DUP) {
errno = EINVAL;
return -1;
}

/* Извлечь значение, используемое нами для передачи pid Клиента 1 */
otherpid = pdupmsg->i.info.tid;
pdupmsg->i.info.tid = 0;

printf(«RFD: other pid: %d\n»
» server nd: 0x%x pid 0x%x chid 0x%x scoid 0x%x coid 0x%x\n»,
otherpid,
pdupmsg->i.info.nd,
pdupmsg->i.info.pid,
pdupmsg->i.info.chid,
pdupmsg->i.info.scoid,
pdupmsg->i.info.coid);

/* Пытаемся установить связь с каналом */
if ((newfd = ConnectAttach(pdupmsg->i.info.nd,
pdupmsg->i.info.pid,
pdupmsg->i.info.chid, 0, 0)) < 0) {
return -1;
}

/* Делаем вид, что мы Клиент 1 */
pdupmsg->i.info.pid = otherpid;

/*
Послать сообщение серверу с просьбой продублировать соединение,
которое установил Клиент 1. В случае успешного выполнения данного
запроса мы будем привязаны к ресурсу сервера. Если запрос завершится
ошибкой, то тогда просто отсоединяемся от канала.
*/
if(MsgSendnc(newfd, &pdupmsg-;>i, sizeof(pdupmsg->i), 0, 0) == -1) {
ConnectDetach_r(newfd);
return -1;
}

return newfd;
}
После элементарных проверок на то, что получено сообщение _IO_DUP, Клиент 2 пытается установить связь с тем же самым каналом, что и Клиент 1, используя функцию ConnectAttach(). Если вызов ConnectAttach() завершается успешно, посылается запрос _IO_DUP, который привяжет полученное соединение в конкретному файлу или устройству менеджера ресурсов, в роли которого выступает Сервер1. После такой привязки идентификатор соединения становится настоящим файловым дескриптором, который можно использовать в нормальных функция типа read()/write().