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

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

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

Другое название именованного канала - 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. Аргументами системного вызова являются путь, режим и разработчик. Путь вместе с атрибутами режима и информации об устройстве. Путь является относительным, если каталог не указан, он будет создан в текущем каталоге. Указанный режим - это режим файла, который определяет тип файла, такой как тип файла и режим файла, как указано в следующих таблицах. Поле 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 ().

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

Давайте разберемся в этом на примере -

Step 1 - Создайте два процесса, один из которых является фифосервером, а другой - фифоклиентом.

Step 2 - Серверный процесс выполняет следующее -

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

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

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

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

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

Step 3 - Клиентский процесс выполняет следующее -

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

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

  • Проверяет, вводит ли пользователь «конец» или иное, чем «конец». В любом случае он отправляет сообщение на сервер. Однако, если строка «конец», это закрывает 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

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

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

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

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

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

Step 2 - Серверный процесс выполняет следующее -

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

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

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

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

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

Step 3 - Клиентский процесс выполняет следующее -

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

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

  • Проверяет, вводит ли пользователь «конец» или иное, чем «конец». В любом случае он отправляет сообщение на сервер. Однако, если строка «конец», это закрывает 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.

/* Filename: fifoclient_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"
int main() {
   int fd;
   int end_process;
   int stringlen;
   int read_bytes;
   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_RDWR);
   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("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         read_bytes = read(fd, readbuf, sizeof(readbuf));
         readbuf[read_bytes] = '\0';
         printf("FIFOCLIENT: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      } else {
         write(fd, readbuf, strlen(readbuf));
         printf("FIFOCLIENT: 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: LINUX IPCs
FIFOCLIENT: Sent string: "LINUX IPCs" and string length is 10
FIFOCLIENT: Received string: "sCPI XUNIL" and length is 10
Enter string: Inter Process Communication
FIFOCLIENT: Sent string: "Inter Process Communication" and string length is 27
FIFOCLIENT: Received string: "noitacinummoC ssecorP retnI" and length is 27
Enter string: end
FIFOCLIENT: Sent string: "end" and string length is 3