C | 进程间通信的方式
1.无名管道
无名管道是实现亲缘间进程通信的一种方式,属于半双工通信。
无名管道的实现是队列,不能使用lseek对读写指针偏移。
无名管道有两个端:数据流入端和数据流出端,也叫写端和读端。它们是两个固定的端口,遵循先入先出,数据读走消失的原则。
如果无名管道中没有数据,read读取时会阻塞等待数据。
无名管道的长度有限,64K。管道写满之后,再次写入会阻塞。
如果关闭了写入端口,管道机制会认为写端关闭,将管道中的数据读出后,read不会再阻塞。不会影响程序运行。
如果关闭读端关闭,写入将不再有意义,再次写入时会发生“管道破裂”。
无名管道不在文件系统上体现,数据存储在内存上,进程结束,数据丢失。
创建无名管道的函数接口 :
头文件:#include <unistd.h>
原型: int pipe(int pipefd[2])
功能: 创建一个无名管道,读写端两个文件描述符分别封装到fd[0]和fd[1]
(fd[0]---r;fd[1]---w)
参数 : 自己定义的数组
返回值:成功返回 0 ;失败返回 -1;
使用无名管道实现亲缘间进程通信 :
因为fork函数创建完子进程后,文件描述符也会被拷贝过去,相当于父子进程共用文件描述符去操作同一个管道。
双方通信需要两个管道。
无名管道实现进程间通信的原理:
2.有名管道
有名管道是建立在无名管道的基础上的一种进程间通信方式,目的是为了解决无名管道只能用于亲缘间进程通信的缺点。
有名管道在文件系统中属于特殊的管道文件,虽然在文件系统上有所体现,但数据并不是存放在磁盘上,而是存放在内存上,进程结束,数据丢失。
有名管道通过文件实现进程间通信,所以需要打开这个文件,进程需要分别以读、写权限打开,如果打开方式不足这两个权限,open会阻塞等待权限。
创建有名管道 :
方式一 :linux命令
mkfifo 有名管道的名字
方式二 :函数接口
头文件:#include <sys/types.h>
#include <sys/stat.h>
原型: int nkfifo(const char *pathname,mode_t mode)
功能: 创建一个有名管道
参数: pathname:目标路径
mode : 权限
返回值:成功返回0;失败返回-1
两个进程间通信需要两个有名管道文件
有名管道实现进程间通信的原理:
3.信号
信号是软件层对硬件层中断的一种模拟,是一种异步通信的方式,通过信号传递信息,信息量较小。
在进程创建初期,会创建一个信号函数表,每一个信号对应一个指向信号处理函数的功能函数指针。
信号的处理方式:
1.忽略:指收到信号后,不采取任何措施。
例如:SIGCHLD :子进程结束后给父进程发送的一个信号
2.默认:指收到信号后,执行进程创建初期构建的信号函数表中默认的信号处理函数。
3.捕获:指将信号函数表中信号对应的默认函数指针指向自己定义的信号处理函数,收到信号后,执行按自己的想法执行。
SIGKILL和SIGSTOP不能被忽略和捕捉。
信号相关的函数接口:
1)signal
头文件:#include <signal.h>
原型: typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler)
功能: 注册一个信号处理函数
参数: signum:信号
handler:信号处理方式
忽略:SIG_IGN 默认:SIG_DFL 捕捉:自定义的函数指针
返回值:成功返回一个指向上一次执行的信号处理函数的函数指针;
失败返回SIG_ERR
2)kill
头文件: #include <sys/types.h>
#include <signal.h>
原型: int kill(pid_t pid, int sig)
功能: 给指定的一个进程发送一个信号
参数: pid :目标进程号
sig :信号号
返回值:成功返回0;失败返回-1
3)pause
头文件:#include <unistd.h>
原型: int pause(void)
功能: 将一个进程挂起,直到收到信号改变状态
注意:只监控信号,但是并不消耗信号
返回值:成功返回接收到的信号号;失败返回 -1
4)raise
头文件: #include <signal.h>
原型: int raise(int sig)
功能: 给自己发送一个信号
参数: sig:信号号
返回值: 成功返回0 ;失败返回非0值
5)alarm
头文件:#include <unistd.h>
原型: unsigned int alarm(unsigned int seconds)
功能: 给自己发送一个闹钟信号---SIGALRM 默认终止进程
参数: seconds:定时时间,单位秒
注意:如果调用alarm后再次调用alarm 函数会刷新定时器的时间,打断了上一次alarm定时,上一次的alarm不会再发送闹钟信号了,会将上一次的alarm剩余的秒数返回回来
返回值:成功返回上一次alarm剩余的秒数 ,0 代表的是定时器时间已到
4.消息队列
消息队列是一种全双工的通行方式,利用内核空间完成通信,实质上是管道在内核空间上的集合。
消息队列的函数接口:
1)ftok
头文件:#include <sys/types.h>
#include <sys/ipc.h>
原型: key_t ftok(const char *pathname,int proj_id)
功能: 根据pathname和id这两个参数生成一个key_t类型的数据
参数: pathname:存在的文件路径即可
proj_id:int数据类型的低8位
返回值:成功返回生成好的键值;失败返回-1
2)msgget
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
原型: int msgget(key_t key,int msgflg)
功能: 创建或者打开一个消息队列
参数: key:通过键值创建一个消息队列
IPC_PRIVATE:直接用于亲缘间进程
自定义key的值(ftok)
msgflg:打开消息队列的方式
IPC_CREAT IPC_EXCL
返回值:成功返回一个消息队列的ID号(非负整数);失败-1
3)msgsnd
头文件:同上
原型: int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg)
功能: 向消息队列放入数据
参数: msqid:目标消息队列的ID号
msgp:要发送消息的地址,必须是一个结构体类型,结构体第一个成员必须是消息的类型(long)的数据,其余成员可以修改。
struct msgbuf {
long type;
char text[1024];
}
msgsz:发送消息中正文的大小
msgflg:发送消息的方式
阻塞:0 非阻塞:IPC_NOWAIT
返回值:成功返回0;失败返回-1
4)msgrcv
头文件:同上
原型: ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg)
功能: 从消息队列中获取数据
参数: msgid:消息队列的ID号
msgp:存放数据的地址,结构体要求同msgsnd
msgsz:消息的正文的大小
msgtyp:消息的类型 如果写0,从消息队列头开始读取
msgflg:获取数据的方式 (同msgsnd)
返回值:成功返回接收的正文的大小;失败返回-1
5)msgctl
头文件:同上
原型: int msgctl(int msgid,int cmd,struct msgqid_ds *buf)
功能: 控制消息队列
参数: msgid:目标消息队列
cmd:控制方式
IPC_STAT:获取消息队列的所有信息
IPC_SET:设置消息队列里的信息(设置msg_perm结构体)
IPC_RMID:删除消息队列,第三个参数写NULL
buf:信息结构体
返回值:成功返回0,失败返回-1
消息队列实现进程间通信的原理:
5.共享内存
共享内存是利用地址映射的方式实现进程间通信,实质上是在内核空间建立一个共享的区域,然后将这片区域映射到用户空间,此时操作用户空间就相当于直接操作内核空间。
共享内存是进程间通信中效率最高、速度最快的一种通信方式。但是共享内存中的数据读走不会消失,每次写入数据时会覆盖之前的数据。
共享内存一般用于数据实时采集上传,本身不具备进程同步的功能。
共享内存的函数接口:
1)shmget
头文件:#include <sys/ipc.h>
#include <sys/shm.h>
原型: int shmget(key_t key,size_t size,int shmflg)
功能: 创建或者打开共享内存
参数: key:创建或者打开共享内存的方式,同消息队列
size:创建共享内存的大小,如果已经存在则无效
size一般都设置为4K的倍数,如果不是4K的倍数,真实的共享内存会近似4K的倍数,但ipcs查看时不会显示出来
shmflg:打开的方式,同消息队列
返回值:成功返回共享内存的ID号,失败返回-1
2)shmat
头文件:同上
原型: void *shmat(int shmid,const void *shmaddr,int shmflg)
功能: 地址映射,将shmid所对应的共享内存的首地址映射到用户空间
参数: shmid:目标共享内存
shmaddr:映射到用户空间的地址
写NULL操作系统会自动分配地址,通过返回值返回给用户空间
shmflg:映射出来的地址的操作权限
0:读写权限 SHM_RDONLY:只读权限
返回值: 成功返回用户空间映射的地址;失败返回(void *)-1
3)shmdt
头文件:同上
原型: int shmdt(const void *shmaddr)
功能: 取消地址映射
参数: shmaddr:用户空间的首地址
返回值:成功返回0;失败返回-1
4)shmctl
头文件:同上
原型: int shmctl(int shmid,int cmd,struct shmid_ds *buf)
功能: 控制共享内存
参数: shmid:共享内存ID号
cmd:控制方式
IPC_STAT:获取共享内存的属性
IPC_SET:设置共享内存的属性
IPC_RMID:删除共享内存
如果有进程正在使用,会等进程断开连接后删除
buf:消息结构体地址
返回值:IPC_STAT控制方式,成功返回ID号;其他情况成功返回0;失败返回-1
使用的时候要进行地址映射,不再使用要取消地址映射。
共享内存实现进程间通信的原理:
6.信号灯集
信号灯集实质上是内核空间信号灯的集合。
信号灯的函数接口:
1)semget
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
原型: int semget(key_t key,int nsems,int semflg)
功能: 创建或者打开一个信号灯集
参数: key:信号灯集的键值(同消息队列)
nsems:创建的信号灯集中信号灯的个数
semflg:打开方式(同消息队列)
返回值:成功返回一个信号灯集的ID号;失败返回-1
2)semctl
头文件:同上
原型: int semctl(int semid,int semnum,int cmd,...)
功能: 控制信号灯集
参数: semid:要控制的信号灯集的ID号
semnum:信号灯的编号
cmd:控制方式
IPC_RMID:删除信号灯集,不考虑第二个参数
GETVAL:获取信号灯的值
SETVAL:设置信号灯的值
...:可变参数,一个联合体
union semun {
int val;
struct semid_ds *buf;
}
返回值:成功返回0;控制方式设置为GETVAL成功返回一个信号灯的值;失败返回-1
函数需要自己封装
//信号灯集示例:
#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//定义一个初始化使用的联合体
union semun {
int val;
};
//初始化函数
//参数:semid:信号灯集ID
// semnum:信号灯编号
// val:要设置的初始值
//返回值:成功返回0;失败返回-1
int sem_init_val(int semid,int semnum,int val)
{
union semun myval;
myval.val = val;
if (-1 == semctl(semid,semnum,SETVAL,myval)) {
printf("初始化信号灯%d失败\n",semnum);
return -1;
}
return 0;
}
// p 操作
//参数:semid:信号灯集ID
// semnum:信号灯编号
//返回值:成功返回0;失败返回-1
int sem_p(int semid,int semnum)
{
struct sembuf mybuf;
mybuf.sem_num = semnum;//信号灯编号
mybuf.sem_op = -1;//申请资源
mybuf.sem_flg = 0;//阻塞方式
if (-1 == semop(semid,&mybuf,1)) {
printf("p操作失败\n");
return -1;
}
return 0;
}
// v操作
//参数:semid:信号灯集ID
// semnum:信号灯编号
//返回值:成功返回0;失败返回-1
int sem_v(int semid,int semnum)
{
struct sembuf mybuf;
mybuf.sem_num = semnum;//信号灯编号
mybuf.sem_op = 1;//申请资源
mybuf.sem_flg = 0;//阻塞方式
if (-1 == semop(semid,&mybuf,1)) {
printf("v操作失败\n");
return -1;
}
return 0;
}
int main(int argc, const char *argv[])
{
//生成一个共享内存使用的key值
key_t mykey = ftok("/home/linux/class/",'a');
if (-1 == mykey) {
printf("生成建值失败\n");
return -1;
}
key_t mykey1 = ftok("/home/linux/class/TCP/",'a');
if (-1 == mykey1) {
printf("生成建值失败\n");
return -1;
}
//创建或者打开信号灯集
int semid = semget(mykey1,2,IPC_CREAT|0664);
if (-1 == semid) {
perror("semget");
return -1;
}
//初始化信号灯集
sem_init_val(semid,0,1);
sem_init_val(semid,1,0);
while (1) {
sem_p(semid,0); //p操作
printf("请输入\n");
scanf("%s",buf);
sem_v(semid,1);
}
return 0;
}
************************************************************
#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun {
int val;
};
int sem_init_val(int semid,int semnum,int val)
{
union semun myval;
myval.val = val;
if (-1 == semctl(semid,semnum,SETVAL,myval)) {
printf("初始化信号灯%d失败\n",semnum);
return -1;
}
return 0;
}
int sem_p(int semid,int semnum)
{
struct sembuf mybuf;
mybuf.sem_num = semnum;
mybuf.sem_op = -1;
mybuf.sem_flg = 0;
if (-1 == semop(semid,&mybuf,1)) {
printf("p操作失败\n");
return -1;
}
return 0;
}
int sem_v(int semid,int semnum)
{
struct sembuf mybuf;
mybuf.sem_num = semnum;
mybuf.sem_op = 1;
mybuf.sem_flg = 0;
if (-1 == semop(semid,&mybuf,1)) {
printf("v操作失败\n");
return -1;
}
return 0;
}
int main(int argc, const char *argv[])
{
key_t mykey = ftok("/home/linux/class/",'a');
if (-1 == mykey) {
printf("生成建值失败\n");
return -1;
}
key_t mykey1 = ftok("/home/linux/class/TCP/",'a');
if (-1 == mykey1) {
printf("生成建值失败\n");
return -1;
}
int semid = semget(mykey1,2,IPC_CREAT|0664);
if (-1 == semid) {
perror("semget");
return -1;
}
while(1)
{
sem_p(semid,1); //p操作
printf("buf = %s\n",buf);
sem_v(semid,0);
}
return 0;
}
7.有名信号量
有名信号量一般用于两个进程间通信。
信号灯的编号从0开始。
操作方式与无名信号量一致,经典的PV操作,来实现进程间通信。
当多个进程间通信时不适用,因为要申请大量的信号灯。
有名信号量函数接口:
1)sem_open
头文件:#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
原型: sem_t *sem_open(const char *name,int oflag,mode_t mode,unsigned int value)
功能: 创建或者打开一个有名信号灯
参数: name:信号灯的名字
oflag:打开方式
0:直接打开(不需要后两个参数) O_CREAT O_EXCL
mode:权限
value:信号灯的初值
返回值:成功返回创建或打开的信号灯的地址;失败返回SEM_FAILED
2)sem_close
头文件:同上
原型: int sem_close(sem_t *sem)
功能: 关闭有名信号灯
参数: sem:有名信号灯的地址
返回值:成功返回0;失败返回-1
3)sem_unlink
头文件:同上
原型: int sem_unlink(const char *name)
功能: 删除有名信号灯
参数: name:有名信号灯的名字
返回值:成功返回0;失败返回-1
4)sem_post
头文件:同上
原型: int sem_post(sem_t *sem)
功能: 释放资源(V操作)
参数: sem:目标信号的
返回值:成功返回0;失败返回-1
5)sem_wait
头文件:同上
原型: int sem_wait(sem_t *sem)
功能: 申请资源(P操作)
参数: sem:目标信号灯
返回值:成功返回0;失败返回-1