IPC 进程间通信
IPC(Interprocess Communication,进程间通信)是指在不同进程之间传播或交换信息的一种机制。它允许多个进程在同一操作系统中协同工作,实现数据的共享和同步。IPC主要包括三大类通信方式:古老的通信方式、IPC对象通信以及socket通信。
1. 古老的通信方式
- 无名管道(PIPE):
- 是UNIX系统IPC最古老的形式,半双工,即数据只能在一个方向上流动。
- 它具有固定的读端和写端,通常用于父子进程或兄弟进程之间的通信。
- 可以看成是一种特殊的文件,对其读写使用普通的read、write函数,但它只存在于内存中,不属于任何文件系统。
- 有名管道(Named PIPE):
- 也称为FIFO(First In First Out),它克服了无名管道只能在亲缘关系进程间通信的限制。
- 有名管道以文件形式存在于文件系统中,有路径名与之关联,可以在无关的进程之间交换数据。
- 信号(Signal):
- 是Unix系统中使用的最古老的进程间通信方法之一。
- 它是异步的,用于通知进程发生了某种事件。
- 操作系统通过信号来通知进程系统中发生了某种预先规定好的事件,也是用户进程之间通信和同步的一种原始机制。
2. IPC对象通信
- 消息队列(Message Queue):
- 消息队列提供了一种从一个进程向另一个进程发送数据块的方法。
- 每个数据块都有一个类型,接收者进程可以根据类型选择性地接收数据。
- 消息队列存在于内核中,由消息队列标识符来标识。
- 共享内存(Shared Memory):
- 允许两个或多个不相关的进程访问同一块内存区域。
- 它是所有IPC方式中速度最快的一种,因为数据不需要在客户机和服务器之间复制。
- 通常需要使用某种形式的同步机制(如信号量)来避免竞态条件。
- 信号量集(Semaphore Set):
- 主要用于控制多个进程对共享资源的访问。
- 它是一种计数器,用于协调多个进程之间的同步。
- 信号量可以被设置为正数或零,表示可用资源的数量。
3. Socket通信
- 网络通信:
- Socket是一种特殊的文件描述符,用于表示两个进程间的网络连接(包括TCP和UDP协议)。
- 它不仅可以在同一台机器上的不同进程间通信,还可以跨网络进行通信。
- Socket通信包括创建socket、绑定地址和端口、监听连接、接受连接、数据收发以及关闭连接等步骤。
管道
管道的特性:
1、管道是 半双工的工作模式(但我们当作单工来使用)
2、所有的管道都是特殊的文件不支持定位操作。lseek->> fd fseek ->>FILE*
3、管道是特殊文件,读写使用文件IO。fgets,fread,fgetc,
open,read,write,close;
1,读端存在,一直向管道中去写,超过64k,写会阻塞。
2,写端是存在的,读管道,如果管道为空的话,读会阻塞。
3.管道破裂:读端关闭,还继续写管道。
4. read 0 :写端关闭,如果管道没有内容,读到0即:read 0 ;代表通信结束
使用框架:
创建管道 ==》读写管道 ==》关闭管道
无名管道
1、无名管道 ===》管道的特例 ===>pipe函数
特性:
1.1 亲缘关系进程使用
1.2 有固定的读写端
流程:
创建并打开管道: pipe函数
#include <unistd.h> 头文件
int pipe(int pipefd[2]);
功能:创建并打开一个无名管道
参数:pipefd[0] ==>无名管道的固定读端
pipefd[1] ==>无名管道的固定写端
返回值:成功 0
失败 -1;
注意事项:
1、无名管道的架设应该在fork之前进行。
无名管道的读写:===》文件IO的读写方式。
读: read()
写: write()
关闭管道: close();pipe
函数的参数是一个整型数组(通常是两个元素的数组),用于存储由系统分配的两个文件描述符。如果调用成功,pipe
函数会将这两个文件描述符存储在提供的数组中,并将数组的第一个元素(pipefd[0]
)设置为管道的读端,将数组的第二个元素(pipefd[1]
)设置为管道的写端。然后,pipe
函数返回0以表示成功。如果调用失败,则返回-1,并设置全局变量errno
以指示错误原因。
验证: read 0 :写端关闭,如果管道没有内容,读到0即:read 0 ;代表通信结束
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{int pipefd[2]={0};int ret = pipe(pipefd);if(-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if(pid>0){//需要决定读写方向close(pipefd[0]);sleep(3);char buf[50]="hello,child";write(pipefd[1],buf,strlen(buf));}else if(0 == pid){close(pipefd[1]);while(1){char buf[50]={0};int rd_ret = read(pipefd[0],buf,sizeof(buf)); //后台进程if(rd_ret<=0){break;}printf("father :%s\n",buf);}printf("ipc end\n");}else {perror("fork");return 1;}return 0;
}
这段代码中只创建了一个管道。管道是通过pipe()
函数创建的,它返回一个包含两个文件描述符的数组:pipefd[0]
和pipefd[1]
。这两个文件描述符分别代表管道的读端和写端。
pipefd[0]
:管道的读端,用于读取数据。pipefd[1]
:管道的写端,用于向管道写入数据。
在代码中,fork()
调用创建了一个新的子进程。子进程和父进程都会继承这个管道的文件描述符。但是,根据fork()
的返回值,父进程和子进程分别关闭了它们不需要的管道端:
- 父进程(
pid > 0
):关闭了读端pipefd[0]
,保留了写端pipefd[1]
,用于向管道写入数据。 - 子进程(
pid == 0
):关闭了写端pipefd[1]
,保留了读端pipefd[0]
,用于从管道读取数据。
父进程在等待3秒后向管道写入字符串"hello,child",然后子进程不断地从管道读取数据,直到没有更多的数据可读。
因此,虽然父进程和子进程都有管道的文件描述符,但它们操作的是同一个管道的两端。整个程序中只存在一个管道,而不是两个独立的管道。
验证如下问题:
1、父子进程是否都有fd[0] fd[1],
如果在单一进程中写fd[1]能否直接从fd[0]中读到。
可以,写fd[1]可以从fd[0]读
2、管道的数据存储方式是什么样的
数据是否一直保留?
栈, 先进后出
队列形式存储 读数据会剪切取走数据不会保留
先进先出
3、管道的数据容量是多少,有没有上限值。
操作系统的建议值: 512* 8 = 4k
代码测试实际值: 65536byte= 64k
4、管道的同步效果如何验证?读写同步验证。
读端关闭能不能写? 不可以 ===>SIGPIPE 异常终止
写端关闭能不能读? 可以,取决于pipe有没有内容,===>read返回值为0 不阻塞
结论:读写端必须同时存在,才能进行
管道的读写。
5、固定的读写端是否就不能互换?
能否写fd[0] 能否读fd[1]? 不可以,是固定读写端。
练习:在父子进程间传输文件内容。父进程读取/home/linux/1.png
文件的内容,并通过管道发送给子进程。子进程则接收这些内容,并将其写入到2.png
文件中。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>int main(int argc, char *argv[])
{int pipefd[2]={0};int ret = pipe(pipefd);if(-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if(pid>0){close(pipefd[0]);int fd = open("/home/linux/1.png",O_RDONLY);if(-1 == fd){perror("father open");exit(1);}while(1){char buf[4096]={0};int rd_ret = read(fd,buf,sizeof(buf));if(rd_ret<=0){break;}write(pipefd[1],buf,rd_ret);}close(fd); close(pipefd[1]);}else if(0 == pid){close(pipefd[1]);int fd = open("2.png",O_WRONLY|O_TRUNC|O_CREAT,0666);if(-1 == fd){perror("child open");exit(1);}while(1) {char buf[4096]={0};int rd_ret = read(pipefd[0],buf,sizeof(buf));if(rd_ret<=0){break;}write(fd,buf,rd_ret);}close(fd);close(pipefd[0]);}else {perror("fork");return 1;}return 0;
}
有名管道
有名管道===》fifo ==》有文件名称的管道。
文件系统中可见
框架:
创建有名管道 ==》打开有名管道 ==》读写管道
==》关闭管道 ==》卸载有名管道
1、创建:mkfifo
#include <sys/types.h>
#include <sys/stat.h>
remove();
int mkfifo(const char *pathname, mode_t mode);
功能:在指定的pathname路径+名称下创建一个权限为
mode的有名管道文件。
参数:pathname要创建的有名管道路径+名称
mode 8进制文件权限。
返回值:成功 0
失败 -1;
2、打开有名管道 open
注意:该函数使用的时候要注意打开方式,
因为管道是半双工模式,所有打开方式直接决定
当前进程的读写方式。
一般只有如下方式:
int fd-read = open("./fifo",O_RDONLY); ==>fd 是固定读端
int fd-write = open("./fifo",O_WRONLY); ==>fd 是固定写端
不能是 O_RDWR 方式打开文件。
不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数
3、管道的读写: 文件IO
读: read(fd-read,buff,sizeof(buff));
写: write(fd-write,buff,sizeof(buff));
4、关闭管道:
close(fd);
5、卸载管道:remove();
int unlink(const char *pathname);
功能:将指定的pathname管道文件卸载,同时
从文件系统中删除。
参数: ptahtname 要卸载的有名管道
返回值:成功 0
失败 -1;
练习:1.实现fifo a,b 进程 实时聊天。可以连续收发。#quit,双方都退出。
写端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>void* th1(void* arg)
{int fd =*(int*)arg; while(1) {printf("to B:");char buf[128]={0};fgets(buf,sizeof(buf),stdin);//#quitif(0 == strcmp(buf,"#quit\n")){exit(0);}write(fd,buf,strlen(buf));}return NULL;}
void* th2(void* arg)
{int fd =*(int*)arg; while(1) {char buf[128]={0};int ret = read(fd,buf,sizeof(buf));if(0==strcmp(buf,"#quit\n") || ret<=0){exit(0);}printf("from B:%s",buf);fflush(stdout);}return NULL;}int main(int argc, char *argv[])
{int ret = mkfifo("myfifo1",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}ret = mkfifo("myfifo2",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}int fd_w = open("myfifo1",O_WRONLY);if(-1 == fd_w){perror("open");return 1;}int fd_r = open("myfifo2",O_RDONLY);if(-1 == fd_r){perror("open");return 1;}pthread_t tid1,tid2;pthread_create(&tid1,NULL,th1,&fd_w);pthread_create(&tid2,NULL,th2,&fd_r);pthread_join(tid1,NULL);pthread_join(tid2,NULL);return 0;
}
读端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>void* th1(void* arg)
{int fd =*(int*)arg; while(1) {printf("to A:");char buf[128]={0};fgets(buf,sizeof(buf),stdin);//#quitif(0 == strcmp(buf,"#quit\n")){exit(0);}write(fd,buf,strlen(buf));}return NULL;}
void* th2(void* arg)
{int fd =*(int*)arg; while(1) {char buf[128]={0};int ret = read(fd,buf,sizeof(buf));if(0==strcmp(buf,"#quit\n") || ret<=0){exit(0);}printf("from A:%s",buf);fflush(stdout);}return NULL;}int main(int argc, char *argv[])
{int ret = mkfifo("myfifo1",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}ret = mkfifo("myfifo2",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}int fd_r = open("myfifo1",O_RDONLY);if(-1 == fd_r){perror("open");return 1;}int fd_w = open("myfifo2",O_WRONLY);if(-1 == fd_w){perror("open");return 1;}pthread_t tid1,tid2;pthread_create(&tid1,NULL,th1,&fd_w);pthread_create(&tid2,NULL,th2,&fd_r);pthread_join(tid1,NULL);pthread_join(tid2,NULL);return 0;
}
多个有名管道,开的顺序要一致;
练习2:实现pipe实现查字典。 1.可以循环查找 2.找到显示意思,没找到显示没找到 3.#quit 都退出。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define MAX 19662
int main(int argc, char *argv[])
{int pipefd[2]={0};int ret = pipe(pipefd);if(-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if(pid>0){close(pipefd[0]);int fd = open("/home/linux/dict.txt",O_RDONLY);if(-1 == fd){perror("open");exit(1);}while(1){char buf[4096]={0};int rd_ret = read(fd,buf,sizeof(buf));if(rd_ret==0){lseek(fd,0,SEEK_SET);continue;}write(pipefd[1],buf,rd_ret);}}else if(0 == pid){close(pipefd[1]);FILE*fp = fdopen(pipefd[0],"r");while(1){char want_word[50]={0};printf("want word:");fgets(want_word,sizeof(want_word),stdin);want_word[strlen(want_word)-1]='\0';if(0 == strcmp(want_word,"#quit")){break;}int num = 0;while(1){char linebuf[512]={0};fgets(linebuf,sizeof(linebuf),fp);char* arg[2]={NULL};arg[0]=strtok(linebuf," ");arg[1]=strtok(NULL,"\r");if(0 == strcmp(want_word,arg[0])){printf("%s:%s\n",arg[0],arg[1]);break;}num++;if(num>=MAX){printf("cant find %s\n",want_word);break;}}}}else {perror("fork");return 1;}return 0;
}
代码中用管道破裂使父进程结束