Учебники

Межпроцессное взаимодействие — именованные каналы

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

Мы использовали одну трубу для односторонней связи и две трубы для двунаправленной связи. Применяется ли такое же условие для именованных каналов. Ответ — нет, мы можем использовать один именованный канал, который можно использовать для двусторонней связи (связь между сервером и клиентом, а также клиентом и сервером одновременно), поскольку Named Pipe поддерживает двунаправленную связь.

Другое имя для именованного канала — FIFO (First-In-First-Out) . Давайте посмотрим системный вызов (mknod ()) для создания именованного канала, который является своего рода специальным файлом.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int mknod(const char *pathname, mode_t mode, dev_t dev);

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

Тип файла Описание Тип файла Описание
S_IFBLK блок специальный S_IFREG Обычный файл
S_IFCHR особый характер S_IFDIR каталог
S_IFIFO FIFO специальный S_IFLNK Символическая ссылка
Файловый режим Описание Файловый режим Описание
S_IRWXU Чтение, запись, выполнение / поиск по владельцу S_IWGRP Разрешение на запись, группа
S_IRUSR Разрешение на чтение, владелец S_IXGRP Выполнить / поиск разрешение, группа
S_IWUSR Разрешение на запись, владелец S_IRWXO Читать, писать, выполнять / искать другими
S_IXUSR Разрешение на выполнение / поиск, владелец S_IROTH Разрешение на чтение, другие
S_IRWXG Чтение, запись, выполнение / поиск по группе S_IWOTH Разрешение на запись, другие
S_IRGRP Разрешение на чтение, группа S_IXOTH Разрешение на выполнение / поиск, другие

Режим файла также может быть представлен в восьмеричной записи, такой как 0XYZ, где X представляет владельца, Y представляет группу, а Z представляет другие. Значения X, Y или Z могут находиться в диапазоне от 0 до 7. Значения для чтения, записи и выполнения — 4, 2, 1 соответственно. Если необходимо в комбинации чтения, записи и выполнения, затем добавьте соответствующие значения.

Скажем, если мы упомянем 0640, то это означает чтение и запись (4 + 2 = 6) для владельца, чтение (4) для группы и отсутствие разрешений (0) для других.

Этот вызов вернет ноль в случае успеха и -1 в случае сбоя. Чтобы узнать причину ошибки, проверьте с помощью переменной errno или функции perror ().

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode)

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

Этот вызов вернет ноль в случае успеха и -1 в случае сбоя. Чтобы узнать причину ошибки, проверьте с помощью переменной errno или функции perror ().

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

Позвольте нам понять это на примере —

Шаг 1 — Создайте два процесса, один из них fifoserver, а другой — fifoclient.

Шаг 2 — Серверный процесс выполняет следующее —

  • Создает именованный канал (используя системный вызов mknod ()) с именем «MYFIFO», если он не создан.

  • Открывает именованный канал только для чтения.

  • Здесь создан FIFO с разрешениями на чтение и запись для владельца. Читайте для группы и никаких разрешений для других.

  • Бесконечно ждет сообщения от Клиента.

  • Если сообщение, полученное от клиента, не является «концом», печатается сообщение. Если сообщение «конец», закрывает fifo и завершает процесс.

Создает именованный канал (используя системный вызов mknod ()) с именем «MYFIFO», если он не создан.

Открывает именованный канал только для чтения.

Здесь создан FIFO с разрешениями на чтение и запись для владельца. Читайте для группы и никаких разрешений для других.

Бесконечно ждет сообщения от Клиента.

Если сообщение, полученное от клиента, не является «концом», печатается сообщение. Если сообщение «конец», закрывает fifo и завершает процесс.

Шаг 3 — Клиентский процесс выполняет следующее —

  • Открывает именованный канал только для записи.

  • Принимает строку от пользователя.

  • Проверяет, вводит ли пользователь «конец» или другой, чем «конец». В любом случае, он отправляет сообщение на сервер. Однако, если строка «end», это закрывает FIFO и также завершает процесс.

  • Повторяется бесконечно, пока пользователь не введет строку «конец».

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

Принимает строку от пользователя.

Проверяет, вводит ли пользователь «конец» или другой, чем «конец». В любом случае, он отправляет сообщение на сервер. Однако, если строка «end», это закрывает FIFO и также завершает процесс.

Повторяется бесконечно, пока пользователь не введет строку «конец».

Теперь давайте посмотрим на файл сервера FIFO.

/* Filename: fifoserver.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "MYFIFO"
int main() {
   int fd;
   char readbuf[80];
   char end[10];
   int to_end;
   int read_bytes;
   
   /* Create the FIFO if it does not exist */
   mknod(FIFO_FILE, S_IFIFO|0640, 0);
   strcpy(end, "end");
   while(1) {
      fd = open(FIFO_FILE, O_RDONLY);
      read_bytes = read(fd, readbuf, sizeof(readbuf));
      readbuf[read_bytes] = '\0';
      printf("Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      to_end = strcmp(readbuf, end);
      if (to_end == 0) {
         close(fd);
         break;
      }
   }
   return 0;
}

Шаги компиляции и выполнения

Received string: "this is string 1" and length is 16
Received string: "fifo test" and length is 9
Received string: "fifo client and server" and length is 22
Received string: "end" and length is 3

Теперь давайте посмотрим на пример кода клиента FIFO.

