IPC机制 实现进程间通信
在多个进程间传输数据或共享信息的机制。
数据交换,共享资源,进程同步,消息传递。
IPC实现原理:通信进程能够访问相同的内存区域。
方法:
管道:无名管道pipe、命名管道FIFO
System V IPC:消息队列、信号量、共享内存
POSIX IPC :消息队列、信号量、共享内存
套接字:socket
一、无名管道pipe
在有父子关系或者有亲缘关系的进程之间通信
字节流通信,数据格式由用户定义/解析
单向通信(半双工模式),数据单向流动(在管道中同时读写会发生错误)
1.创建无名管道 pipe
两种形式
cpp
int pipe( int fd[2] ) ;
int pipe( int fd[2] , int flags);
int fd[2]:整型数组,存储管道的文件描述符;fd[0] 读文件描述符,fd[1] 写文件描述符
int flags: 设置管道标志的参数,0或以下参数的按位和:
O_NONBLOCK 非阻塞 读管道或写管道时不会阻塞管道
O_CLOEXEC 设置当在execve系统调用执行时关闭管道
返回值:
成功 返回 0
失败 返回 -1,并设置errno
2.写管道write
cpp
#define TEST_STRING "123456789"
void write( fd[1] , TEST_STRING , sizeof(TEST_STRING));
写端fd[1],将TEST_STRING代表的字符写入管道,长度为 sizeof(TEST_STRING)
3.读管道read
cpp
char rbuf[100]={0};
void read( fd[0] , rbuf , 100);
读端fd[0] , 将管道中的数据读到rbuf中,每次读100个字符长度
4.关闭管道 close
关闭读端
cpp
close(fd[0]);
关闭写端
cpp
close(fd[1]);
5.实现父子进程间的半双工通信
以下是父进程和子进程通信,父进程写,子进程读:

无名管道一般跟fork()函数搭配使用。
第一步,调用pipe函数,生成pipeinnode文件节点,生成管道。
第二步,调用fork函数,父进程创建子进程,同时父进程中的文件表也会拷贝到子进程,此时子进程也可以通过fd4,fd5文件描述符访问管道。
第三步,关闭父进程的读端(fd[0]),关闭子进程的写端(fd[1])。
此时父子进程就可以通过管道进行通信。
PS:pipe一定要在fork之前
创建pipe_test文件
bash
vi pipe_test.c
pipe_test文件代码实现
cpp
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int fd[2]={};//存储pipe的文件描述符
void pipe_test()
{
pid_t pid=fork();//创建子进程
if(pid==0)//子进程
{
printf("children\n");
close(fd[1]);//子进程关闭写端
while(1)
{//读数据
char rbuf[100]={0};
read(fd[0],rbuf,100);
printf("rbuf:%s\n",rbuf);
}
close(fd[0]);//读完数据关闭读端
}
else if(pid>0)//父进程
{
printf("parent\n");
close(fd[0]);//父进程关闭读端
#define TEST_STRING "1234567890"
while(1)
{
write(fd[1],TEST_STRING,sizeof(TEST_STRING));
sleep(1);
}
close(fd[1]);
}
}
else
{}
}
int main(int argc,char * argv[])
{
int ret=pipe(fd);//创建管道
if(ret==-1)//创建管道失败
{
perror("pipe error");//设置errno
return -1;
}
pipe_test();//调用函数
return 0;
}
退出 分别按下按键 Esc : w q
编译 pipe_test文件
bash
gcc -o pipe_test pipe_test.c
运行
bash
./pipe_test
结果
6.实现父子进程间的全双工通信
如图,父进程和子进程之间能够交换数据,双方都可以向对方写数据和读数据。

第一步,父进程调用两次pipe,创建两个管道。
第二步,父进程调用fork,创建子进程,此时子进程有和父进程相同的文件表,也有两个管道的文件描述符。
第三步,父进程只保留 管道1的读端 和 管道2的写端,
子进程只保留管道1的写端 和 管道2的读端。
二、命名管道FIFO
无名管道只能在具有亲缘关系的进程之间传递数据,如果想要在不相关的进程之间传递数据,可以使用 FIFO文件 即 "命名管道" (通常也被称为 "有名管道")完成这项任务。
命名管道是一种特殊类型的文件(别忘了Linux中所有事物都是文件),它在文件系统中以文件名形式存在。文件标识为"p"。
【Linux其他文件类型可查看博客 【Linux】Linux中七种文件类型-CSDN博客】
1.创建FIFO文件
创建FIFO文件 可以在命令行上创建,也可以在程序中创建
(1)在命令行中创建命名管道
在命令行中创建管道也有两种方式
① mkfifo filename
bash
mkfifo filename
filename 是FIFO文件的文件名
② mknod filename p
bash
mknod filename p
filename 是FIFO文件的文件名
p表示创建的是管道文件
(2)在程序中创建命名管道
在程序中也有两个函数可以创建命名管道
注意添加头文件
cpp
#include <sys/types.h>
#include <sys/stat.h>
① int mkfifo(filename , mode)
bash
int mkfifo (const char * filename , mode_t mode) ;
const char * filename :表示FIFO文件的文件名
mode_t mode :表示该FIFO文件的文件权限,如可读、可写、可执行
命名管道的权限由 mkfifo
的 mode
参数和 umask
共同决定。
如 mkfifo(fifo_path, 0666); // 实际权限为 0666 & ~umask
② int mknod(filename , mode | S_IFIFO , 0)
对于这个函数,作者也不太懂,只是看到书上提到了,所以也在这里提出来。对于一般要求可以不掌握。
bash
int mknod (const char * filename , mode_t mode | S_IFIFO , (dev_t) 0) ;
举例:
创建 fifo_test.c文件
bash
vi fifo_test.c
fifo_test.c文件
cpp
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
int fifo_test()
{
const char* fifo_path="./myfifo";
int ret=mkfifo(fifo_path,0777);
if(ret<0)
{
printf("create myfifo failure\n");
}
printf("create myfifo sucessed\n");
return 0;
}
int main(int argc,char * argv[])
{
fifo_test();
return 0;
}
退出 分别按下按键 Esc : w q
编译 fifo_test文件
bash
gcc -o fifo_test fifo_test.c
运行
bash
./fifo_test
结果

