前言
本文记录进程间通信的方式,每个方式都有具体使用的实例。
进程间通信的方式(重点)
管道、信号、信号量、共享内存、消息队列、套接字
无名管道
特点
(1)半双工(即数据只能在一个方向上流动),具有固定的读端和写端。
(2)只能用于具有亲缘关系的进程之间的通信(父子进程或兄弟进程)。
(3)可以看成是一个特殊的文件,对于它的读写也可以是write,read等函数,但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
#include <unistd.h>
int pipe(int fd[2]);//返回值:成功返回0,失败返回-1
例子
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd[2];
int pid;
char buf[128];
//int pipe(int pipefd[2]);
if(pipe(fd) == -1){
printf("creat pipe failed\n");
}
//pid_t fork(void);
pid = fork();
if(pid < 0){
printf("creat child failed\n");
}else if(pid > 0){
sleep(3);
printf("this is father\n");
close(fd[0]);
//ssize_t write(int fd, const void *buf, size_t count);
write(fd[1],"hello from father",strlen("hello from father"));
wait();
}else{
printf("this is child\n");
close(fd[1]);
read(fd[0],buf,128);
printf("read from father: %s\n",buf);
exit(0);
}
return 0;
}
//执行结果
this is child
this is father
read from father: hello from father
FIFO(命名管道)
FIFO,也称为命名管道,它是一种文件类型。
特点
(1)FIFO可以在无关的进程之间交换数据,与无名管道不同。
(2)FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
#include <sys/stat.h>
//返回值:成功返回0,出错返回-1
int mkfifo(const char *pathname, mode_t mode);
其中的mode参数与open函数中mode相同。一但创建了一个FIFO,就可以用一般的文件I/O函数操作它。
当open一个FIFO时,是否设置非阻塞标志(O_NONOBLOCK)的区别:
(1)若没有指定O_NONBLOCK(默认),只读open要阻塞到某个其他进程为写而打开此FIFO。类似的,只写open要阻塞到某个其他进程为读而打开它。
(2)若指定了O_NONBLOCK,则只读open立即返回。而只写open将出错返回-1 如果没有进程已经为读而打开该FIFO,其errno置ENXIO。
例子
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
int main()
{
char buf[30] = {0};
int n_read = 0;
//int mkfifo(const char *pathname, mode_t mode);
if((mkfifo("./file",0600) == -1)&& errno != EEXIST){
printf("mkfifo failed\n");
perror("why");
}
int fd = open("./file",O_RDONLY);
while(1){
n_read = read(fd,buf,30);
printf("read %dbyte,file message: %s\n",n_read,buf);
}
printf("open success\n");
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int cnt = 0;
char *str="this is message";
int fd = open("./file",O_WRONLY);
printf("write open success\n");
while(1){
write(fd,str,strlen(str));
sleep(1);
cnt++;
if(cnt == 5){
break;
}
}
close(fd);
return 0;
}
信号
进程间通信中唯一一个异步通信机制
异步:可以不按照代码的顺序去执行,当产生某种事件时再去处理
同步:按照顺序执行,且不可以去做其他的事情
查看信号的种类
kill -l
信号名 | 发出方式 | 进程接收后的动作 |
2) SIGINT | ctrl+c | 终止进程 |
3) SIGQUIT | ctrl+\ | 终止进程 |
20) SIGTSTP | ctrl+z |
暂停进程 |
14) SIGALRM | alarm函数发出 | 终止进程 |
17) SIGCHLD | 子进程结束时发给父进程 | 忽略 |
捕获信号
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:捕获信号,进程收到这个信号后去执行自定义的操作。
参数: int signum 指定捕获的信号 SIGINT或2
sighandler_t handler 当进程接收到这个信号后,执行的函数
信号的处理方式
(1)捕获信号:signal(SIGINT, func);
(2)忽略信号:signal(SIGINT, SIG_IGN);//SIG_IGN 代表忽略这个信号
(3)默认:不需要额外的操作,系统默认有一种执行方式
发送信号
kill函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数:pid_t pid,给谁发信号,pid号
int sig,发什么信号
14) SIGALRM信号
由alarm函数发出信号
案例
每隔1秒打印出系统时间,不可以用sleep(类似递归的思路)
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void func(int n)
{
time_t tm = time(0);//获取当前系统时间(以秒为单位)
printf("%s\n", ctime(&tm));//将 秒的时间 转换为 字符串
alarm(1);
}
int main()
{
alarm(1);//设置闹铃,1s之后发送一个SIGALRM信号到当前进程
signal(SIGALRM, func);
while(1);//保证进程活着
}
17) SIGCHLD信号
子进程结束时,给父进程发信号。
案例
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void func(int num)
{
printf("父进程:我知道了\n");
wait(NULL);//回收子进程资源
}
int main()
{
int pid = fork();
if (pid == 0)
{
//子进程
sleep(10);
printf("子进程:i am dead\n");
}
else if (pid > 0)
{
signal(SIGCHLD, func);
while (1)
{
printf("父进程:打麻将\n");
sleep(1);
}
}
}
消息队列
#include <sys/msg.h>
//创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
//添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const viod *ptr, size_t size, int flag);
//读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid,viod *pyr, size_t size, long type, int flag);
//控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msgid_ds *buf);
在以下两种情况下,msgget将创建一个新的消息队列:
- 如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
- key参数为IPC_PRIVATE。
函数msgrcv在读取消息队列时,type参数有以下几种情况:
- type == 0,返回队列中的第一个消息;
- type > 0,返回队列中消息类型为type的第一个消息;
- type < 0,返回队列中消息类型值小于或等于type绝对值的消息,如果有多个,取类型值最小的消息。
例子
/* msgGet.c */
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf readBuf;
struct msgbuf sendBuf = {988,"thank you for reach"};
//1、获取
//int msgget(key_t key, int msgflg);
int msgId = msgget(0x123,IPC_CREAT|0777);
if(msgId == -1 ){
printf("get quen failed");
}
//int msgget(key_t key, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
//2、读取消息
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
printf("read from quen:%s\n",readBuf.mtext);
//3、添加消息
msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
return 0;
}
/* msgSend.c */
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf readBuf;
struct msgbuf sendBuf = {888,"this is message from quen"};
//1、获取
int msgId = msgget(0x123,IPC_CREAT|0777);
if(msgId == -1 ){
printf("get quen failed");
}
//2、添加消息
msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
//3、读取消息
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);
printf("return from get:%s\n",readBuf.mtext);
return 0;
}
代码对比指令:vimdiff demo1.c demo2.c
共享内存
共享内存是允许两个或者多个进程访问同一块物理内存,实现数据的快速交互,所以是最快的进程间通信的方式。
容易出现互斥现象,使用互斥锁进行解决。
特点
共享内存是进程间通信里效率最高的。
使用步骤
(1)创建共享内存/打开 shmget();
(2)映射共享内存 shmat();
(3)使用共享内存,传输数据
(4)释放共享内存 shmdt();
(5)删除共享内存 shmctl();
查看共享内存
ipcs -m //ipc进程间通信的缩写 s是list查看 m是memory内存
ipcrm -m (共享内存id) //删除掉共享内存
相关函数介绍
shmget函数
//创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int shmflg);
参数: key_t key 共享内存的key,如果对应key的共享内存不存在,则创建;反之则打开。
如果key填入 IPC_PRIVATE 代表当前共享内存是私有的,只能在父子进程间使用;
如果key填入其他值,代表是公有的,可以在不相干的进程间使用。
size_t size 共享内存的大小(应该是page size的整数倍 4096Byte = 4KB)
int shmflg 权限,如果创建公有的共享内存,需要加上 IPC_CREAT
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
//int shmget(key_t key, size_t size, int shmflg); 函数原型
int main()
{
//创建私有
//int shmid = shmget(IPC_PRIVATE,1024*4,0666);
//创建公有
int shmid = shmget(1111,1024*4,IPC_CREAT|0666);
printf("shmid = %d\n",shmid);
return 0;
}
shmat函数
//映射共享内存
//连接共享内存到当前的进程地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
相当于malloc函数的用法
参数:int shmid shmget的返回值
const void *shmaddr 指定的映射后的逻辑地址,一般填NULL,代表进程自己分配
int shmflg 一般为0,代表可读可写
返回值:共享内存的首地址
shmdt函数
//解除映射关系,断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(const void *shmaddr);
相当于free函数的用法,参数是shmat函数的返回值
shmctl函数
//控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:删除共享内存
参数:shmid shmget函数的返回值
int cmd IPC_RMID
struct shmid_ds *buf NULL
例子
/* shmwrite.c */
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int shmid;
char *shmaddr;
key_t key;
//key_t ftok(const char *pathname, int proj_id);
key = ftok(".",1);
//int shmget(key_t key, size_t size, int shmflg);
shmid = shmget(key,1024*4,IPC_CREAT|0666);
if(shmid == -1){
printf("shmget failed\n");
exit(-1);
}
//void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr = shmat(shmid,0,0);
printf("shmat ok\n");
strcpy(shmaddr,"this is zhang");
sleep(5);
//int shmdt(const void *shmaddr);
shmdt(shmaddr);
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl(shmid,IPC_RMID,0);
printf("quit\n");
return 0;
}
/* shmread.c */
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int shmid;
char *shmaddr;
key_t key;
//key_t ftok(const char *pathname, int proj_id);
key = ftok(".",1);
//int shmget(key_t key, size_t size, int shmflg);
shmid = shmget(key,1024*4,0);
if(shmid == -1){
printf("shmget failed\n");
exit(-1);
}
//void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr = shmat(shmid,0,0);
printf("shmat ok\n");
printf("data: %s\n",shmaddr);
//int shmdt(const void *shmaddr);
shmdt(shmaddr);
printf("quit\n");
return 0;
}
查看共享内存的指令:ipcs -m
删除掉共享内存:ipcrm -m (key值)
ftok函数说明
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
//例子
int key = ftok("/bin", 1);
功能:根据目录,生成key值
参数1:有效路径名
参数2:编号
返回值:key值
信号量
原型
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
例子
#include <stdio.h>
#include <signal.h>
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);
}
}
int main()
{
struct sigaction act;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
printf("pid = %d\n",getpid());
/*int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);*/
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
#include <stdio.h>
#include <signal.h>
int main(int argc, char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
union sigval value;
value.sival_int = 100;
//int sigqueue(pid_t pid, int sig, const union sigval value);
sigqueue(pid,signum,value);
printf("done\n");
return 0;
}