本文演示 CentOS 7.6:Linux testerfans 3.10.0-1160.45.1.el7.x86_64
前言
Pipes用于关联进程之间的通信,我们是否可以使用管道进行不相关的进程通信呢?例如,我们想从一个终端执行客户端程序,而从另一个终端执行服务器程序?答案是否定的。那我们如何才能实现无关进程的通信呢?答案就是Named Pipes(命名管道)。命名管道也适用于关联进程之间的通信,但实际中并不会这样使用。
关联进程中我们使用一根Pipe进行单向通信,使用两根Pipe进行双向通信。这种情况是否适用于命名管道呢?答案是否定的。因为我们可以使用单个命名管道就可以实现进程之间的双向通信(服务器和客户端之间的通信,同时加上客户端和服务器之间的通信)。命名管道的另一个名称是FIFO(先进先出)。
命名管道特点
- 命名管道可以在无关的进程之间交换数据,与无名管道不同。
- 命名管道有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
系统调用
mkfifo()
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode)
mkfifo() 创建一个名为 pathname 的 FIFO 特殊文件。 mode 指定 FIFO 的权限。如果未给出完整路径名(或绝对路径),则将在执行进程的当前文件夹中创建文件。文件模式信息如 mknod() 系统调用中所述。
传参
- pathname 创建一个名为 pathname 的 FIFO 特殊文件。
- mode mode 指定 FIFO 的权限。
返回 - 0:成功。
- -1:失败。
使用 errno 变量或 perror() 函数查看失败信息。
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);
系统调用 mknod() 创建一个名为 pathname 的文件系统节点(文件、设备专用文件或命名管道),其属性由 mode 和 dev 指定。
传参
- pathname 存放命名管道文件路径,路径名是相对的,如果没有指定目录,它将在当前目录中创建。
- mode 指定要使用的权限和要创建的节点的类型。它应该是下面列出的文件类型之一和新节点的权限的组合(使用按位或)。
文件类型 | 描述 | 文件类型 | 描述 |
---|---|---|---|
S_IFBLK | block device 块特别 | S_IFREG | regular file 常规文件 |
S_IFCHR | character device 字符设备 | S_IFDIR | directory 目录 |
S_IFIFO | FIFO 命名管道 | S_IFLNK | symbolic link 符号链接 |
文件模式 | 描述 | 文件模式 | 描述 |
---|---|---|---|
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)。
- dev 如果文件类型是 S_IFCHR 或 S_IFBLK,则 dev 指定新创建的设备特殊文件的主要和次要编号;否则将被忽略。
返回
- 0:成功。
- -1:失败。
使用 errno 变量或 perror() 函数查看失败信息。
示例程序 1-命名管道单向通信
我们设计两个在不同的两个终端分别运行客户端和服务端的程序,他们将执行单向通信,即:客户端写入,服务端读取。当客户端输入”end“后,客户端和服务端退出。
示意图
步骤
第1步 - 创建两个进程,一个是 fifoserver,另一个是 fifoclient。
第 2 步- 服务器进程执行以下操作 :
- 如果未创建,则使用mknod()方法创建名为“MYFIFO”的命名管道。
- 只读打开命名管道。
- 所有者分配读/写权限,组分配读权限,其他人没有权限。
- 无限等待来自客户端的消息。
- 如果从客户端收到的消息不是“end”,则打印该消息。
- 如果消息为“end”,则关闭 fifo 并结束进程。
第 3 步 - 客户端进程执行以下操作 :
- 只写打开命名管道。
- 接受来自用户的字符串。
- 检查用户是否输入“end”或“end”以外的内容。
- 无论哪种方式,它都会向服务器发送消息。但是,如果字符串是“end”,这将关闭 FIFO 并结束进程。
- 无限重复,直到用户输入字符串“end”。
源代码
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;
//如果不存在则创建名称为MYFIFO的命名管道,为所有者分配读/写权限 组分配读权限 其他人无权限
mknod(FIFO_FILE, S_IFIFO|0640, 0);
//将"end"字符串拷贝到end[]数组内
strcpy(end, "end");
while(1) {
//只读方式打开FIFO,获取文件描述符
fd = open(FIFO_FILE, O_RDONLY);
//从FIFO中读取内容,返回读取到的字节数量
read_bytes = read(fd, readbuf, sizeof(readbuf));
//将读取到的内容最后设置为\0
readbuf[read_bytes] = '\0';
//打印读取内容
printf("Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
//对比读取到的内容是否为"end" 如果相等则关闭FIFO文件并跳出程序
to_end = strcmp(readbuf, end);
if (to_end == 0) {
close(fd);
break;
}
}
return 0;
}
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");
//读写方式打开一个MYFIFO文件 不存在则新建
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
end_process = strcmp(readbuf, end_str);
//printf("end_process is %d\n", end_process);
//如果输入不是end 像FIFO内写入标准输入的信息 如果是end依然写入
并且关闭文件并跳出程序
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;
}
编译
[root@testerfans pipes]# gcc fifoserver.c -o fifoserver
[root@testerfans pipes]# gcc fifoclient.c -o fifoclient
执行/输出
我们打开两个shell,一个作为客户端,一个作为服务端,执行结果如截图。
示例程序 2-命名管道双向通信
示例1我们演示了命名管道的单向通信,即:客户端写入,服务端读取。接下来我们将演示命名管道的双向通信,即:客户端和服务端之间互相发送消息。当客户端输入”end“后,客户端和服务端停止运行。
示意图
步骤
第1步 - 创建两个进程,一个是 fifoserver_twoway,另一个是 fifoclient_twoway。
第 2 步- 服务器进程执行以下操作 :
- 如果未创建,则使用mknod()方法在/tmp下创建名为“MYFIFO”的命名管道。
- 读写权限打开命名管道。
- 所有者分配读/写权限,组分配读权限,其他人没有权限。
- 无限等待来自客户端的消息。
- 如果从客户端收到的消息不是“end”,则打印该消息并将消息反转返回给客户端。
- 如果消息为“end”,则关闭 fifo 并结束进程。
第 3 步 - 客户端进程执行以下操作 :
- 读写权限打开命名管道。
- 接受来自用户的字符串。
- 检查用户是否输入“end”或“end”以外的内容。
- 无论哪种方式,它都会向服务器发送消息。但是,如果字符串是“end”,这将关闭 FIFO 并结束进程。
- 如果不是结束,等待服务端返回的信息。
- 无限重复,直到用户输入字符串“end”。
源代码
fifoserver_twoway.c
#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;
//如果不存在在/tmp下创建fifo_twoway的FIFO文件 所有者具备读写权限 组具备读权限 其他无权限
mkfifo(FIFO_FILE, S_IFIFO|0640);
strcpy(end, "end");
//读写方式打开文件
fd = open(FIFO_FILE, O_RDWR);
//从FIFO内读取文件 如果客户端输入end关闭文件并退出程序
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;
}
//如果客户端输入的不是end 则将读取到的信息反转
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;
}
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");
//向FIFO内写入文件并从中读取,因为服务端进行了两秒的sleep操作,确保客户端能正常读取到信息
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;
}
编译
[root@testerfans pipes]# gcc fifoserver_twoway.c -o fifoserver_twoway
[root@testerfans pipes]# gcc fifoclient_twoway.c -o fifoclient_twoway
执行/输出
我们打开两个shell,一个作为客户端,一个作为服务端,执行结果如截图。
总结
和Pipes无名管道相比,我们演示的Names Pipe命名管道的单向通信和双向通信都没有使用到fork()方法,说明在不相关进程之间的通信可以使用命名管道。另外命名管道进行双向通信的时候并没有创建两个管道,说明使用一个命名管道即可实现不相关进程间的单向或双向通信。
评论区