目录 0.进程间通信? 1.进程间通信目的 2.进程间通信分类 3.进程间通信的本质理解 1.什么是管道? 2.匿名管道 1.认识函数 2.如何让不同的进程,看到同一份资源? 3.用fork来共享管道原理 4.站在文件描述符角度 -- 深刻理解管道 5.站在内核角度 -- 管道本质 6.管道读写规则 7.管道特点 8.使用 3.命名管道 0.为什么有命名管道? 1.创建一个命名管道 2.命名管道原理 3.匿名管道与命名管道的区别 4.命名管道的打开规则 5.思考问题 6.使用 -- 极其简化的代码,只供了解使用流程
0.进程间通信?
1.进程间通信目的
数据传输 :一个进程需要将它的数据发送给另一个进程资源共享 :多个进程之间共享同样的资源通知事件 :一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止 时要通知父进程)进程控制 :有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变
2.进程间通信分类
管道:–> Linux原生能提供 System V IPC:–> 多进程 --> 单机通信 System V 消息队列(不常用) System V 共享内存 System V 信号量(不讨论 - 原理) POSIX IPC:–> 多线程 --> 网络通信
3.进程间通信的本质理解
进程间通信的前提:让不同的进程看到同一块"内存" (特定的结构组织的)所谓的进程看到同一块"内存",属于哪一个进程?
1.什么是管道?
从一个进程连接到另一个进程的一个数据流称为一个"管道" 有入口,有出口 管道都是单向传输内容的 管道中传输的都是"资源" 管道通信背后是进程之间通过管道进行通信
2.匿名管道
1.认识函数
功能 创建一匿名管道 函数原型 int pipe(int fd[2]); 参数:fd 文件描述符数组,其中fd[0]表示读端,fd[1]表示写端 返回值 成功返回0,失败返回错误代码
2.如何让不同的进程,看到同一份资源?
分别以读写方式打开同一个文件 fork()创建子进程 双方进程各自关闭自己不需要的文件描述符 以上能够让具有血缘关系的进程进行进程间通信 --> 常用于父子进程
3.用fork来共享管道原理
4.站在文件描述符角度 – 深刻理解管道
5.站在内核角度 – 管道本质
看待管道,就如同看待文件一样!管道的使用和文件一致
6.管道读写规则
当没有数据可读时: O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止 O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN 当管道满的时候: O_NONBLOCK disable: write调用阻塞,直到有进程读走数据 O_NONBLOCK enable:调用返回-1,errno值为EAGAIN 如果所有管道写端 对应的文件描述符被关闭,则read返回0 如果所有管道读端 对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出 当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性 当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性 所以有如下情况: 写快,读慢,写满不能再写了 写慢,读快,管道没有数据的时候,读必须等待 写关,读0,标识读到了文件结尾 读关,写继续写,OS终止写进程
7.管道特点
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信 通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道 内核会对管道操作进行**访问控制(**同步与互斥) 管道提供面向流式 的通信服务 – 面向字节流 管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的 管道是单向通信的 --> 半双工通信的一种特殊情况 管道是半双工的,数据只能向一个方向流动 需要双方通信时,需要建立起两个管道
8.使用
int main ( )
{ int pipefd[ 2 ] = { 0 } ; int n = pipe ( pipefd) ; pid_t id = fork ( ) ; if ( id == 0 ) { close ( pipefd[ 1 ] ) ; char buffer[ 1024 ] = { 0 } ; while ( true ) { ssize_t s = read ( pipefd[ 0 ] , buffer, sizeof ( buffer) - 1 ) ; if ( s > 0 ) { cout << "child get a message[" << getpid ( ) << "] Parent# " << buffer << endl; } else if ( s == 0 ) { cout << "writer quit, follow him :P" << endl; break ; } } exit ( 1 ) ; } close ( pipefd[ 0 ] ) ; string msg = "我是父进程,我正在发消息" ; int count = 0 ; char send_buffer[ 1024 ] ; while ( true ) { snprintf ( send_buffer, sizeof ( send_buffer) , "%s[%d]:%d" , msg. c_str ( ) , getpid ( ) , count++ ) ; write ( pipefd[ 1 ] , send_buffer, strlen ( send_buffer) ) ; sleep ( 1 ) ; if ( count == 5 ) { cout << "writer quie :P" << endl; break ; } } close ( pipefd[ 1 ] ) ; pid_t ret = waitpid ( id, nullptr , 0 ) ; return 0 ;
}
3.命名管道
0.为什么有命名管道?
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信 如果想在不相关的进程之间交换数据 ,可以使用FIFO文件 来做这项工作,它经常被称为命名管道 命名管道是一种特殊类型的文件
1.创建一个命名管道
命名管道可以从命令行上创建 ,命令: $ mkfifo filename
命名管道也可以从程序里创建,函数: int mkfifo(const char *filename, mode_t mode);
2.命名管道原理
如何让不同的进程看到同一份资源? 在磁盘中创建一个文件,不同的进程同时打开它 此文件可以被打开,但是不会将内存数据进行刷新到磁盘
3.匿名管道与命名管道的区别
匿名管道由pipe 函数创建并打开 命名管道由mkfifo 函数创建,打开用open FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同 ,一但这些工作完成之后,它们具有相同的语义
4.命名管道的打开规则
如果当前打开操作是为读 而打开FIFO时: O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO O_NONBLOCK enable:立刻返回成功 如果当前打开操作是为写 而打开FIFO时: O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
5.思考问题
多个进程在通过管道通信时,删除管道文件则无法继续通信? 错误 管道的生命周期随进程,本质是内核中的缓冲区 命名管道文件只是标识,用于让多个进程找到同一块缓冲区,删除后,之前已经打开管道的进程依然可以通信
6.使用 – 极其简化的代码,只供了解使用流程
# define MODE 0666
# define SIZE 128
string ipcPath = "./fifo.ipc" ;
void getMessage ( int fd)
{ char buffer[ SIZE] = { 0 } ; while ( true ) { ssize_t s = read ( fd, buffer, sizeof ( buffer) - 1 ) ; if ( s > 0 ) { cout << "[" << getpid ( ) << "] " << "client say " << buffer << endl; } else if ( s == 0 ) { cerr << "[" << getpid ( ) << "] " << "read end of file, client quit, server follow it :P" << endl; break ; } else { perror ( "read" ) ; break ; } }
} int main ( )
{ if ( mkfifo ( ipcPath. c_str ( ) , MODE) < 0 ) { perror ( "mkfifo" ) ; exit ( 1 ) ; } int fd = open ( ipcPath. c_str ( ) , O_RDONLY) ; int nums = 3 ; for ( int i = 0 ; i < nums; i++ ) { pid_t id = fork ( ) ; if ( id == 0 ) { getMessage ( fd) ; exit ( 1 ) ; } } for ( int i = 0 ; i < nums; i++ ) { waitpid ( - 1 , nullptr , 0 ) ; } close ( fd) ; unlink ( ipcPath. c_str ( ) ) ; return 0 ;
}
int main ( )
{ int fd = open ( ipcPath. c_str ( ) , O_WRONLY) ; string buffer; while ( true ) { cout << "Please Enter Message Line:> " ; std:: getline ( std:: cin, buffer) ; write ( fd, buffer. c_str ( ) , buffer. size ( ) ) ; } close ( fd) ; return 0 ;
}