进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、信号、共享内存、Socket。
一、管道
无名管道
特点
半双工,具有固定的读端和写端;
每次的读写操作,需要打开读端关闭写端,或者打开写端关闭读端;
它只能用于具有父子进程或者兄弟进程之间;
对于它的读写可以直接使用普通的read、write等函数;
它不属于任何文件系统,并且只存在于内存。
API原型
SYNOPSIS
#include <unistd.h>
int pipe(int pipefd[2]);
API参数:一个int类型长度为2的数组,[0]为读区,[1]写区;
API返回值:成功返回0,失败返回-1;
demo编写
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int fd[2]; //fd[0]读区,fd[1]写区
int pid;
char readBuf[128];
//创建无名管道
if(pipe(fd) == -1){
printf("creat pipe failed...\n");
}
//创建子进程
pid = fork();
//返回值为-1时,创建失败
if(pid < 0){
printf("creat child failed...\n");
}
//父进程,返回值是子进程的进程号
else if(pid > 0){
sleep(3);
printf("this is father \n");
//关闭父进程的读区
close(fd[0]);
//将内容写道父进程的写区里面
write(fd[1],"hello form father",strlen("hell form father"));
wait(NULL);
}
//子进程,返回值是0
else{
printf("this is child \n");
//关闭写区
close(fd[1]);
//读取子进程读区的内容到readBuf[]里面
read(fd[0],readBuf,128);
printf("read from father : %s\n",readBuf);
exit(0);
}
return 0;
}
demo运行
有名管道/fifo
特点
fifo可以在无关的进程之间交换数据,与无名管道不同;
fifo有路径名与之相关联,以一种特殊设备文件存在于内存;
类似于在进程中使用文件中来传输数据,在数据读出是,fifo管道中同时清理数据清理,有着“先进先出”的原则。
API原型
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
API参数
pethname:管道的路径名称
mode:与open函数中的 mode 相同
API返回值
成功:返回0;
出错:返回-1。
demo编写(创建管道)
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
int main()
{
//创建一个有名管道,返回值为-1时出错
if(mkfifo("./file",0600) == -1){
//通过捕捉错误码,进一步筛选
if(errno != EEXIST){
printf("mkfifo failed!\n");
perror("why");
}
else if(errno == EEXIST){
printf("file had\n");
}
}
//否则输出创建成功
else{
printf("mkfifo success\n");
}
return 0;
}
demo运行
demo编写(读写双方程序读写)
//read.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
//int mkfifo(const char *pathname, mode_t mode)
int main()
{
char readBuf[1024] = {0};
int fd;
while(1){
//以只读的方式打开fifo,进程会进入阻塞,当其他进程进行写的时候,才会往下跑
fd = open("./file",O_RDONLY);
printf("open success\n");
int n_read = read(fd,readBuf,1024);
printf("read %d byte from fifo,context : %s\n",n_read,readBuf);
close(fd);
}
return 0;
}
//write.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
int main()
{
int cnt = 0;
char *str = "this message from fifo";
int fd = open("./file",O_WRONLY);
printf("write open success\n");
while(cnt++ != 5){
sleep(1);
printf("%d\n",cnt);
write(fd,str,strlen(str));
}
close(fd);
return 0;
}
demo运行
二、消息队列
特点
消息队列,消息的链接表,存放在内核当中,一个消息队列由一个标识符来识别;
消息队列,是面向于记录的,具有特定的格式和优先级;
消息队列,独立于发送和接收进程。进程终止时,消息队列及其内容不会被删除;
消息队列,支持随机查询,也可以按照消息的类型查询。
API原型
SYNOPSIS
#include <sys/msg.h>
//创建或打开消息队列
int msgget(key_t key, int msgflg);
//添加消息
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
//读取消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//控制消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
API参数
msgget(创建或者打开消息队列)
key:消息队列对象的key(标识符),通常用ftok获取;
msgflg:消息队列的访问权限,msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符;
返回值:返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1。
msgrcv(读区消息)
msqid:消息队列的key(标识符);
msgp:读取消息的结构体;
struct msgbuf{
long mgytpe;//消息类型
char mtext; //消息正文
}
msgsz:要读取消息的大小;
msgtyp:
0:接收第一个消息
>0:接收类型等于msgtyp的第一个消息
<0:接收类型等于或小于msgtyp绝对值的第一个消息
msgflg:
0:阻塞,没有该消息就会一直等待阻塞;
IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG;
IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息;
IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息。
返回值:实际读取到消息的实际长度。
msgsnd(写入消息)
msqid:消息队列的key(标识符);
msgp:存放消息队列的结构体,与msgrcv发送的类型相同;
msgsz:要写入消息的大小;
msgflg:
0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列;
IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回;
IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程;
返回值:成功返回0,失败返回-1,并设置errno。
msgctl(控制消息队列)
msqid:消息队列的key(标识符);
msd:
IPC_STAT:获得msgid的消息队列头数据到buf中;
IPC_STAT:获得msgid的消息队列头数据到buf中;
buf:消息队列管理结构体.
返回值:成功返回0,失败返回-1,并设置errno。
demo编写(双方)
//msgGet.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct msgBuf{
long mtype;
char mtext[128];
}msgBuf;
int main()
{
msgBuf readBuf;
msgBuf sendBuf = {988,"thank you for reach"};
key_t key;
key = ftok(".",123); //
printf("key = %x\n",key);
int msgID = msgget(key,IPC_CREAT|0777);
if(msgID == -1){
printf("get que failed\n");
}
msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),888,0);
printf("read from que : %s\n",readBuf.mtext);
msgsnd(msgID,&sendBuf,strlen(sendBuf.mtext),0);
printf("send over\n");
msgctl(msgID,IPC_RMID,NULL);
return 0;
}
//msgsend.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct msgBuf{
long mtype;
char mtext[128];
}msgBuf;
int main()
{
msgBuf sendBuf = {888,"this is message from quen"};
msgBuf readBuf;
key_t key;
//"."指的是当前文件夹,123为子序列号
key = ftok(".",123);
printf("key = %x\n",key);
int msgID = msgget(key,IPC_CREAT|0777);
if(msgID == -1){
printf("get que failed\n");
}
msgsnd(msgID,&sendBuf,strlen(sendBuf.mtext),0);
printf("send over\n");
msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),988,0);
printf("return from get : %s\n",readBuf.mtext);
msgctl(msgID,IPC_RMID,NULL);//释放掉队列
return 0;
}
demo运行
三、共享内存
特点
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取;
因为多个进程可以同时操作,所以需要进行同步;
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
API原型
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
//创建共享内存
int shmget(key_t key, size_t size, int shmflg);
//映射共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
//断开共享内存连接
int shmdt(const void *shmaddr);
//控制共享内存
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
API参数
shmget(创建共享内存)
key:创建内存的key(标识符),可以通过ftok生成;
size:共享内存的大小;
shmflg:读写的权限,与open的mode相同;
返回值:成功返回共享内存的ID,失败返回-1并设置errno。
shmat(映射共享内存)
shmid:共享内存的ID;
shmaddr
0,系统自动分配地址,一般使用该项;
非0,没有指定SHM_RND则连接到addr所指定的地址上;
非0,指定SHM_RND则连接到shmaddr -(shmaddr mod SHMLAB)所表示的地址上;
shmflg:SHM_RDONLY(只读),
SHM_WRONLY(只写),
SHM_RDWR(可读可写);
返回值:成功返回指向共享内存的指针,失败返回-1并设置errno。
shmdt(断开共享内存)
shmaddr:共享内存的指针;
返回值:成功返回0,失败返回-1。
shmctl(控制共享内存)
shmid:共享内存的ID
cmd:
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中;
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内;
IPC_RMID:删除这片共享内存;
struct shmid_ds *buf:内存管理的结构体;
返回值:成功返回0,失败返回-1并设置errno。
demo编写
//shmw.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
char *shmaddr;
key_t key;
key = ftok(".",123);
//创建共享内存
int shmID = shmget(key, 1024*4, IPC_CREAT|0666);
if(shmID == -1){
printf("shmget failed!\n");
}
//映射共享内存
shmaddr = shmat(shmID, 0, 0);//第一个0,是linux内核自动分配内存,
//第二个0,是设置成可读可写的模式
//写入共享内存
strcpy(shmaddr,"ErHui");
sleep(5);
//卸载共享内存
shmdt(shmaddr);
//去掉共享内存
shmctl(shmID,IPC_RMID,0);
printf("quit");
return 0;
}
//shmr.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
char *shmaddr;
key_t key;
key = ftok(".",123);
//创建共享内存
int shmID = shmget(key, 1024*4, 0);
if(shmID == -1){
printf("shmget failed!\n");
}
//映射共享内存
shmaddr = shmat(shmID, 0, 0);//第一个0,是linux内核自动分配内存,
//第二个0,是设置成可读可写的模式
//读取
printf("data : %s\n",shmaddr);
//卸载共享内存
shmdt(shmaddr);
printf("quit");
return 0;
}
demo运行
四、信号
特点:软中断的信号
可以通过kill -l命令查看系统所有的信号。 目前Linux支持64种信号。 信号分为非实时信号 (不可靠信号)和实时信号(可靠信号)两种类型,对应于 Linux 的信号值为 1-31 和 34-64。
API原型
SYNOPSIS
#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);
API参数
signum:信号的类型
handler:信号关联的动作
SIG_ING:忽略该信号
SIG_DFL:恢复信号的系统默认处理,不写就是系统默认操作;
sighandler_t类型的函数指针。
demo编写()
//demo1是去捕捉这些信号
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
//typedef void (*sighandler_t)(int);函数指针
//sighandler_t signal(int signum, sighandler_t handler);
//强制杀死进程
//1.用 ps -aux|grep a.out ,查看当前运行的进程的ID号
//2.用 kill -9 [ID号],即可
//3.或者用 kill -SIGKILL [ID号],
void handler(int signum)
{
printf("get signum=%d\n",signum);
switch(signum){
case SIGINT:
printf("SIGINT\n");
break;
case SIGKILL:
printf("SIGKILL\n");
break;
default:
break;
}
}
int main()
{
printf("pid : %d",getpid())
signal(SIGINT,handler);
signal(SIGKILL,handler);
while(1);
return 0;
}
//demo2负责将上面这个进程杀死
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
//int kill(pid_t pid,int sig)
int main(int argc,char **argv)
{
int signum;
int pid;
char cmd[128] = {0};
pid = atoi(argv[1]);
signum = atoi(argv[2]);// 字符串转换
printf("num = %d , pid = %d\n",signum,pid);
sprintf(cmd,"kill -%d %d",signum,pid);//字符串格式化
//kill(pid, signum);
system(cmd);//使用system函数执行脚本
printf("send signal ok\n");
return 0;
}
五、信号量
特点:
API原型
SYNOPSIS
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval{
int sival_int;
void *sival_ptr;
};
int sigaction(int signum,const struct sigaction *act,
struct sigaction *oldact);
struct sigaction{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused*/
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int inglibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address(since Linux 2.6.32) */
}
API参数
sigqueue:(向一个进程发送信号)
pid:进程的pid;
sig:发送的信号;
const union sigval value:联合结构体;
返回值:成功0,出错返回-1,并且设置errno值。
sigaction:
signum:要捕捉的信号;
const struct sigaction *act:接收到信号之后对信号进行处理的结构体
struct sigaction *oldact:接收到信号之后,保存原来对此信号处理的各种方式与信号(可用来
做备份)。如果不需要备份,此处可以填NULL;
sa_handler和sa_sigaction函数一次只能使用其中一个
如果只是想处理信号,但是不获取信号信息,就使用sa_handler函数;
如果想要处理信号,并且获取信号的各种信息,就使用sa_sigaction函数;
返回值:成功0,出错返回-1,并且设置errno值。
demo编写
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
//接收端
//void (*sa_sigaction)(int, siginfo_t *, void *)
void handler(int signum, siginfo_t *info, void *context)
{
printf("get signum %d\n",signum);
if(context != NULL){
printf("get data=%d\n",info->si_int);
printf("get data=%d\n",info->si_value.sival_int);
printf("from : %d\n",info->si_pid);
}
}
int main()
{
struct sigaction act;
printf("pid : %d\n",getpid());
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
//发送端
//int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int argc,char **argv)
{
int signum = atoi(argv[1]);
int pid = atoi(argv[2]);
union sigval value;
value.sival_int = 100;
sigqueue(pid, signum, value);
printf("%d : done\n",getpid());
return 0;
}