进程间通信(IPC,InterProcess Communication)
参考博客:
是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。
一、管道(无名管道)
相当于水管,从上端流入,下端流出,内容被读取后管道就是空的。
1.它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
2.它只能用于父子进程或者兄弟进程之间的通信。
3.它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。父子进程退出后,管道也就消失。
int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1
当一个管道建立时,它会创建两个文件描述符:fd[0]
为读而打开,fd[1]
为写而打开。如下图:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>
int main()
{
int pipefd[2];
pid_t pid;
char writebuf[] = {"hello!"};
char readbuf[7] = "\0";
int wstatus;
if(pipe(pipefd) == 0){
printf("创建管道成功\n");
}else{
printf("创建管道失败!\n");
}
if((pid = fork()) < 0){
printf("创建子进程失败!\n");
}else if(pid == 0)//子进程
{
printf("子进程,pid=%d;",getpid());
int size_write = write(pipefd[1], writebuf, strlen(writebuf));
if(size_write > 0){
printf("往管道写入%d字节\n",size_write);
}
close(pipefd[1]);
_exit(1);
}else if(pid > 0 )//父进程返回子进程进程号
{
waitpid(pid,&wstatus,0);//父进程等待子进程退出
if(WIFEXITED(wstatus)){
printf("子进程退出,status=%d\n",WEXITSTATUS(wstatus));
}
printf("父进程,pid=%d;",getpid());
int size_read = read(pipefd[0], readbuf , sizeof(readbuf));
if(size_read > 0){
printf("从管道读取%d字节,内容:%s\n",size_read,readbuf);
}else{
perror("read error");
}
close(pipefd[0]);
}
return 0;
}
二、命名管道(FIFO)
FIFO,称为命名管道,它是一种文件类型。
1、特点
-
FIFO可以在无关的进程之间交换数据,与无名管道不同。
-
FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
2、原型
int mkfifo(const char *pathname, mode_t mode);
其中的 mode 参数与open
函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK
)的区别:
-
若没有指定
O_NONBLOCK
(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。 -
若指定了
O_NONBLOCK
,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
创建了个fifo、命名管道,相当于一个文件。两个进程打开这个文件写入内容或者读取内容,一个进程为读打开文件会阻塞直到另外一个进程为写打开文件,这样两个进程就能传递信息。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
//fifo写端
int main()
{
int fd;
int size_w;
char buf[] = "hello !"; ///
//int mkfifo(const char *pathname, mode_t mode);
if(mkfifo("./file1",0600) == -1){
perror("创建fifo失败!");
}else{
printf("创建fifo成功\n");
}
fd = open("./file1",O_WRONLY);
if(fd < 0){
perror("打开fifo失败");
return -1;
}
size_w = write(fd,buf,strlen(buf));
if(size_w < 0){
perror("写fifo失败");
}else{
printf("往fifo写入%d字节\n",size_w);
}
close(fd);
return 0;
}
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
//fifo写端
int main()
{
int fd;
int size_r;
char buf[8];
fd = open("./file1",O_RDONLY);
if(fd < 0){
perror("打开fifo失败");
return -1;
}
size_r = read(fd,buf,sizeof(buf));
if(size_r < 0){
perror("读fifo失败");
}else{
printf("从fifo读取%d字节: %s\n",size_r,buf);
}
close(fd);
return 0;
}
三、消息队列
消息队列,是消息的链表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
1、特点
-
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
-
消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
-
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
2、原型
// 创建或打开消息队列:成功返回队列ID,失败返回-1
1 int msgget(key_t key, int msgflg);
// 添加消息:成功返回0,失败返回-1
2 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
3 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
4 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
1.msgget()
key: 返回的消息队列id与key的值相关联。
以下两种情况,会创建一个新的消息队列
- key参数为IPC_PRIVATE
- 没有与key相对应的消息队列存在,并且flag包含IPC_CREATE
2.msgsnd()
ptr: 指向结构体,结构体格式如下。
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
size: 结构体中数组mtext[]的大小,而非整个结构体的大小。
flag: 控制消息队满或者某些特殊情况下的处理方法。如果flag中设置了IPC_NOWAIT标志,函数将立即返回,不发送消息且返回值是-1,如果flag中无IPC_NOWAIT标志,则发送进程挂起以等待队列中腾出可用的空间。一般该参数设置为0。
3.msgrcv()
type: msgrcv() 中type有以下三种情况,为获取消息的优先级:
- type == 0 ,返回队列中的第一个消息;
- type > 0 ,返回队列中消息类型为 type 的第一个消息;
- type < 0 ,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
flag: 用于控制消息队列中无指定类型时的处理方法。若flag设置了IPC_NOWAIT标志,函数会立刻返回,返回值是-1,errno 会设置为 ENOMSG。若IPC_NOWAIT标志未被设置,并且无可读的消息,则调用进程被阻塞,直到一条相应类型的消息到达队列。一般情况下,该参数设置为0,无消息可读时,调用线程被阻塞。
步骤:
1.生成key值(ftok()),根据key值打开或创建消息队列(msgget());
2.发送信息(mesgsnd()),程序会阻塞直到另一进程读取消息(msgrcv());
例程
写入端
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[64]; /* message data */
};
//消息队列写端
int main()
{
int msgid;
key_t key ;
struct msgbuf msgbuf1={88,"hello from queue!"};
if((key=ftok("./",1)) < 0){//生成key值
perror("ftok error");
return -1;
}
msgid = msgget(key,IPC_EXCL);查看该key对应消息队列是否存在
if(msgid > 0){
printf("消息队列已存在,msgid=%d\n",msgid);
}else{
msgid = msgget(key,IPC_CREAT|0666);//生成新的消息队列
if(msgid < 0){
printf("创建消息队列失败,errno=%d,[%s]\n",errno,strerror(errno));
return -1;
}else{
printf("消息队列创建成功,msgid=%d\n",msgid);
}
}
int res = msgsnd(msgid,&msgbuf1,strlen(msgbuf1.mtext),0);
if(res < 0){
perror("msgsnd error");
}else{
printf("msgsnd写入成功\n");
}
//msgctl(msgid,IPC_RMID,NULL);
return 0;
}
读取端
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[64]; /* message data */
};
//消息队列读端
int main()
{
int msgid;
key_t key;
struct msgbuf msgbuf1;
if((key=ftok("./",1)) < 0){
perror("ftok error");//生成key值
return -1;
}
msgid = msgget(key,IPC_EXCL);//查看该key对应消息队列是否存在
if(msgid > 0){
printf("消息队列已存在,msgid=%d\n",msgid);
}else{
msgid = msgget(key, IPC_CREAT|0666);//生成新的消息队列
if(msgid < 0){
printf("创建消息队列失败,errno=%d,[%s]\n",errno,strerror(errno));
return -1;
}else{
printf("消息队列创建成功,msgid=%d\n",msgid);
}
}
int size_r = msgrcv(msgid,&msgbuf1,sizeof(msgbuf1.mtext),88,0);//0,如果
队列里没有相应type,msgrcv会阻塞
if(size_r < 0){
perror("msgrcv error");
}else{
printf("读取消息队列成功:msg:%s\n",msgbuf1.mtext);
}
msgctl(msgid,IPC_RMID,NULL);//删除消息队列
return 0;
}
四、共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
1、特点
-
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
-
因为多个进程可以同时操作,所以需要进行同步。
-
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
2、原型
1 #include <sys/shm.h>
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);
4 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr);
8 // 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
步骤:
A进程写:
1.创建共享内存(shmget());
2.将共享内存映射到当前进程的地址空间(shmat()),用变量指向内存;
3.用strcpy()往共享内存写数据;
4.断开进程与共享内存的连接(shmdt());
B进程读:
1.获取共享内存(shmget());
2.将共享内存映射到当前进程的地址空间(shmat()),用指针指向内存;
3.断开进程与共享内存的连接(shmdt());
4 .删除共享内存(shmctl);
写进程断开与共享内存连接后,在没使用shmclt(shmid,IPC_RMID,0)删除共享内存之前读进程都可以从里面读取,读取后共享内存为空。
例程
写端代码:
/*写端代码*/
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#define SHM_SIZE 1024*3
int main()
{
int shmid;
key_t key;
char *shmaddr;
if((key=ftok(".",1)) < 0){//创建key
perror("ftok error");
return -1;
}
shmid = shmget(key,0,IPC_EXCL);//检查是否有key对应的共享内存
if(shmid > 0){
printf("key=%d 对应的共享内存已存在,shmid=%d\n",key,shmid);
}else{
shmid = shmget(key,SHM_SIZE,IPC_CREAT|0666);//创建新的共享内存
if(shmid){
printf("共享内存创建成功,shmid=%d\n",shmid);
}else{
printf("共享内存创建失败,errno=%d:%s\n",errno,strerror(errno));
return -1;
}
}
shmaddr = shmat(shmid,0,0);//连接共享内存到当前进程的地址空间,成功返回指向共享内存的指针
if(shmaddr == (void*)-1){
perror("shmat error");
return -1;
}
printf("连接共享内存成功\n");
//向共享内存写入数据
strcpy(shmaddr,"hello from shm");
printf("已写入共享内存\n");
sleep(5);
shmdt(shmaddr);//断开与共享内存的连接
return 0;
}
读端代码:
/*读端代码*/
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#define SHM_SIZE 1024*3
int main()
{
int shmid;
key_t key;
char *shmaddr;
if((key=ftok(".",1)) < 0){//创建key
perror("ftok error");
return -1;
}
shmid = shmget(key,0,IPC_EXCL);//检查是否有key对应的共享内存
if(shmid > 0){
printf("key=%d 对应的共享内存已存在,shmid=%d\n",key,shmid);
}else{
shmid = shmget(key,SHM_SIZE,IPC_CREAT|0666);//创建新的共享内存
if(shmid){
printf("共享内存创建成功,shmid=%d\n",shmid);
}else{
printf("共享内存创建失败,errno=%d:%s\n",errno,strerror(errno));
return -1;
}
}
shmaddr = shmat(shmid,0,0);//连接共享内存到当前进程的地址空间,成功返回指向共享内存的指针
if(shmaddr == (void*)-1){
perror("shmat error");
return -1;
}
printf("连接共享内存成功\n");
//从共享内存读取数据
printf("共享内存:%s\n",shmaddr);
shmdt(shmaddr);//断开与共享内存的连接
shmctl(shmid,IPC_RMID,0);//删除共享内存
return 0;
}
查看系统中的共享内存
ipcs -m
删除共享内存
ipcrm -m +key