进程间通信 - 命名管道
管道旨在用于相关进程之间的通信。我们能否将管道用于不相关进程的通信,例如,我们想从一个终端执行客户端程序,从另一个终端执行服务器程序?答案是不行。那么我们如何实现不相关进程的通信呢?简单的答案是命名管道。尽管这适用于相关进程,但对于相关进程的通信使用命名管道毫无意义。
我们使用一个管道进行单向通信,使用两个管道进行双向通信。命名管道是否也适用相同的条件?答案是否定的,我们可以使用单个命名管道进行双向通信(服务器和客户端之间的通信,以及客户端和服务器同时的通信),因为命名管道支持双向通信。
命名管道的另一个名称是FIFO(先进先出)。让我们看看创建命名管道的系统调用 (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() 函数。
让我们考虑一个在一个终端运行服务器并在另一个终端运行客户端的程序。该程序只会执行单向通信。客户端接受用户输入并将消息发送到服务器,服务器在输出上打印消息。该过程将持续进行,直到用户输入字符串“end”。
让我们通过一个例子来理解这一点:
步骤 1 - 创建两个进程,一个是 fifoserver,另一个是 fifoclient。
步骤 2 - 服务器进程执行以下操作:
创建名为“MYFIFO”的命名管道(使用系统调用 mknod()),如果尚未创建。
以只读方式打开命名管道。
此处,创建的 FIFO 具有所有者可读可写权限。组可读,其他用户无权限。
无限期地等待来自客户端的消息。
如果从客户端接收到的消息不是“end”,则打印该消息。如果消息是“end”,则关闭 fifo 并结束进程。
步骤 3 - 客户端进程执行以下操作:
以只写方式打开命名管道。
接受来自用户的字符串。
检查用户输入的是“end”还是其他字符串。无论哪种方式,它都会将消息发送到服务器。但是,如果字符串是“end”,则这将关闭 FIFO 并结束进程。
无限期地重复,直到用户输入字符串“end”。
现在让我们看看 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 - 服务器进程执行以下操作:
创建名为“fifo_twoway”的命名管道(使用库函数 mkfifo())在 /tmp 目录中,如果尚未创建。
以读写方式打开命名管道。
此处,创建的 FIFO 具有所有者可读可写权限。组可读,其他用户无权限。
无限期地等待来自客户端的消息。
如果从客户端接收到的消息不是“end”,则打印该消息并反转字符串。反转后的字符串将发送回客户端。如果消息是“end”,则关闭 fifo 并结束进程。
步骤 3 - 客户端进程执行以下操作:
以读写方式打开命名管道。
接受来自用户的字符串。
检查用户输入的是“end”还是其他字符串。无论哪种方式,它都会将消息发送到服务器。但是,如果字符串是“end”,则这将关闭 FIFO 并结束进程。
如果发送的消息不是“end”,它将等待来自客户端的消息(反转后的字符串)并打印反转后的字符串。
无限期地重复,直到用户输入字符串“end”。
现在,让我们看看 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