进程间通信
进程之间是相互独立的,多个进程之间如果需要协同工作(多进程),那么进程之间就需要通信(数据交换)。
进程间通信的分类
1、简单数据通信
命令行参数、信号、文件
2、传统的进程间通信
管道文件、匿名管道
3、XSI通信间通信
共享内存、消息队列、信号量
4、网络进程间通信
本地网络:根据网络协议的通信格式,同一个系统内的进程进行通信。
网络通信:根据网络协议的通信格式,不同计算机之间的进程通信。
管道通信
管道是UNIX系统最古老的通信方式,早期管道是半双工的工作模式,只允许数据单项流动,现在的系统大多是全双工的,数据可以双向流动。
管道文件
是一种特殊的文件,采用流式的工作模式。
与普通文件的区别
1、数据读取完之后就消失
2、读取数据时,如果数据不存在则阻塞
编程模型
进程A 函数 进程B
创建管道 mkfifo
打开管道 open 打开管道
写入数据 write/read 读取数据
关闭管道 close 关闭管道
删除管道 unlink
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个管道文件
pathname:管道文件的路径
mode:文件的权限
返回值:成功返回0,失败返回-1
匿名管道
由内核维护一个内核对象,没有文件名,但能以文件的方式操作,只适合以fork创建的父子进程之间使用。
int pipe(int pipefd[2]);
功能:创建一个匿名的管道,并以参数形式返回管道的文件描述符。
返回值:成功返回0,失败返回-1。
pipefd[0] 读
pipefd[1] 写
编程模型
进程A 函数 进程B
创建匿名管道 pipe
创建子进程 fork 复制父进程的管道
关闭读通道 close 关闭写通道
写数据 write/read 读数据
关闭写通道 close 关闭读通道
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main()
{
int fd[2] = {};
char buf[4096] = {};
//创建管道
if(pipe(fd))
{
perror("pipe");
return -1;
}
//创建子进程
pid_t pid = fork();
//父进程
if(pid)
{
//关闭读通道
close(fd[0]);
//写数据
for(;;)
{
printf(">>");
gets(buf);
int ret = write(fd[1],buf,strlen(buf)+1);
if(0 >= ret)
{
perror("write");
return -1;
}
if(0 == strcmp("quit",buf))
{
printf("通信结束\n");
break;
}
usleep(1000);
}
//关闭写通道
close(fd[1]);
while(-1 != wait(NULL));
}
//子进程
else
{
//关闭写通道
close(fd[1]);
//读数据
for(;;)
{
int ret = read(fd[0],buf,sizeof(buf));
if(0 >= ret)
{
perror("read");
return -1;
}
if(0 == strcmp("quit",buf))
{
printf("通信结束");
break;
}
printf("read:%s\n",buf);
}
//关闭读通道
close(fd[0]);
}
}
XSI通信间通信
由X/Open组织设计一套用于进程间通信的接口,简称XSI。
IPC对象
1、XSI的通信方式是由内核维护一个用于进程通信的对象,简称IPC对象。
2、这种对象有三种,分别是共享内存、消息队列、信号量。
3、该类对象与文件对象一样,只给应用层暴露一个整数,叫IPC标识。
IPC键值
1、相当于文件的名字,用来创建IPC对象/打开IPC对象。
2、如果应用层提供的IPC键值相同,就会获取同一个IPC对象的标识符。
3、IPC键值是一个无符号长整型,可以自动生成,也可以由程序员自定义(可能会冲突)。
key_t ftok(const char *pathname, int proj_id);
功能:生成一个IPC键值
pathname:当前项目的路径
注意:生成键值时依赖的是真实的路径,而不是字符串
proj_id:当前项目版本号
共享内存
在内核中开辟一块内存,供多个进程进行映射,共享使用该内存,达到通信的效果。
特点:不需要复制,是最快的IPC机制
缺点:需要解决同步访问的问题
int shmget(key_t key, size_t size, int shmflg);
功能:在内核创建一块共享内存
key:IPC键值
size:共享内存的大小
shmflg:
IPC_CREAT 创建共享内存
IPC_EXCL 如果已经存在则创建失败
0 获取共享内存
mode_flags 共享内存的权限,与文件权限设置文件一致,0mmm
返回值:
成功,则返回共享内存的标识符,相当于文件描述符
失败,返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射内核中的共享内存
shmid:IPC标识符,shmget的合法返回值
shmaddr:要映射的虚拟地址,如果为NULL,则系统自动选择要映射的虚拟地址
shmflg:
0 以读写方式映射
SHM_RDONLY 以只读方式映射
SHM_RND 当shmaddr不为空时,对shmaddr的值向内存赠的整数倍进行向下取整作为映射地址
返回值:成功返回映射后的地址,失败返回0xffffffff
int shmdt(const void *shmaddr);
功能:取消映射
shmaddr:映射成功的虚拟地址
返回值:成功返回0,失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:管理共享内存
shmid:IPC标识符,shmget的合法返回值
cmd:
IPC_STAT 获取共享内存的属性
IPC_SET 设置共享内存的属性,只能修改大小和权限
IPC_RMID 删除共享内存的属性
struct shmid_ds {
struct ipc_perm shm_perm; //共享内存的拥有者以及权限
size_t shm_segsz; //共享内存的大小
time_t shm_atime; //最后一次的映射时间
time_t shm_dtime; //最后一次取消映射时间
time_t shm_ctime; //最后一次修改时间
pid_t shm_cpid; //创建者的进程ID
pid_t shm_lpid; //最后一次映射或者取消映射的进程ID
shmatt_t shm_nattch; //被映射的次数
...
};
struct ipc_perm {
key_t __key; //IPC键值
uid_t uid; //拥有者的用户ID
gid_t gid; //拥有者的组ID
uid_t cuid; //创建者的用户ID
gid_t cgid; //创建者的组ID
unsigned short mode; //共享内存的权限
unsigned short __seq; //共享内存的序列号
};
编程模型
进程A 进程B 函数
创建共享内存 获取 shmget
映射共享内存 映射 shmat
使用共享内存 使用 …(signal)
取消映射 取消 shmdt
删除共享内存 shmctl
消息队列
小项目:多进程编程之银行系统(消息队列):传送门
基本特点:是由内核维护的数据链接表,进程之间可以通过它按类型、顺序收发数据。
消息格式:
struct msgbuf{
long mtype //消息类型,由程序员自己决定
char mtext[1]; //消息内容
};
int msgget(key_t key, int msgflg);
功能:在内核中创建一块消息队列
key:IPC键值
shmflg:
IPC_CREAT 创建消息队列
IPC_EXCL 如果已经存在则创建失败
0 获取消息队列
mode_flags 消息队列的权限,与文件权限设置文件一致,0mmm
返回值:
成功,则返回消息队列的标识符,相当于文件描述符
失败,返回-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:发消息
msgp:消息结构体变量地址
msgsz:消息内容的长度,不包括消息类型
msgflg:
0 当消息队列满时,阻塞
IPC_NOWAIT 当消息队列满时不阻塞
MSG_NOERROR 当消息内容超出消息队列限制时,自动截断消息内容,不产生错误
返回值:
成功,返回 0
失败,返回-1
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:接收消息
msqid:消息队列标识符,msgget的合法返回值
msgp:存储消息的结构体变量地址
msgsz:存储消息内容的容量
msgtyp:消息类型
msgflg:
0 当要接收的消息不存在时等待
IPC_NOWAIT 当要接收的消息不存在时不等待
MSG_EXCEPT 接收消息队列第一个不是msgtyp的消息
返回值:
成功,返回实际接收到的字节数
失败,返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:管理消息队列
msqid:消息队列标识符,msgget的合法返回值
cmd:
IPC_STAT 获取消息队列的属性
IPC_SET 设置消息队列的属性,只能修改大小和权限
IPC_RMID 删除消息队列的属性
struct msqid_ds {
struct ipc_perm msg_perm; //消息队列的拥有者以及权限
time_t msg_stime; //最后发送消息的时间
time_t msg_rtime; //最后接收消息的时间
time_t msg_ctime; //最后修改消息的时间
unsigned long __msg_cbytes; //当前消息队列的总字节数
msgqnum_t msg_qnum; //当前消息队列中消息的数量
msglen_t msg_qbytes; //一条消息内容的最大字节数
pid_t msg_lspid; //最后发送消息的进程ID
pid_t msg_lrpid; //最后接收消息的进程ID
};
编程模型
进程A 进程B 函数
创建消息队列 获取消息队列 msgget
发送消息 接收消息 msgsnd/msgrcv
接收消息 发送消息
删除消息队列 msgctl
信号量
信号量详细介绍+例子:传送门
在内核中被若干个进程共享的变量,用于计数,解决多进程之间对有限的共享资源进行访问。
当进程需要获取一份资源时,就应该先对计数器减一,如果不够减则阻塞,当资源使用完后并归还,应该对计数器加一。
进程间共享的资源不多,信号量在进程使用不多
相关函数
int semget(key_t key, int nsems, int semflg);
功能:创建或获取信号量
key:IPC键值
nsems:信号量的个数
semflg:
0 获取信号量
O_CREAT 创建信号量
O_EXCL 如果已经存在则出错
返回值:
成功返回信号标识符
失败返回-1
int semctl(int semid, int semnum, int cmd, ...);
功能:管理信号量
1、设置信号量属性
2、获取信号量属性
3、删除信号量
4、设置/获取信号量的值
semid:信号量标识符,semget合法的返回值
semnum:信号量的数量
cmd:
IPC_STAT 获取信号量的属性
第四个参数 struct semid_ds *buf 输出型参数
IPC_SET 设置信号量的属性
第四个参数 struct semid_ds *buf 输入型参数
IPC_RMID 删除信号量
没有第四个参数
GETALL 获取所有信号量的值
第四个参数 unsigned short *array 该数组中用于存储每个信号量的值
GETVAL 获取一个信号量的值
没有第四个参数,semnum相当于下标,信号量的值通过返回值返回。
SETALL 设置所有信号量的值
第四个参数 unsigned short *array 该数组中存储这每个信号量要设置的值
SETVAL 设置一个信号量的值
第四个参数 int val 设置信号量的值,semnum相当于下标
返回值:因cmd的不同而不同,失败返回-1
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:对信号量加1或减1
semid:信号量标识符,semget的合法返回值
sops:用于操作信号量的结构数组
nsops:结构数组的长度
返回值:成功返回0,失败返回-1
struct sembuf{
unsigned short sem_num; //信号量下标
short sem_op; //操作数(+1 或者 -1)
short sem_flg;
IPC_NOWAIT:当信号量不够sem_op减时,进程不阻塞
SEM_UNDO:当进程结束时,本次操作将会取消
}
int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *timeout);
功能:多了个倒计时的功能
timeout:用于阻塞的时间,
编程模型
进程A 进程B 函数
创建信号量 获取信号量 semget
初始化信号量 semctl
减信号量 减信号量 semop
使用资源 使用资源 …
加信号量 加信号量 semop
删除信号量 semctl
查看XSI对象
ipcs -m 显示共享内存
ipcs -q 显示消息队列
ipcs -s 显示信号量
ipcs -a 显示所有IPC对量
删除XSI对象
ipcrm -m shmid 删除共享内存
ipcrm -q msgid 删除消息队列
ipcrm -s semid 删除信号量