目录
0. 命名管道
命名管道(FIFO)和管道(pipe)基本相同,但也有一些显著的不同。
其特点是:
- 半双工,数据在同一时刻只能在一个方向上流动。
- 写入FIFO中的数据遵循先入先出的规则。
- FIFO所传送的数据是无格式的,这要求FIFO的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。
- FIFO在文件系统中作为一个特殊的文件而存在并且在文件系统中可见,所以有名管道可以实现不相关进程间通信,FIFO中的内容却存放在内存中。
- 管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
- 从FIFO读数据是一次性操作,数据一旦被读取,它就从FIFO中被抛弃,释放空间以便写更多的数据。
- 当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。
- FIFO有名字,不相关的进程可以通过打开命名管道进行通信。
1. 有名管道的创建
方法1:用shell命令mkfifo创建有名管道
mkfifo 文件名
方法2:使用函数mkfifo
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道,产生一个本地文件系统可见的文件pathname
参数:
pathname:有名管道创建生成的文件,可以代码路径
mode: 管道文件的权限,一般通过八进制数设置即可,例如0664
返回值:
成功:0
失败:-1
代码示例:
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
if (mkfifo("fifo_file", 0664) == -1)
{
// printf("errno:%d\n",errno);
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
return 0;
}
2. 有名管道的基本读写操作
由于有名管道在本地创建了一个管道文件,所以系统调用的IO函数基本都可以对有名管道进行操作,但是不能使用lseek修改管道文件的偏移量。
注意:有名管道创建的本地的文件只是起到表示作用,真正有名管道实现进程间通信还是在内核空间开辟内存,所以本地产生的文件只是一个标识,没有其他作用,对本地管道文件的操作实质就是对内核空间的操作。
代码案例:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFONAME "fifo_file"
int main()
{
if (mkfifo(FIFONAME, 0664) == -1)
{
// printf("errno:%d\n",errno);
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
int fd;
//打开
fd = open(FIFONAME, O_RDWR);
if (fd == -1)
{
perror("fail to open");
exit(1);
}
//写入数据
if (write(fd, "hello world", strlen("hello world")) == -1)
{
perror("fail to write");
exit(1);
}
//再次写数据
write(fd, "nihao bejing", strlen("hello world"));
char buf[32] = "";
//读数据
if (read(fd, buf, sizeof(buf)) == -1)
{
perror("fail to read");
exit(1);
}
printf("buf = [%s]\n", buf);
/* 此时管道中无数据,再读则阻塞
if (read(fd, buf, sizeof(buf)) == -1)
{
perror("fail to read");
exit(1);
}
*/
//关闭
close(fd);
return 0;
}
3. 有名管道实现进程间通信
由于有名管道在本地创建了一个管道文件,所有不相关的进程间也可以实现通信。
下面程序1和程序2分别是两个不同的进程通过命名管道进行通信。
程序1:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFO_01 "fifo_01"
#define FIFO_02 "fifo_02"
int main()
{
if (mkfifo(FIFO_01, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
if (mkfifo(FIFO_02, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
int fd_w, fd_r;
if ((fd_w = open(FIFO_01, O_WRONLY)) == -1)
{
perror("fail to oepn");
exit(1);
}
if ((fd_r = open(FIFO_02, O_RDONLY)) == -1)
{
perror("fail to open");
exit(1);
}
// printf("%d %d\n",fd_w,fd_r);
char buf[128] = "";
ssize_t bytes;
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fail to fokr");
exit(1);
}
else if (pid > 0)
{
while (1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
if ((bytes = write(fd_w, buf, sizeof(buf))) == -1)
{
perror("fail to write");
exit(1);
}
}
}
else
{
while (1)
{
if ((bytes = read(fd_r, buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
if (bytes)
{
buf[bytes] = '\0';
printf("from fifo_02: %s\n", buf);
memset(buf, 0, sizeof(buf));
}
}
}
return 0;
}
程序2:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFO_01 "fifo_01"
#define FIFO_02 "fifo_02"
int main()
{
if (mkfifo(FIFO_01, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
if (mkfifo(FIFO_02, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
int fd_w, fd_r;
if ((fd_r = open(FIFO_01, O_RDONLY)) == -1)
{
perror("fail to open");
exit(1);
}
if ((fd_w = open(FIFO_02, O_WRONLY)) == -1)
{
perror("fail to oepn");
exit(1);
}
// printf("%d %d\n",fd_w,fd_r);
char buf[128] = "";
ssize_t bytes;
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fail to fork");
exit(1);
}
else if (pid > 0)
{
while (1)
{
if ((bytes = read(fd_r, buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
if (bytes)
{
buf[bytes] = '\0';
printf("from fifo_01: %s\n", buf);
memset(buf, 0, sizeof(buf));
}
}
}
else
{
while (1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
if ((bytes = write(fd_w, buf, sizeof(buf))) == -1)
{
perror("fail to write");
exit(1);
}
}
}
return 0;
}
运行截图:
4. 有名管道的读写规律(阻塞)
4.1 读写端都存在,只读不写
//读写端都存在,只读不写
//如果原本管道中有数据,则正常读取
//如果管道中没有数据,则read函数会阻塞等待
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFONAME1 "myfifo"
int main()
{
if (mkfifo(FIFONAME1, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//读写端都存在,只读不写
//如果原本管道中有数据,则正常读取
//如果管道中没有数据,则read函数会阻塞等待
int fd;
if ((fd = open(FIFONAME1, O_RDWR)) == -1)
{
perror("fail to open");
exit(1);
}
write(fd, "hello world", 11);
char buf[128] = "";
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
return 0;
}
执行结果:
4.2 读写端都存在,只写不读
//读写端都存在,只写不读
//当有名管道的缓冲区写满后,write函数会发送阻塞
//默认有名管道的缓冲区为64K字节
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<stdlib.h>
#define FIFONAME "myfifo"
int main()
{
if (mkfifo(FIFONAME, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//读写端都存在,只写不读
//当有名管道的缓冲区写满后,write函数会发送阻塞
//默认有名管道的缓冲区为64K字节
int fd;
if ((fd = open(FIFONAME, O_RDWR) == -1)
{
perror("fail to open");
exit(1);
}
int num = 0;
while (1)
{
write(fd, "", 1024);
num++;
printf("num = %d\n", num);
}
return 0;
}
执行结果:
4.3 同一个进程中,只有读端,没有写端
//在一个进程中,只有读端,没有写端
//会在open函数的位置阻塞
代码示例:
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFONAME1 "myfifo"
int main()
{
if (mkfifo(FIFONAME1, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//在一个进程中,只有读端,没有写端
//会在open函数的位置阻塞
printf("********************************\n");
int fd;
if ((fd = open(FIFONAME1, O_RDONLY)) == -1)
{
perror("fail to open");
exit(1);
}
printf("___________________________\n");
char buf[128] = "";
ssize_t bytes;
if ((bytes = read(fd, buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
printf("buf = %s\n", buf);
printf("bytes = %ld\n", bytes);
return 0;
}
执行结果:
4.4 同一个进程中,只有写段,没有读端
//在一个进程中只有写端,没有读端
//会在open函数的位置阻塞
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<stdlib.h>
#define FIFONAME "myfifo"
int main()
{
if (mkfifo(FIFONAME, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//在一个进程中只有写端,没有读端
//会在open函数的位置阻塞
int fd;
if ((fd = open(FIFONAME, O_WRONLY)) == -1)
{
perror("fail to open");
exit(1);
}
write(fd, "hello world", 11);
printf("****************************\n");
return 0;
}
执行结果:
4.5 一个进程只读,一个进程只写
将上面4.3和4.4代码一起运行,保证有名管道读写端都存在。
规律:
只要保证有名管道的读写端都存在,不管是几个进程,都不会在open这里阻塞了。
如果一个进程只读,一个进程只写,都运行后,如果关系写端,读端read会返回0;
如果一个进程只读,一个进程只写,都运行后,如果关闭读端,写端会立即产生SIGPIPE信号,默认的处理方式是退出进程。
5.有名管道的读写规律(非阻塞)
指定O_NONBLOCK(即open位或O_NONBLOCK)
- 先以只读方式打开,如果没有进程为写而打开一个FIFO,只读open成功,并且open不阻塞
- 先以只写方式打开,如果没有进程为读而打开一个FIFO,只写open将出错返回-1
- read、write读写命名管道中读数据时不阻塞
- 通信过程中,读进程退出后,写进程想命名管道内些数据时,写进程也会(收到SIGPIPE信号)退出。
代码示例:
#include<string.h>
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFONAME "myfifo"
int main()
{
int fd;
if (mkfifo(FIFONAME, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifoJ");
exit(1);
}
}
#if 0
//如果open标志位设置为非阻塞,并且以只读的方式打开管道文件
//open函数和read函数都不会阻塞
fd = open(FIFONAME, O_RDONLY | O_NONBLOCK);
if (fd < 0)
{
perror("fail to open");
exit(1);
}
while (1)
{
char recv[100] = "";
memset(recv, 0, sizeof(recv));
read(fd, recv, sizeof(recv));
printf("read from myfifo buf = [%s]\n", recv);
sleep(1);
}
#endif
#if 0
//如果open标志位设置为非阻塞,并且以只写方式打开管道文件
//open函数会直接报错
char send[100] = "Hello I love you";
fd = open(FIFONAME, O_WRONLY | O_NONBLOCK);
if (fd < 0)
{
perror("fail to open");
exit(1);
}
write(fd, send, strlen(send));
printf("write to myfifo buf = %s\n", send);
char recv[100];
read(fd, recv, sizeof(recv));
printf("read from myfifo buf = [%s]\n", recv);
#endif
#if 1
//如果open标志位设置为非阻塞,并且以读写方式打开管道文件
//这样和阻塞是一样的效果。
char send[100] = "Hello I love you";
fd = open(FIFONAME, O_RDWR | O_NONBLOCK);
if (fd < 0)
{
perror("fail to open");
exit(1);
}
write(fd, send, strlen(send));
printf("write to myfifo buf = %s\n", send);
char recv[100];
read(fd, recv, sizeof(recv));
printf("read from myfifo buf = [%s]\n", recv);
#endif
return 0;
}
执行截图:
总结:
命名管道(FIFO)为我们提供了一个在不同进程间进行数据传递的简单而有效的途径。了解如何正确使用FIFO,避免常见的问题,比如权限设定和缓冲区管理。虽然有其复杂性,但是只要掌握了这些要点,我们就能够用好FIFO,为进程间的通信带来便利。