一、基本概念
什么是进程间通信:是指两个或多个进程之间交互数据的过程,因为进程之间是相互独立的,为了进程间协同工作就必须实现
进程间交互数据
进程间通信的分类:
简单的进程间通信:信号、普通文件、环境变量表、命令行参数
传统的进程间通信:管道文件
XSI的进程间通信:共享内存、消息队列、信号量
网络进程间的通信:Socket套接字
二、传统的进程间通信-管道文件
管道是UNIX系统中最古老的进程通信技术,古老意味着所有系统都支持,早期的管道都是半双工通信,现有的系统管道是全双工通信
管道就是一种特殊的文件,数据在文件中是流动的,读取之后就自动消失,如果文件中没有数据则会阻塞
有名管道:基于有文件名的管道文件的通信
编程模型
进程A 进程B
创建管道
打开管道 打开管道
写数据 读数据
关闭管道 关闭管道
删除管道
创建有名管道文件的方式:
1、命令 mkfifo
2、函数
int mkfifo(const char *pathname, mode_t mode);
功能:创建有名管道
pathname:管道文件的权限
mode:管道文件权限 0664
int fd = open("fifo",O_WRONLY);
if(0 > fd)
{
perror("open");
unlink("fifo");
return -1;
}
char buf[256] = {};
for(;;)
{
read(fd,buf,strlen(buf)+1);
if(strcmp(buf,"quit"))
{
printf("%s",buf);
break;
}
}
close(fd);
匿名管道:
只适合通过fork创建父子进程之间使用
int pipe(int pipefd[2]);
功能:创建一个匿名管道文件
通过pipefd返回该匿名管道文件的读权限fd和写权限的fd
pipefd[0] 用于读匿名管道
pipefd[1] 用于写匿名管道
编程模型:
父进程 子进程
获取一对fd
创建子进程 拷贝/共享一对fd
关闭读 关闭写
写数据 读数据
关闭写 关闭读
三、XSI进程间通信
X/open公司制定的用于进程间通信的系统(S)接口(I)
XSI进程间通信都需要借助系统内核完成,需要创建内核对象,内核对象会以整数的形式返回给用户态,类似于文件描述符,
也叫做IPC标识符
文件的创建打开需要借助文件名,IPC内核对象需要借助IPC键值(整数),必须要确保IPC键值是独一无二的
key_t ftok(const char *pathname, int proj_id);
功能:计算出一个独一无二的IPC键值
pathname:项目路径
proj_id:项目标号
返回值:计算出来的IPC键值
注意:项目路径必须真实存在,否则计算出来的key永远相同
共享内存:
基础特点:
两个或多个进程之间共享一块由内核负责统一管理内存,该内存可以与多个进程的虚拟内存进行映射
优点:不需要复制信息,直接读写内存,是最快的一种IPC机制
缺点:需要考虑同步访问问题,一般使用信号
int shmget(key_t key,size_t size,int shmflg);
功能:创建\获取一块共享内存
key:IPC键值
size:共享内存的大小,获取共享内存时此参数无意义,一般给0
shmflg:
IPC_CREAT 创建共享内存,如已存在直接获取
IPC_EXCL 共享内存已存在,返回失败
获取时直接给0
注意:如果是创建共享内存还需要额外提供共享内存的权限 例如:IPC_CREAT|0664
返回值:IPC标识符,失败-1
void *shmat(int shmid,const void *shmaddr, int shmflg);
功能:让虚拟内存与共享内存进行映射
shmid:IPC标识符 shmget的返回值
shmaddr:想要映射的虚拟内存首地址,为NULL时系统会自动分配地址
shmflg:
SHM_RDONLY 以只读方式映射共享内存
SHM_RND:只有shmaddr参数不为NULL时才有有效,表示从shmaddr开始向下以整数页方式映射
返回值:与共享内存映射成功后的虚拟内存首地址,失败返回(void *) -1
int shmdt(const void *shmaddr);
功能:取消映射
shmaddr:映射成功后的虚拟内存首地址
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
功能:删除/控制共享内存
shmid:IPC标识符
cmd:
IPC_STAT 获取共享内存属性 buf输出型参数
IPC_SET 设置共享内存属性 buf输入型参数
IPC_RMID 删除共享内存 NULL
buf
编程模型:
进程A 进程B
创建共享内存 获取共享内存
映射共享内存 映射共享内存
写数据并通知其他进程 收到通知并读数据
收到通知并读数据 写数据并通知其他进程
取消映射 取消映射
删除共享内存
#include
#include
#include
#include
#include
#include
#include
#include
int shmid;
char* shm;
// 接收到通知 读数据
void sigread(int num)
{
printf("read:%s\n",shm);
if(0 == strcmp(shm,"quit"))
{
printf("通信结束!\n");
// 取消映射
if(shmdt(shm))
{
perror("shmdt");
}
// 删除共享内存
if(shmctl(shmid,IPC_RMID,NULL))
{
perror("shmctl");
}
exit(0);
}
}
int main(int argc,const char* argv[])
{
signal(34,sigread);
// 创建共享内存
shmid = shmget(ftok(".",110),4096,IPC_CREAT|0644);
if(0 > shmid)
{
perror("shmget");
return -1;
}
pid_t pid = 0;
printf("我是%u,请输入与我通信的进程ID:",getpid());
scanf("%u",&pid);
// 映射共享内存
shm = shmat(shmid,NULL,0);
if((void*)-1 == shm)
{
perror("shmat");
shmctl(shmid,IPC_RMID,NULL);
return -1;
}
// 写数据并通知其他进程
for(;;)
{
printf(">>>");
scanf("%s",shm);
kill(pid,34);
if(0 == strcmp(shm,"quit"))
{
printf("通信结束!\n");
break;
}
}
// 取消映射
if(shmdt(shm))
{
perror("shmdt");
}
// 删除共享内存
if(shmctl(shmid,IPC_RMID,NULL))
{
perror("shmctl");
}
exit(0);
}
消息队列:
基本特点:是由内核负责维护管理的链式数据队列,不是根据先后顺序出队,而是根据消息类型进行收发数据
int msgget(key_t key, int msgflg);
功能:创建\获取消息队列
key:IPC键值
msgflg:
IPC_CREAT 消息队列已存在则获取,否则创建
IPC_EXCL 消息队列已存在则返回错误
注意:如果创建需要提供权限
返回值:IPC标识符,失败-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列发送消息包
msqid:IPC标识符
msgp:要发送的消息包的首地址
struct msgbuf {
long mtype; // 消息类型
char mtext[n]; // 数据
};
msgsz:数据的字节数,不包含消息类型
msgflg:
阻塞发送一般给0
IPC_NOWAIT 当消息队列满,不等待立即返回
返回值:成功0,失败-1
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息队列中接收对应消息包的数据
msqid:IPC标识符
msgp:存储消息包的内存首地址
msgsz:存储数据的内存字节数(尽量大些)
msgtyp:消息类型(按照类型获取,不按照顺序)
>0 读取消息类型=msgtyp的消息
=0 读取消息队列中第一条消息
<0 读取消息类型小于abs(msgtyp)的消息,如果有多个则读值最小的
msgflg
IPC_NOWAIT 消息队列都不符合时不阻塞,立即返回
MSG_EXCEPT 如果msgtyp>0,则读取第一条不等于msgtyp的消息
MSG_NOERROR 如果不包含此标志,如果实际发送过来的数据字节数>接收的字节数,则返回失败,
如果包含此标志,那么就只读取接收的字节数,一定会成功
返回值:成功读取到数据的字节数
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
功能:获取\修改消息队列的属性、删除队列
msqid:IPC标识符
cmd:
IPC_STAT 获取消息队列属性 buf输出型参数
IPC_SET 设置消息队列属性 buf输入型参数
IPC_RMID 删除消息队列 NULL
buf:消息队列管理结构体
编程模型:
进程A 进程B
创建消息队列 获取消息队列
发送消息 获取消息
获取消息 发送消息
删除消息队列
message.h
#ifndef MESSAGE_H
#define MESSAGE_H
#define MSG_SIZE 256
typedef struct Msg
{
long type;
char data[MSG_SIZE];
}Msg;
#endif//MESSAGE_H
msgA.c
#include
#include
#include
#include
#include
#include
#include
#include "message.h"
int main(int argc,const char* argv[])
{
// 创建消息队列
int msqid = msgget(ftok(".",120),IPC_CREAT|0644);
if(0 > msqid)
{
perror("msgget");
return -1;
}
Msg msg = {};
for(;;)
{
// 发送消息
msg.type = 5;
printf(">>>");
scanf("%s",msg.data);
if(msgsnd(msqid,&msg,strlen(msg.data)+1,0))
{
perror("msgsnd");
break;
}
if(0 == strcmp(msg.data,"quit")) break;
// 接收消息
if(0 >= msgrcv(msqid,&msg,MSG_SIZE,6,0))
{
perror("msgrcv");
break;
}
printf("recv:%s\n",msg.data);
if(0 == strcmp(msg.data,"quit")) break;
}
printf("通信结束!\n");
usleep(1000);
// 删除消息队列
if(msgctl(msqid,IPC_RMID,NULL))
{
perror("msgctl");
return -1;
}
return 0;
}
msgB.c
#include
#include
#include
#include
#include
#include
#include
#include "message.h"
int main(int argc,const char* argv[])
{
// 获取消息队列
int msqid = msgget(ftok(".",120),0);
if(0 > msqid)
{
perror("msgget");
return -1;
}
Msg msg = {};
for(;;)
{
// 接收消息
if(0 >= msgrcv(msqid,&msg,MSG_SIZE,5,0))
{
perror("msgrcv");
break;
}
printf("recv:%s\n",msg.data);
if(0 == strcmp(msg.data,"quit")) break;
// 发送消息
msg.type = 6;
printf(">>>");
scanf("%s",msg.data);
if(msgsnd(msqid,&msg,strlen(msg.data)+1,0))
{
perror("msgsnd");
break;
}
if(0 == strcmp(msg.data,"quit")) break;
}
printf("通信结束!\n");
return 0;
}
信号量:
基本特点:由内核管理的一个"全局变量",用于记录共享资源的数量,限制进程对共享资源的访问使用
信号量是一种数据操作锁,本身是不具备数据交互功能,而是通过控制其他的通信资源从而配合实现进程间通信
1、如果信号量的值大于0,说明可以使用资源,使用时需要信号量-1,然后再使用
2、如果信号量的值等于0,说明没有资源可使用,此时进程进入休眠,直到信号量的值大于0,进程会被唤醒,执行步骤1
3、当资源使用完毕,把信号量的值+1,正在休眠的进程就会被唤醒
int semget(key_t key, int nsems, int semflg);
功能:创建\获取信号量
key:IPC键值
nsems:信号量的数量 一般写1
semflg:IPC_CREAT 信号量已存在则获取,否则创建
IPC_EXCL 信号量已存在则返回错误
注意:如果创建需要提供权限
返回值:IPC标识符 失败-1
int semctl(int semid, int semnum, int cmd, ...);
功能:删除、控制信号量
semid:IPC标识符
semnum:要操作的第几个信号量,从0开始,下标
cmd:
IPC_STAT 获取信号量属性 buf输出型参数
IPC_SET 设置信号量属性 buf输入型参数
IPC_RMID 删除信号量 NULL
SETVAL 设置某个信号量的值
SETALL 设置所有信号量的值
GETVAL 获取某个信号量的值
GETALL 获取所有信号量的值
GETNCNT 获取等待拿资源的进程数量
int semop(int semid,struct sembuf *sops,size_t nsops);
功能:对信号量进行加减操作
semid:IPC标识符
sembuf{
unsigned short sem_num; // 信号量的下标
short sem_op; //
1 信号量+1
-1 信号量-1 如果不能减,则默认阻塞
short sem_flg; //
IPC_NOWAIT 不阻塞
SEM_UNDO 如果进程终止没有手动还资源,系统会自动还
}
nsops:表示指向结构体的数量,一般写1
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc,const char* argv[])
{
// 创建信号量
int semid = semget(ftok(".",119),1,IPC_CREAT|0644);
if(0 > semid)
{
perror("semget");
return -1;
}
// 设置信号量的值
// 0下标 5资源数
if(semctl(semid,0,SETVAL,5))
{
perror("semctl");
semctl(semid,0,IPC_RMID);
return -1;
}
printf("我是父进程%u,我有%d头小毛驴\n",
getpid(),semctl(semid,0,GETVAL));
for(int i=0; i<10; i++)
{
pid_t pid = fork();
if(0 == pid)
{
// 尝试获取共享资源
srand(i);
struct sembuf buf = {0,-1,0};
semop(semid,&buf,1);
// 工作
printf("我是子进程%u,我骑了一头小毛驴!还剩%d头小毛驴\n",getpid(),semctl(semid,0,GETVAL));
sleep(rand()%8+3);
// 还共享资源
buf.sem_op = 1;
semop(semid,&buf,1);
printf("我是子进程%u,我还了一头小毛驴!还剩%d头小毛驴\n",getpid(),semctl(semid,0,GETVAL));
return 0;
}
}
while(-1 != wait(NULL));
printf("我是父进程%u,我有%d头小毛驴\n",
getpid(),semctl(semid,0,GETVAL));
}