Linux系统编程之进程间通信(管道)
进程间通信的常用方式及特征:
管道:简单
信号:开销小
mmap映射:非血缘关系进程间
socket(本地套接字):稳定
一、匿名管道 pipe
匿名管道:
实现原理:内核借助环形队列机制,使用内核缓冲区实现。
特质: 1. 伪文件
2. 管道中的数据只能一次读取。
3. 数据在管道中,只能单向流动。
局限性:1. 自己写,不能自己读。
2. 数据不可以反复读。
3. 半双工通信。
4. 血缘关系进程间可用。
pipe函数:创建,并打开管道。
int pipe(int fd[2]);
参数: fd[0]: 读端。
fd[1]: 写端。
返回值: 成功: 0
失败: -1 errno
管道的读写行为:
读管道:
1. 管道有数据,read返回实际读到的字节数。
2. 管道无数据:1)无写端,read返回0 (类似读到文件尾)
2)有写端,read阻塞等待。
写管道:
1. 无读端, 异常终止。 (SIGPIPE导致的)
2. 有读端: 1) 管道已满, 阻塞等待
2) 管道未满, 返回写出的字节个数。
1.1 一写一读
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int ret;
int fd[2];
pid_t pid;
char *str = "hello pipe\n";
char buf[1024];
ret = pipe(fd);
if (ret == -1)
sys_err("pipe error");
pid = fork();
if (pid > 0) {
close(fd[0]); // 关闭读段
sleep(3);
write(fd[1], str, strlen(str));
close(fd[1]);
} else if (pid == 0) {
close(fd[1]); // 子进程关闭写段
ret = read(fd[0], buf, sizeof(buf));
printf("child read ret = %d\n", ret);
write(STDOUT_FILENO, buf, ret);
close(fd[0]);
}
return 0;
}
1.2 多写一读
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
int fd[2], i, n;
char buf[1024];
int ret = pipe(fd);
if(ret == -1){
perror("pipe error");
exit(1);
}
for(i = 0; i < 2; i++){
if((pid = fork()) == 0)
break;
else if(pid == -1){
perror("pipe error");
exit(1);
}
}
if (i == 0) {
close(fd[0]);
write(fd[1], "1.hello\n", strlen("1.hello\n"));
} else if(i == 1) {
close(fd[0]);
write(fd[1], "2.world\n", strlen("2.world\n"));
} else {
close(fd[1]); //父进程关闭写端,留读端读取数据
sleep(1);
n = read(fd[0], buf, 1024); //从管道中读数据
write(STDOUT_FILENO, buf, n);
for(i = 0; i < 2; i++) //两个儿子wait两次
wait(NULL);
}
return 0;
}
1.3 兄弟进程实现 ls | wc -l
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int fd[2];
int ret, i;
pid_t pid;
ret = pipe(fd);
if (ret == -1) {
sys_err("pipe error");
}
for(i = 0; i < 2; i++) { // 表达式2 出口,仅限父进程使用
pid = fork();
if (pid == -1) {
sys_err("fork error");
}
if (pid == 0) // 子进程,出口
break;
}
if (i == 2) { // 父进程 . 不参与管道使用.
close(fd[0]); // 关闭管道的 读端/写端.
close(fd[1]);
wait(NULL); // 回收子进程
wait(NULL);
} else if (i == 0) { // xiong
close(fd[0]);
dup2(fd[1], STDOUT_FILENO); // 重定向stdout
execlp("ls", "ls", NULL);
sys_err("exclp ls error");
} else if (i == 1) { //弟弟
close(fd[1]);
dup2(fd[0], STDIN_FILENO); // 重定向 stdin
execlp("wc", "wc", "-l", NULL);
sys_err("exclp wc error");
}
return 0;
}
二、命名管道 fifo
fifo管道:可以用于无血缘关系的进程间通信。
命名管道: mkfifo
无血缘关系进程间通信:
读端,open fifo O_RDONLY
写端,open fifo O_WRONLY
2.1 写端
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
void sys_err(char *str)
{
perror(str);
exit(-1);
}
int main(int argc, char *argv[])
{
int fd, i;
char buf[4096];
if (argc < 2) {
printf("Enter like this: ./a.out fifoname\n");
return -1;
}
fd = open(argv[1], O_WRONLY); //打开管道文件
if (fd < 0)
sys_err("open");
i = 0;
while (1) {
sprintf(buf, "hello itcast %d\n", i++);
write(fd, buf, strlen(buf)); // 向管道写数据
sleep(1);
}
close(fd);
return 0;
}
2.2 读端
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
void sys_err(char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int fd, len;
char buf[4096];
if (argc < 2) {
printf("./a.out fifoname\n");
return -1;
}
//int fd = mkfifo("testfifo", 644);
//open(fd, ...);
fd = open(argv[1], O_RDONLY); // 打开管道文件
if (fd < 0)
sys_err("open");
while (1) {
len = read(fd, buf, sizeof(buf)); // 从管道的读端获取数据
write(STDOUT_FILENO, buf, len);
sleep(3); //多個读端时应增加睡眠秒数,放大效果.
}
close(fd);
return 0;
}