
进程有独立地址空间不能直接访问对方内存IPC 就是实现不同进程之间数据交换、同步协作的机制。IPC 分类传统 IPC管道、命名管道、信号System V IPC共享内存、消息队列、信号量POSIX IPCposix 消息队列、posix 信号量、posix 共享内存Socket支持本机 跨网络进程通信进程通信的原理:进程空间 相互独立 每个进程拥有独立的4G内存空间但是内核空间共享所有进程间通信方式本质都是借助操作系统内核来完成的匿名管道 pipepipe()系统调用内核在内核缓冲区开辟一块环形缓冲区内存不在磁盘。创建两个文件描述符fd[0]读端、fd[1]写端这两个 fd 属于进程。fork()之后子进程复制文件描述符父子进程指向同一个内核缓冲区。只有文件描述符只能用于有血缘关系进程通信父子、兄弟半双工一端读、一端写。int pipe(int pipefd[2]); pipefd --- 用来存放管道两端的文件描述符 pipefd[0] ---管道读端 pipefd[1] ---管道写端 返回值: 成功 0 失败 -1 errno被设置管道的大小为64KB字节也是65536个字节。管道读写规则 读 端 写 端 存在 存在 ---写管道如果管道为空此时可以写但是管道满写操作阻塞 存在 存在 ---读管道如果管道中有数据则读取没有数据此时读操作会阻塞 存在 不存在 ---读管道如果管道中有数据则读取没有数据此时读操作直接返回 不存在 存在 ---写管道管道破裂练习创建一个子进程实现父子进程间的任意信息pipe(fd)创建无名管道实现父子进程通信fork()创建子进程父子进程各自持有管道两个 fd父进程关闭读端读取键盘输入把数据写到管道输入quit就关闭写端等待子进程结束子进程关闭写端循环读管道数据打印到屏幕收到quit直接退出注意管道是半双工父子必须关闭自己不用的那一端。#includestdio.h #includeunistd.h #includestring.h #include sys/types.h #include sys/wait.h int main(int argc, const char *argv[]) { int fd[2]; // 管道文件描述符数组fd[0]读端fd[1]写端 // 创建无名管道成功返回0失败返回-1 if(pipe(fd) 0) { perror(pipe fail); // 打印管道创建失败原因 return -1; } // 打印管道读写两端的文件描述符 printf(pipe success\nfd[0]%d\tfd[1]%d\n,fd[0],fd[1]); // 创建子进程父进程返回子进程pid子进程返回0出错返回-1 pid_t pid fork(); if(pid -1) { perror(fork fail); // 创建子进程失败打印错误信息 return -1; } // 父进程分支 if(pid 0) { close(fd[0]); // 父进程只负责写管道关闭管道读端 while(1) // 循环读取键盘输入写入管道 { char buf[20] {0}; // 从标准输入(键盘文件描述符0)读取用户输入 int ret read(0,buf,sizeof(buf)); // 将读到的数据写入管道写端传给子进程 write(fd[1],buf,ret); // 判断输入是否为quit退出程序 if(strncmp(buf,quit,4) 0) { close(fd[1]); // 关闭管道写端 wait(NULL); // 阻塞等待回收子进程资源防止僵尸进程 return 0; } } } // 子进程分支 if(pid 0) { close(fd[1]); // 子进程只负责读管道关闭管道写端 while(1) // 循环从管道读取父进程发来的数据 { char buf[20] {0}; // 从管道读端读取父进程发送的数据 int ret read(fd[0],buf,sizeof(buf)); // 如果读到quit子进程关闭读端退出 if(strncmp(buf,quit,4) 0) { close(fd[0]); return 0; } // 将读到的数据输出到标准输出(屏幕文件描述符1) write(1,buf,ret); } } return 0; }练习父子进程的双向通信有名管道 FIFO 半双工一根管道只能单向传输双向通信必须使用两根 FIFOa2bA 写 → B 读b2aB 写 → A 读每个程序内部fork()父子进程分工一个负责键盘输入写管道另一个阻塞读管道接收对方消息。mkfifo重复创建会报错判断errno ! EEXIST避免管道已存在时报错。unlink()删除磁盘上的管道文件避免残留。a进程代码#includestdio.h #includeunistd.h #includestring.h #include sys/types.h #include sys/wait.h #include sys/stat.h #includestdlib.h #includeerrno.h #includefcntl.h int main(int argc, const char *argv[]) { // 创建有名管道a2ba写b读存在则忽略报错 if(mkfifo(a2b,0666) 0 errno ! EEXIST) { perror(mkfifo fail); return -1; } // 创建有名管道b2ab写a读 if(mkfifo(b2a,0666) 0 errno ! EEXIST) { perror(mkfifo fail); return -1; } printf(mkfifo success\n); // fd_w打开a2b管道只写a向b发送消息 int fd_w open(a2b,O_WRONLY); if(fd_w 0) { perror(open a2b fail); unlink(a2b); unlink(b2a); return -1; } // fd_r打开b2a管道只读a读取b发来的消息 int fd_r open(b2a,O_RDONLY); if(fd_r 0) { perror(open b2a fail); unlink(a2b); unlink(b2a); return -1; } // fork创建子进程父子分工一个写、一个读 pid_t pid fork(); if(pid 0) { perror(fork fail); close(fd_w); close(fd_r); unlink(a2b); unlink(b2a); return -1; } if(pid 0) { // 父进程键盘输入写入a2b管道发给b进程 while(1) { printf(a to b:); char buf[20] {0}; int ret read(0,buf,sizeof(buf)); // 读取键盘输入 write(fd_w,buf,ret); // 写入a2b管道 if(strncmp(buf,quit,4) 0) // quit退出 break; } } if(pid 0) { // 子进程读取b2a管道接收b进程发来的数据 while(1) { char buf[20] {0}; int ret read(fd_r,buf,sizeof(buf)); // 读b2a管道 if(strncmp(buf,quit,4) 0) // 收到quit结束 break; printf(b to a:%s\n,buf); // 打印b发送的内容 } } // 关闭fd删除有名管道文件 close(fd_w); close(fd_r); unlink(a2b); unlink(b2a); return 0; }b进程代码#includestdio.h #includeunistd.h #includestring.h #include sys/types.h #include sys/wait.h #include sys/stat.h #includestdlib.h #includeerrno.h #includefcntl.h int main(int argc, const char *argv[]) { // 创建有名管道a2ba进程写b进程读EEXIST代表管道已存在不报错 if(mkfifo(a2b,0666) 0 errno ! EEXIST) { perror(mkfifo fail); return -1; } // 创建有名管道b2ab进程写a进程读实现双向通信需要两根FIFO if(mkfifo(b2a,0666) 0 errno ! EEXIST) { perror(mkfifo fail); return -1; } printf(mkfifo success\n); // fd_w打开a2b管道只读b进程从a2b读取a进程发来的数据 int fd_w open(a2b,O_RDONLY); //打开从读取的FIFO if(fd_w 0) { perror(open fail); unlink(a2b); // 删除管道文件 unlink(b2a); return -1; } // fd_r打开b2a管道只写b进程往b2a写入数据发给a进程 int fd_r open(b2a,O_WRONLY); //打开向写入的FIFO if(fd_r 0) { perror(open fail); unlink(a2b); unlink(b2a); return -1; } // 创建子进程一个进程负责读管道一个负责键盘输入写管道 pid_t pid fork(); if(pid 0) { perror(fork fail); close(fd_w); close(fd_r); unlink(a2b); unlink(b2a); return -1; } if(pid 0) { // 父进程负责读a2b管道接收a进程发送过来的消息 while(1) { char buf[20] {0}; int ret read(fd_w,buf,sizeof(buf)); // 从a2b管道读取a发来数据 if(strncmp(buf,quit,4) 0) // 读到quit跳出循环结束通信 break; printf(a to b:%s\n,buf); // 打印a进程发来的内容 } } if(pid 0) { // 子进程读取键盘输入通过b2a管道发送给a进程 while(1) { char buf[20] {0}; printf(b to a:); int ret read(0,buf,sizeof(buf)); // 读取键盘输入 write(fd_r,buf,ret); // 将输入写入b2a管道发给a if(strncmp(buf,quit,4) 0) // 输入quit退出循环 break; } } // 关闭管道文件描述符删除磁盘上的FIFO管道文件 close(fd_w); close(fd_r); unlink(a2b); unlink(b2a); return 0; }有名管道FIFO有名管道 FIFO文件系统中存在对应的管道文件内核缓冲区和匿名管道一致区别在于拥有文件名、可被任意进程打开支持无亲缘关系进程间通信。可以使用open、read、write、close等命令进行打开关闭、读写操作但是不能使用lseek进行定位有名管道的创建int mkfifo(const char *pathname, mode_t mode); 参数: pathname 管道文件名 mode 文件权限 权限与open时加入了creat后输入的权限参数方式相同 返回值: 成功 0 失败 -1 errno被设置有名管道进行通信时需要进行打开操作 open如果只有一端 以 只读或只写的方式打开 此时程序会阻塞在open操作必须等待 另一端 以只写或只读的相对方式打开阻塞才会解除此时也表示有名管道文件 读端和写端都打开了练习创建有名管道实现两个进程之间的单向通信一个进程从终端读取数据通过管道发送到另一个进程另一个进程接收数据并打印出来。终端 A 运行 ./fifo_write终端 B 运行 ./fifo_read 两端都打印 mkfifo success然后都卡在 open直到两端都启动在写端A键盘输入hello回车写端 read (0) 获取 hellowrite 写入管道读端 read 拿到 hello打印read data:hello在写端输入quit回车写端将 quit 写入管道检测到 quitbreakclose (fd)程序结束读端读到 quitbreakunlink 删除 fifo 文件close (fd)程序结束