本文记录进程之间不同通讯方式的学习过程以及程序案例,学习过程中得益于以下两篇优秀博文:
凉了!张三同学没答好「进程间通信」,被面试官挂了…(图文并茂,言简意骇,有助于概念理解)
进程间通信的六种常见方式(逻辑清晰,干净整洁,有程序源码)
管道使用简单,但效率较低,不适用于进程之间的频繁信息交流。
匿名管道pipe 是一种半双工的通信方式,只能在具有亲缘关系的进程间使用.进程的亲缘关系一般指的是父子关系。使用写入端口时关闭读取端口,使用读取端口时关闭写入端口,可以使用普通的read、write等函数进行读写操作,操作时遵循先进先出原则。
#include
int pipe(int pipefd[2]);
#include
#include
#include
#include
#include
#include
//匿名管道的创建与使用
int main()
{
int fd[2];
char buf[1024]={""};
int pid;
int states;
if(pipe(fd)==-1)
{
printf("fail!\n");
}
pid=fork();
if(pid<0)
{
printf("creat child fail!\n");
}
else if(pid>0)//父进程
{
printf("This is Father\n");
sleep(3);
close(fd[0]);//管道0负责读 1负责写
write(fd[1],"Hello from father",strlen("Hello from father"));
wait(&states);
}
else//子进程
{
printf("This is child\n");
close(fd[1]);
read(fd[0],buf,1000);
printf("read from father:%s\n",buf);
exit(0);
}
return 0;
}
命名管道FIFO,以一种特殊设备文件形式存在于文件系统中,有路径名与之相关联。因此,可以在无关的进程之间交换数据。
#include
#include
int mkfifo(const char *pathname, mode_t mode);
进程向管道写入
#include
#include
#include
#include
#include
#include
#include
#include
//有名管道的写入
int main()
{
char *str="message from fife";
int cnt=0;
int fd=open("flie",O_WRONLY);
printf("write open success\n");
while(1)
{
sleep(1);
write(fd,str,strlen(str));
if(cnt>5)
{
break;
}
cnt++;
}
close(fd);
return 0;
}
进程从管道读取
#include
#include
#include
#include
#include
#include
#include
#include
//有名管道的读取fifo
int main()
{
char buf[30]={0};
int nread=0;
if(mkfifo("flie",0600==-1) && errno!=EEXIST)
{
printf("mkfifo failed\n");
perror("why");
}
int fd=open("flie",O_RDONLY);
printf("open success\n");
while(1)
{
nread=read(fd,buf,30);
printf("read %d byte from fifo,context:%s\n",nread,buf);
}
close(fd);
return 0;
}
消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除,不必遵循先进先出原则,可以跟据数据类型进行读取。
消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在,而前面提到的匿名管道的生命周期,是随进程的创建而建立,随进程的结束而销毁。
但是,鉴于传输上限的限制消息队列不适合应用于大型数据的传输。同时,本质是用户态与内核态之间的信息拷贝,增大了工作量。
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
发送端
#include
#include
#include
#include
#include
struct msgbuf
{
long mtype;
char mtext[128];
};
int main()
{
struct msgbuf sendbuf={888,"This is message from quen"};
//1. int msgget(key_t key, int msgflg);
int msgid=msgget(0x1234,IPC_CREAT|0777);
if(msgid==-1)
{
printf("creat que failed!\n");
}
//2. int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//3. ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)
msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
return 0;
}
接收端
#include
#include
#include
#include
#include
#include
#include
struct msgbuf
{
long mtype;
char mtext[128];
};
int main()
{
struct msgbuf readbuf;
//1. int msgget(key_t key, int msgflg);
int msgid=msgget(0x1234,IPC_CREAT|0777);
if(msgid==-1)
{
printf("creat que failed!\n");
}
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)
msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),888,0);
printf("read from que:%s\n",readbuf.mtext);
return 0;
}
先发再读
#include
#include
#include
#include
#include
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf sendbuf={888,"message from send"};
struct msgbuf readbuf;
key_t key;
if((key = ftok(".",'z')) < 0){
printf("ftok error\n");
}
int msgId = msgget(key,IPC_CREAT|0777);
if(msgId == -1){
printf("get quen failed\n");
}
msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
printf("send over\n");
msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),999,0);
printf("read from get is:%s\n",readbuf.mtext);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
先读再发
#include
#include
#include
#include
#include
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf readbuf;
memset(readbuf.mtext, '\0', sizeof(readbuf.mtext));
struct msgbuf sendbuf={999,"thank for your reach"};
key_t key;
//获取key值
if((key = ftok(".",'z')) < 0){
printf("ftok error\n");
}
int msgId = msgget(key,IPC_CREAT|0777);
if(msgId == -1){
printf("get quen failed\n");
perror("why");
}
msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0);
printf("read from send is:%s\n",readbuf.mtext);
msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
消息队列的读取和写入的过程,都会有发生用户态与内核态之间的消息拷贝过程。那共享内存的方式,就很好的解决了这一问题。共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr);
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
发送数据
#include
#include
#include
//共享内存的创建
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//int shmdt(const void *shmaddr);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//int shmdt(const void *shmaddr);
int main()
{
key_t key;
key=ftok(".",1);
int shmid=shmget(key,1024*4,IPC_CREAT|0777);//创建,内存大小必须得是MB的整数倍
if(shmid==-1)
{
printf("failed!\n");
exit(-1);
}
char *shmadder=shmat(shmid,0,0);//映射挂载
printf("shmat ok\n");
strcpy(shmadder,"message frome send");
sleep(5);//等待五秒
shmdt(shmadder);//卸载
shmctl(shmid,IPC_RMID,0);//删除空间
printf("quit!!!\n");
return 0;
}
读取数据
#include
#include
#include
#include
//共享内存的创建
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//int shmdt(const void *shmaddr);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//int shmdt(const void *shmaddr);
int main()
{
key_t key;
key=ftok(".",1);
int shmid=shmget(key,1024*4,0);//获取
if(shmid==-1)
{
printf("failed!\n");
exit(-1);
}
char *shmadder=shmat(shmid,0,0);//映射挂载
printf("shmat ok\n");
printf("data:%s\n",shmadder);
shmdt(shmadder);//卸载
shmctl(shmid,IPC_RMID,0);//删除空间
printf("quit!!!\n");
return 0;
}
信号量与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。信号量基于操作系统的 P、V原子操作,用于进程间同步,若要在进程间传递数据需要结合共享内存。每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);
#include
#include
#include
#include
#include
// int semget(key_t key, int nsems, int semflg);
// int semctl(int semid, int semnum, int cmd, ...);
// int semop(int semid, struct sembuf *sops, size_t nsops);
union semun{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
//P操作,拿钥匙
void PGetKey(int semid)
{
struct sembuf sops;
sops.sem_num = 0;// 信号量组中对应的序号,0~sem_nums-1
sops.sem_op = -1;// 信号量值在一次操作中的改变量
sops.sem_flg = SEM_UNDO;// IPC_NOWAIT, SEM_UNDO
semop(semid, &sops, 1);
}
//V操作,放回钥匙
void VPutBackKey(int semid)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = 1;
sops.sem_flg = SEM_UNDO;
semop(semid, &sops, 1);
}
int main()
{
key_t key;
int semid;
if((key == ftok(".",6)) < 0)
{
printf("ftok error\n");
}
semid = semget(key , 1, IPC_CREAT|0666);//创造信号量集合 钥匙,数量为1
union semun sem;
sem.val = 0;//初始状态为没有钥匙
semctl(semid, 0, SETVAL, sem);//SETVAL初始化信号量为一个已知的值,这时就需要第四个参数
//0表示操作第一把钥匙
int pid = fork();
if(pid < 0)
{
printf("fork failed\n");
}else if(pid == 0)//子进程
{
printf("this is child\n");
VPutBackKey(semid);//子进程运行结束后把钥匙放回
}else//父进程
{
PGetKey(semid);//等子进程将钥匙放回后才会进行操作,保证子进程先执行(最开始无锁状态,父进程拿不到锁,进程堵塞)
printf("this is father\n");
VPutBackKey(semid);
semctl(semid, 0, IPC_RMID);//销毁钥匙
}
return 0;
}
信号signal属于软中断,属于异步通讯。终端用户输入了 ctrl+c 来中断程序,会通过信号机制(SIGINT)停止一个程序。
信号处理的三种方法分别是:忽略、捕捉和默认动作。
//接收函数,第二个参数指向信号处理函数
sighandler_t signal(int signum, sighandler_t handler);
//发送函数
int kill(pid_t pid, int sig);
接收
#include
#include
// typedef void (*sighandler_t)(int);
// sighandler_t signal(int signum, sighandler_t handler);
/*接受到信号后,让信号处理该函数*/
void handler(int signum)
{
printf("signum = %d\n",signum);
switch(signum){
case 2:
printf("SIGINT\n");
break;
case 9:
printf("SIGKILL\n");
break;
case 10:
printf("SIGUSR1\n");
break;
}
}
int main()
{
signal(SIGINT,handler);
signal(SIGKILL,handler);
signal(SIGUSR1,handler);
while(1);
return 0;
}
发送
#include
#include
#include
#include
// int kill(pid_t pid, int sig);
int main(int argc,char **argv)
{
int signum;
int pid;
char cmd[128]={0};
signum = atoi(argv[1]);//将字符型转为整型 //消息号
pid = atoi(argv[2]);//进程号
kill(pid,signum);//进程号,信号编号
// sprintf(cmd,"kill -%d%d",signum,pid);
// system(cmd);
printf("signum = %d,pid = %d\n",signum,pid);
return 0;
}
#include
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
};
//回调函数句柄sa_handler、sa_sigaction只能任选其一
接收端
#include
#include
#include
#include
#include
// int sigaction(int signum, const struct sigaction *act,
// struct sigaction *oldact);
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("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;//be able to get message
sigaction(SIGUSR1,&act,NULL);//注册信号
while(1);
return 0;
}
发送端
#include
#include
#include
#include
#include
#include
// int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int argc,char **argv)
{
int signum;
int pid;
union sigval value;
value.sival_int=100;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
sigqueue(pid,signum,value);
printf("send successful\n");
printf("pid=%d,done\n",getpid());
return 0;
}