4.4. Двусторонние каналы
4.4. Двусторонние каналы
В предыдущем разделе мы отметили, что во многих системах реализованы двусторонние каналы. В Unix SVR4 это обеспечивается самой функцией pipe, а во многих других ядрах — функцией socketpair. Но что в действительности представляет собой двусторонний канал? Представим себе сначала однонаправленный канал, изображенный на рис. 4.8.
Рис. 4.8. Односторонний канал
Двусторонний канал мог бы быть реализован так, как это изображено на рис. 4.9. В этом случае неявно предполагается существование единственного буфера, в который помещается все, что записывается в канал (с любого конца, то есть дескриптора), и при чтении из канала данные просто считываются из буфера.
Рис. 4.9. Одна из возможных реализаций двустороннего канала (неправильная)
Такая реализация вызовет проблемы, например, в программе листинга А.14. Здесь требуется двусторонняя передача информации, причем потоки данных должны быть независимы. В противном случае некоторый процесс, записав данные в канал и перейдя затем в режим чтения из этого же канала, рискует считать обратно те же данные, которые были им только что туда записаны.
На рис. 4.10 изображена правильная реализация двустороннего канала.
Рис. 4.10. Правильная реализация двустороннего канала
Здесь двусторонний канал получается из объединения двух односторонних. Все данные, записываемые в fd[1], будут доступны для чтения из fd[0], а данные, записываемые в fd[0], будут доступны для чтения из fd[1].
Программа в листинге 4.4 иллюстрирует использование одного двустороннего канала для двусторонней передачи информации.
Листинг 4.4. Двусторонняя связь через двусторонний канал
//pipe/fduplex.c
1 #include "unpipc.h"
2 int
3 main(int argc, char **argv)
4 {
5 int fd[2], n;
6 char c;
7 pid_t childpid;
8 Pipe(fd); /* предполагается двусторонний канал (напр., SVR4) */
9 if ((childpid = Fork()) == 0) { /* child */
10 sleep(3):
11 if ((n = Read(fd[0], &c, 1)) != 1)
12 err_quit("child: read returned %d", n);
13 printf("child read %c ", c):
14 Write(fd[0], "c", 1);
15 exit(0);
16 }
17 /* родитель */
18 Write(fd[1], "p", 1);
19 if ((n = Read(fd[1], &c, 1)) != 1)
20 err_quit("parent: read returned %d", n):
21 printf("parent read %c ", c);
22 exit(0);
23 }
В этой программе сначала создается двусторонний канал, затем делается системный вызов fork. Породивший процесс записывает символ р в канал, а затем считывает из канала данные. Дочерний процесс ждет три секунды, считывает символ из канала, а потом записывает туда символ с. Задержка чтения для дочернего процесса позволяет породившему процессу вызвать read первым — таким образом мы можем узнать, не будет ли им считан обратно только что записанный символ.
При запуске этой программы в Solaris 2.6, в которой организована поддержка двусторонних каналов, мы получим ожидаемый результат:
solaris % fduplex
child read p
parent read с
Символ р передается по одному из двух односторонних каналов, изображенных на рис. 4.10, а именно по верхнему каналу. Символ с передается по нижнему одностороннему каналу. Родительский процесс не считывает обратно записанный им в канал символ р (что и требуется).
При запуске этой программы в Digital Unix 4.0B, в которой по умолчанию создаются односторонние каналы (двусторонние каналы — как в SVR4 — будут создаваться в том случае, если при компиляции указать специальные параметры), мы увидим результат, ожидаемый для одностороннего канала:
alpha % fduplex
read error: Bad file number
alpha % child read p
write error: Bad file number
Родительский процесс записывает символ р, который успешно считывается дочерним процессом, однако при попытке считывания из канала (дескриптор fd[l]) родительский процесс прерывается с ошибкой, как и дочерний процесс, при попытке записи в канал (дескриптор fd[0]). Вспомните рис. 4.8. Функция read возвращает код ошибки EBADF, означающий, что дескриптор не открыт для чтения. Аналогично write возвращает тот же код ошибки, если дескриптор не был открыт на запись.
Данный текст является ознакомительным фрагментом.