进程间通信
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
- 管道通常指无名管道,是UNIX系统IPC最古老的形式;
- FIFO,FIFO,是一种文件类型;
- 消息队列,是消息的链接表,存放在内核中;
- 信号量,是一个计数器;
- 共享内存;
匿名(无名)管道通信
匿名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,具有固定的读端和写端。而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read,write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且 只存在于内存中 。当父子进程退出后,管道就消失了。
// 需要的头文件
#include <unistd.h>
// 通过pipe()函数来创建匿名管道
// 返回值:成功返回0,失败返回-1
// fd参数返回两个文件描述符
// fd[0]指向管道的读端,fd[1]指向管道的写端
// fd[1]的输出是fd[0]的输入。
int pipe (int fd[2]);
- 父进程创建管道,得到两个⽂件描述符指向管道的两端
- 父进程fork出子进程,⼦进程也有两个⽂件描述符指向同⼀管道。
- 父进程关闭fd[0],子进程关闭fd[1],即⽗进程关闭管道读端,⼦进程关闭管道写端(因为管道只支持单向通信)。⽗进程可以往管道⾥写,⼦进程可以从管道⾥读,管道是⽤环形队列实现的,数据从写端流⼊从读端流出,这样就实现了进程间通信。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main()
{
int fd [2]; //两个文件描述符
int pid;
char buff [128];
if(pipe(fd) == -1){ //创建管道
printf("Create pipe error! \n"); //创建管道错误
}
pid = fork(); //创建子进程
if(pid <0 ){
printf("Fork error \n"); //fork错误
}else if(pid > 0){ //父进程
sleep(3);
printf("this is father\n");
close(fd[0]); //关闭读端
write(fd[1],"hello from father\n",strlen("hello from father"));
wait();
}else{
printf("this is child\n");
close(fd[1]); //关闭写端
read(fd[0],buff,128);
printf("read from father:%s\n",buff);
exit(0);
}
return 0;
}
高级管道通信
高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
有名管道通信
有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
#include<sys/stat.h>
#include<sys/types.h>
//返回值:成功返回0 ,失败返回-1
int mkfifo(char *pathname , mode_t mode);
管道打开规则
1.如果当前打开FIFO是为读时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回,否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志,即只设置了O_RDONLY),反之,如果当前打开操作没有设置了非阻塞标志,即O_NONBLOCK,则返回成功。
2、**如果当前打开FIFO是为写时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;**否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。
3、通俗的说,**要打开一个FIFO命名管道文件,需要一个进程以写打开,并且另一个进程要以读打开,只有满足了有读打开和有写打开,命名管道才算打开成功。**就像水管一样,水管的进口和出口需要同时打开,水才能流过去。
当open一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别?
- 若没有指定O_NONBLOCK(默认),只读open要阻塞 到某个 其他进程为写而打开此FIFO ,类似的,只写open要阻塞到某个其他他进程为读而打开它。
- 若指定了O_NOBLOCK,则只读open立即返回。而只写open将出错误返回-1 如果没有进程已经为读而打开该FIFO,其errno置ENXIO 。
//./read 实现两个进程间通信:多次写入
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<errno.h>
#include<fcntl.h>
int main(){
int buf[30]={0};
int nread=0;
int fd = open("./file",O_RDONLY);
printf("read open success\n");
while(1){
nread = read(fd,buf,30);
printf("read%d byte from fifo context:%s\n",nread,buf);
}
return 0;
}
//./write 实现两个进程间通信:多次写入
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>
int main(){
int cnt=0;
char *str="message from fifo";
int fd = open("./file",O_WRONLY);
printf("write open success\n");
while(1){
write(fd,str,strlen(str));
sleep(1);
if(cnt == 5){
break;
}
}
return 0;
}
不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。
消息队列通信
消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
特点
- 消息队列是面向记录的。其中的消息具有特定的格式以及特定 优先级。
- 消息队列独立于发生与接收进程,进程终止时,消息队列及内容并不会被删除。
- 消息队列可以实现消息的随机查询消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
原型
#include<sys/msg.h>
//创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key ,int flag);
//添加消息:成功返回0,失败返回-1
参数说明:
msqid :队列ID
ptr: 消息内容
size :消息大小
flag: 打开队列方式
int msgsnd(int msqid , void *ptr , size_t size ,int flag);
//读取消息:成功返回消息数据的长度,失败返回-1
参数说明:
msqid :队列ID
ptr: 消息
size :消息大小
type: 队列类型
- type == 0 ,返回队列中的第一个消息;
- type > 0 返回队列中消息类型为type 的第一个消息;
- type < 0 返回队列中消息类型值小于或者等于type 绝对值的消息,如果由多个,则取类型值最小的消息
type值非0 时用于以非先进先出次序读消息。也可以把type看做优先级的权值
flag: 打开队列方式
int msgrcv (int masqid ,void *ptr ,size ,long type ,int flag);
//控制消息队列:成功返回0 ,失败返回-1
int msgctl (int msqid ,int cmd ,struct msqid_ds *buf);
消息队列的使用
案例一: 消息队列编程收发数据代码:
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
//查找/创建消息队列 ,并发送消息
//假设key是写好的 0x1234
struct msgBuf
{
long mtype;
char mtext[128];
};
int main(){
struct msgBuf readBuf;
int msgID = msgget(0x1234,IPC_CREAT | 0777);
if(msgID == -1){
printf("get qun failuer\n");
}
msgrcv(msgID , &readBuf,sizeof(readBuf.mtext),888,0);
printf("read from qun :%s\n",readBuf.mtext);
return 0;
}
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
//查找/创建消息队列 ,并读取消息
struct msgBuf
{
long mtype;
char mtext[128];
};
int main(){
struct msgBuf sendBuf ={888,"send context to qun\n"};
int msgID = msgget(0x1234,IPC_CREAT | 0777);
if(msgID == -1){
printf("get qun failuer\n");
}
msgsnd(msgID , &sendBuf,strlen(sendBuf.mtext),0);
return 0;
}
案例二: 也可以同时互相通信。(同时接收消息)
假设key是写好的 0x1234
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
//读取并发送消息
struct msgBuf
{
long mtype;
char mtext[128];
};
int main(){
struct msgBuf readBuf;
struct msgBuf sendBuf={988,"Tranks for send!\n"};
int msgID = msgget(0x1234,IPC_CREAT | 0777);
if(msgID == -1){
printf("get qun failuer\n");
}
msgrcv(msgID , &readBuf,sizeof(readBuf.mtext),888,0);
printf("read from qun :%s\n",readBuf.mtext);
msgsnd(msgID , &sendBuf,strlen(sendBuf.mtext),0);
return 0;
}
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
//发送并读取消息
struct msgBuf
{
long mtype;
char mtext[128];
};
int main(){
struct msgBuf sendBuf ={888,"send context to qun\n"};
struct msgBuf readBuf;
int msgID = msgget(0x1234,IPC_CREAT | 0777);
if(msgID == -1){
printf("get qun failuer\n");
}
msgsnd(msgID , &sendBuf,strlen(sendBuf.mtext),0);
msgrcv(msgID , &readBuf,sizeof(readBuf.mtext),988,0);
printf("read from get :%s\n",readBuf.mtext);
return 0;
}
上面我们都是再key固定的情况下做出的通讯,如何找到key呢?我们需要学习下面的键值key是生成
键值(key)的生成
key的生成需要用到ftok函数.
ftok函数
系统建立IPC通讯(消息队列,信号量和共享内存)时必须指定一个ID值,通常 情况下,该ID值通过ftok函数得到。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok( const char * fname, int id )
参数说明:
fname 就是你指定的文件名(已经存在的文件名),一般使用当前目录。
id 是子序号。虽然是int类型,但是只使用8bits(1-255)。
key_t key;
key=ftok(".","z");
printf("key"=%x\n,key);
int msgID = msgget(key ,IPC_CREAT | 0777);
信号量通信
信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
信号
信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存通信
共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
套接字通信
套接字( socket ) : 套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。