(在后面的文章中,作者将不再赘叙关于编译运行文件的命令)
2.打开FIFO文件 open
打开FIFO的一个限制是,程序不能以O_RDWR模式(即可读可写模式)打开FIFO文件进行读写操作,这样做的后果未明确定义。但是这个限制是有道理的,如果一个管道以读/写方式打开,那么进程就会从这个管道中读取到它自己写入的数据。
通常使用FIFO文件是为了单向传递数据,要实现全双工模式,跟上文提到的无名管道一样,可以使用一对FIFO文件(即两个FIFO文件)。
cpp
int open( const char * filepath , int oflags) ;
const char * filepath : FIFO文件的路径名
int oflags : 有O_RDONLY ,O_WRONLY ,O_NONBLOCK 三种参数可选可组合(O_RDWR已被排除),共有四种组合方式:
O_RDONLY : 只读模式。这种情况下open调用将阻塞,除非有一个进程以写的方式打开同一个FIFO,否则将不会返回。
O_RDONLY | O_NONBLOCK :非阻塞读。即便没有其他进程以写的方式调用FIFO,这个open调用也会成功并立刻返回
O_WRONLY : 只写模式。这种情况下open调用将阻塞,除非有一个进程以读的方式打开同一个FIFO,否则将不会返回。
**O_WRONLY | O_NONBLOCK :非阻塞写。**如果没有其他进程以读的方式调用FIFO,这个open调用将返回错误码-1,并且FIFO也不会打开。如果有一个进程以读的方式打开FIFO文件,就可以通过它的返回的文件描述符对这个FIFO文件进行写操作。
注意:O_NONBLOCK搭配 O_RDONLY和O_WRONLY的效果是不同的。
非阻塞读的open调用总是成功的。
如果没有进程以读的方式打开FIFO文件,那么非阻塞写的open调用将失败。
3.写FIFO文件 write
cpp
int pipe_fd = open(FIFO_NAME , open_mode) ; //pipe_fd 为FIFO文件的文件描述符
#define BUFFER_SIZE PIPE_BUF
char buffer[BUFFER_SZIE + 1] ;
write( pipe_fd , buffer , BUFFER_SIZE) ;
将 buffer 传入 文件描述符为pipe_fd的命名管道中 , 长度为BUFFER_SIZE。
成功返回0,失败返回-1。
系统对任意时刻在一个FIFO中可以存在的数据长度所有限制的。它由#define PIPE_BUF 语句定义,通常可以在头文件limits.h 中找到它。在Linux系统中,它的值一般为4096字节。
4.读FIFO文件 read
read函数的用法同write,就是在需要在另一个进程中使读函数。
bash
ret = read(pipe_fd , buffer , BUFFER_SIZE);
5.实现两进程之间的命名管道通信
(1)在命令行中之间使用FIFO通信
① 新建一个myfifo 文件
bash
mkfifo myfifo
② 打开一个终端,将FIFO文件中的内容输出
bash
cat < ./myfifo
③ 打开另一个终端,往FIFO文件中输入
bash
echo "hello world" > ./myfifo
④ 查看两进程

(2)在程序中使用FIFO通信
fifocom.c文件
cpp
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/wait.h>
void *writer(void *arg)
{
printf("ww : writer begin\n");
const char * fifo_path="./myfifo";
int fd = open(fifo_path , O_WRONLY);
if(fd==-1)
{
printf("ww: open fifo failure\n");
exit(-1);
}
char * s="hello from writer\n";
write(fd,s ,strlen(s));
close(fd);
printf("ww : close writer\n");
return NULL;
}
void *reader(void * arg)
{
printf("rr : reader begin\n");
const char * fifo_path="./myfifo";
int fd = open(fifo_path, O_RDONLY);
if(fd==-1)
{
printf("ww: open fifo failure\n");
exit(-1);
}
char buff[100];
read(fd, buff, sizeof(buff));
printf("rr recived: %s\n",buff);
close(fd);
printf("rr : close reader\n");
return NULL;
}
int main(int argc,char * argv[])
{
printf("everything is ok------\n");
pthread_t read_thread,write_thread;
pthread_create(&read_thread , NULL , reader ,NULL);
pthread_create(&write_thread , NULL , writer ,NULL);
pthread_join(read_thread ,NULL);
pthread_join(write_thread ,NULL);
wait(NULL);
return 0;
}
结果:
