进程间通信(简称IPC即InterProcess Communication),简单来说,就是两个进程之间的数据交换。
在进程这一小节,讲过通过exit函数,使当前进程退出并向父进程传递退出状态,父进程再通过wait函数收集终止状态。不过这只能局限于父、子进程,且不能实现子进程运行过程中的数据交换。我们要用到IPC来实现任何两个进程之间的数据交换。
进程间通信方式一般有以下几种:
1、匿名管道(仅支持同一主机的IPC)
2、命名管道:FIFO(仅支持同一主机的IPC)
3、消息队列(仅支持同一主机的IPC)
4、共享内存(仅支持同一主机的IPC)
5、信号(仅支持同一主机的IPC)
6、信号量(仅支持同一主机的IPC)
7、Scocket(支持同一主机和不同主机的IPC)
8、Stream(支持同一主机和不同主机的IPC)
一、管道
管道,通常是指无名管道,是UNIX系统IPC最古老的形式。
1、特点
(1)它是半双工(即数据只能在一个方向上流动),具有固定的读端和写端。
(2)它只能用于具有亲缘关系的进程之间的通信。(父子进程或者兄弟进程)
(3)它可以看成一种特殊的文件,对于它的读写也可以使用普通的read、write等函数,但是它不是普通的文件,并不属于其他任何的文件系统,并且只存在于内存中。
功能:创建一个管道,同时会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。
函数原型:
#include
int pipe(int fd[2]);
参数:
fd[0]为读端,fd[1]为写端。
返回值:
若成功返回0,失败返回-1。
例程:创建父进程与子进程之间的管道,父进程打开写端fd[1],关闭读端fd[0],往管道里写入数据;子进程打开读端fd[0],关闭写端fd[1],从管道里读取数据。
#include
#include
int main()
{
int fd[2];
pid_t pid;
char ret[1024];
char buf[1024];
//函数原型:int pipe(int fd[2]);
if(pipe(fd)==-1)
{
printf("create pipe fail!\n");
return -1;
}
if((pid=fork())<0)
{
printf("new process creat fail!\n");
}
else if(pid>0)
{
while(1)
{
close(fd[0]);
printf("please input str to pipe:\n");
scanf("%s",ret);
write(fd[1],ret,sizeof(ret)/sizeof(char));
}
}
else
{
while(1)
{
close(fd[1]);
read(fd[0],buf,sizeof(buf)/sizeof(char));
printf("get str from pipe buf:%s\n",buf);
}
}
return 0;
}
二、FIFO
FIFO,也称命名管道,它是一种文件类型。
1、特点
(1)FIFO可以在无关的进程之间交换数据,与无名管道不同。
(2)FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
函数原型:
#include
#include
int mkfifo(const char *pathname, mode_t mode);
参数:
pathname:路径名
mode:权限
返回值:
若成功返回0,失败返回-1并设置参数errno。
其中的mode参数与open函数中的mode相同。一旦创建了一个FIFO,就可以用一般文件I/O函数操作它。
当open一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
1、若没有指定O_NONBLOCK(默认),只读open要阻塞到某个其他进程为写而打开此FIFO。类似的,只写open要阻塞到某个进程为读而打开它。
2、若指定了O_NONBLOCK,则只读open立即返回。而只写open将出错返回-1。如果没有进程已经为读而打开该FIFO,其errno置ENXIO。
例程:演示使用设置非阻塞标志(O_NONBLOCK)的FIFO进行IPC的过程
//文件read_fifo.c
#include
#include
#include
#include
#include
#include
#include
int main()
{
int fd;
char buf[1024]="";
int nread=0;
if((mkfifo("./file",0600)==-1)&&(errno!=EEXIST))
{
printf("mkfifo fail!\n");
perror("why");
}
if((fd=open("./file",O_RDONLY))==-1)
{
printf("open fifo fail!\n");
}
printf("start read str!\n");
while(1)
{
nread=read(fd,buf,1024);
sleep(1);
if(nread==-1)
{
printf("read fail!\n");
}
else
{
printf("nread:%d bytes context:%s\n",nread,buf);
}
memset(buf,'\0',1024);
}
return 0;
}
//文件write_fifo.c
#include
#include
#include
#include
#include
#include
#include
int main()
{
int fd;
char buf[1024]="hello linux!\n";
int nwrite=0;
if((mkfifo("./file",0600)==-1)&&(errno!=EEXIST))
{
printf("mkfifo fail!\n");
perror("why");
}
if((fd=open("./file",O_WRONLY))==-1)
{
printf("open fifo fail!\n");
}
printf("start write str!\n");
while(1)
{
nwrite=write(fd,buf,strlen(buf));
sleep(1);
if(nwrite==-1)
{
printf("write fail!\n");
}
}
return 0;
}
三、消息队列
消息队列是消息的链接表,存放在内核中,一个消息队列一个标识符(即队列ID)来标识。
1、特点
(1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
(2)消息队列独立于发送接收过程。进程终止时,消息队列及其内容并不会被删除。
(3)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
2、函数原型
#include
#include
#include
//创建或打开消息队列,成功返回队列ID,失败返回-1。
int msgget(key_t key, int msgflg);
//添加消息,成功返回0,失败返回-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//读取消息,成功返回0,失败返回-1
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
在以下两种情况,msgget将创建一个新的消息队列:
1、如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
2、key参数为IPC_PRIVATE。
获取key值函数
函数原型:
#include
#include
key_t ftok(const char *pathname, int proj_id);
参数:
pathname:指定的文件名,一般使用当前路径
proj_id:子序号
返回:
根据路径名和子序号生成的key值
例程:演示使用消息队列进行IPC的过程
//文件msgsnd.c
#include
#include
#include
#include
#include
//消息结构
struct msgbuf {
long mtype;
char mtext[1024];
};
int main()
{
struct msgbuf Rcvmsg;
memset(Rcvmsg.mtext,'\0',1024);
struct msgbuf Sndmsg={
888,"hello linux!"};
//设置接收的消息类型和内容
int msgid;
int size_rcv=0;
//获取key值
key_t key=ftok(".",'z');
//打印key值
printf("msg queue key is %d\n",key);
//创建消息队列
if((msgid=msgget(key,IPC_CREAT|0777))==-1)
{
printf("msg create fail!\n");
}
msgsnd(msgid,&Sndmsg,strlen(Sndmsg.mtext),0);
//返回类型为887的第一个消息
if((size_rcv=msgrcv(msgid,&Rcvmsg,sizeof(Rcvmsg.mtext),887,0))==-1)
{
perror("why");
printf("msg rcv fail!\n");
}
printf("rcv bytes:%d context:%s\n",size_rcv,Rcvmsg.mtext);
//移除消息队列
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
//文件msgrcv.c
#include
#include
#include
#include
#include
//消息结构
struct msgbuf {
long mtype;
char mtext[1024];
};
int main()
{
struct msgbuf Rcvmsg;
memset(Rcvmsg.mtext,'\0',1024);
struct msgbuf Sndmsg={
887,"thank you!"};
//设置接收的消息类型和内容
int size_rcv=0;
int msgid;
//获取key值
key_t key=ftok(".",'z');
//打印key值
printf("msg queue key is %d\n",key);
//创建消息队列
if((msgid=msgget(key,IPC_CREAT|0777))==-1)
{
printf("msg create fail!\n");
}
//返回类型为888的第一个消息
if((size_rcv=msgrcv(msgid,&Rcvmsg,sizeof(Rcvmsg.mtext),888,0))==-1)
{
printf("msg rcv fail!\n");
perror("why");
}
printf("rcv bytes:%d context:%s\n",size_rcv,Rcvmsg.mtext);
msgsnd(msgid,&Sndmsg,strlen(Sndmsg.mtext),0);
//移除消息队列
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
四、共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
1、特点
(1)共享内存是最快的一种IPC,因为进程是直接对内存进行存取。
(2)因为多个进程可以同时操作,所以需要进行同步。
2、函数原型
#include
#include
//创建或获取一个共享内存,成功返回共享内存ID,失败返回-1
//size是以4KB为最小单位
int shmget(key_t key, size_t size, int shmflg);
//连接共享内存到当前进程的地址空间,成功返回指向内存的指针,失败则返回-1
//shmaddr为0时,内存自动分配共享空间,shmflg为0时,共享内存可读可写
void *shmat(int shmid,const void *shmaddr, int shmflg);
//断开与共享内存的连接,成功返回0,失败返回-1
int shmdt(const void *shmaddr);
//控制共享内存的相关信息,成功返回0,失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
指令:
ipcs -m 查看进程申请的共享内存
ipcrm -m 共享内存ID号 断开并释放与进程连接的共享存储
例程:演示使用共享内存进行IPC的过程
步骤:
1、创建或打开共享内存(shmget)
2、映射(shmat)
3、数据操作
4、释放共享内存(shmdt)
5、移除共享内存(shmctl)
//文件shmwrite.c
#include
#include
#include
#include
#include
int main()
{
int shmid;
char* shmaddr;
key_t key=ftok(".",'z');
shmid=shmget(key,4*1024,IPC_CREAT|0666);
if(shmid==-1)
{
printf("shmget fail\n");
exit(-1);
}
shmaddr=shmat(shmid, 0, 0);
strcpy(shmaddr,"hello linux!\n");
sleep(5);
shmdt(shmaddr);
shmctl(shmid,IPC_RMID,0);
return 0;
}
//文件shmread.c
#include
#include
#include
#include
#include
int main()
{
int shmid;
char* shmaddr;
key_t key=ftok(".",'z');
shmid=shmget(key,4*1024,IPC_CREAT|0666);
if(shmid==-1)
{
printf("shmget fail\n");
exit(-1);
}
shmaddr=shmat(shmid, 0, 0);
printf("shmaddr:%s",shmaddr);
shmdt(shmaddr);
return 0;
}