/* Filename: fifoclient.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "MYFIFO"
int main() {
   int fd;
   int end_process;
   int stringlen;
   char readbuf[80];
   char end_str[5];
   printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
   fd = open(FIFO_FILE, O_CREAT|O_WRONLY);
   strcpy(end_str, "end");
   
   while (1) {
      printf("Enter string: ");
      fgets(readbuf, sizeof(readbuf), stdin);
      stringlen = strlen(readbuf);
      readbuf[stringlen - 1] = '\0';
      end_process = strcmp(readbuf, end_str);
      
      //printf("end_process is %d\n", end_process);
      if (end_process != 0) {
         write(fd, readbuf, strlen(readbuf));
         printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
      } else {
         write(fd, readbuf, strlen(readbuf));
         printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         close(fd);
         break;
      }
   }
   return 0;
}

Давайте рассмотрим прибывающий вывод.

Шаги компиляции и выполнения

FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: this is string 1
Sent string: "this is string 1" and string length is 16
Enter string: fifo test
Sent string: "fifo test" and string length is 9
Enter string: fifo client and server
Sent string: "fifo client and server" and string length is 22
Enter string: end
Sent string: "end" and string length is 3

Двусторонняя связь с использованием именованных каналов

Связь между трубами должна быть однонаправленной. Каналы были ограничены односторонней связью в целом, и для двусторонней связи нужны как минимум две трубы. Трубы предназначены только для взаимосвязанных процессов. Каналы нельзя использовать для связи между несвязанными процессами, скажем, если мы хотим выполнить один процесс с одного терминала, а другой процесс с другого терминала, это невозможно с каналами. Есть ли у нас какой-нибудь простой способ общения между двумя процессами, скажем, несвязанными процессами простым способом? Ответ ДА. Именованный канал предназначен для связи между двумя или более несвязанными процессами и может также иметь двунаправленную связь.

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

Ниже приведен пример —

Шаг 1 — Создайте два процесса, один из них fifoserver_twoway, а другой — fifoclient_twoway.

Шаг 2 — Серверный процесс выполняет следующее —

  • Создает именованный канал (используя библиотечную функцию mkfifo ()) с именем fifo_twoway в каталоге / tmp, если он не создан.

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

  • Здесь создан FIFO с разрешениями на чтение и запись для владельца. Читайте для группы и никаких разрешений для других.

  • Бесконечно ждет сообщения от клиента.

  • Если сообщение, полученное от клиента, не является «концом», печатает сообщение и переворачивает строку. Перевернутая строка отправляется обратно клиенту. Если сообщение «конец», закрывает fifo и завершает процесс.

Создает именованный канал (используя библиотечную функцию mkfifo ()) с именем fifo_twoway в каталоге / tmp, если он не создан.

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

Здесь создан FIFO с разрешениями на чтение и запись для владельца. Читайте для группы и никаких разрешений для других.

Бесконечно ждет сообщения от клиента.

Если сообщение, полученное от клиента, не является «концом», печатает сообщение и переворачивает строку. Перевернутая строка отправляется обратно клиенту. Если сообщение «конец», закрывает fifo и завершает процесс.

Шаг 3 — Клиентский процесс выполняет следующее —

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

  • Принимает строку от пользователя.

  • Проверяет, вводит ли пользователь «конец» или другой, чем «конец». В любом случае, он отправляет сообщение на сервер. Однако, если строка «end», это закрывает FIFO и также завершает процесс.

  • Если сообщение отправлено как не «конец», оно ожидает сообщения (обратная строка) от клиента и печатает обратную строку.

  • Повторяется бесконечно, пока пользователь не введет строку «конец».

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

Принимает строку от пользователя.

Проверяет, вводит ли пользователь «конец» или другой, чем «конец». В любом случае, он отправляет сообщение на сервер. Однако, если строка «end», это закрывает FIFO и также завершает процесс.

Если сообщение отправлено как не «конец», оно ожидает сообщения (обратная строка) от клиента и печатает обратную строку.

Повторяется бесконечно, пока пользователь не введет строку «конец».

Теперь давайте посмотрим на пример кода сервера FIFO.

/* Filename: fifoserver_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "/tmp/fifo_twoway"
void reverse_string(char *);
int main() {
   int fd;
   char readbuf[80];
   char end[10];
   int to_end;
   int read_bytes;
   
   /* Create the FIFO if it does not exist */
   mkfifo(FIFO_FILE, S_IFIFO|0640);
   strcpy(end, "end");
   fd = open(FIFO_FILE, O_RDWR);
   while(1) {
      read_bytes = read(fd, readbuf, sizeof(readbuf));
      readbuf[read_bytes] = '\0';
      printf("FIFOSERVER: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      to_end = strcmp(readbuf, end);
      
      if (to_end == 0) {
         close(fd);
         break;
      }
      reverse_string(readbuf);
      printf("FIFOSERVER: Sending Reversed String: \"%s\" and length is %d\n", readbuf, (int) strlen(readbuf));
      write(fd, readbuf, strlen(readbuf));
      /*
      sleep - This is to make sure other process reads this, otherwise this
      process would retrieve the message
      */
      sleep(2);
   }
   return 0;
}

void reverse_string(char *str) {
   int last, limit, first;
   char temp;
   last = strlen(str) - 1;
   limit = last/2;
   first = 0;
   
   while (first < last) {
      temp = str[first];
      str[first] = str[last];
      str[last] = temp;
      first++;
      last--;
   }
   return;
}

Шаги компиляции и выполнения

FIFOSERVER: Received string: "LINUX IPCs" and length is 10
FIFOSERVER: Sending Reversed String: "sCPI XUNIL" and length is 10
FIFOSERVER: Received string: "Inter Process Communication" and length is 27
FIFOSERVER: Sending Reversed String: "noitacinummoC ssecorP retnI" and length is 27
FIFOSERVER: Received string: "end" and length is 3

Теперь давайте посмотрим на пример кода клиента FIFO.