进程间通信方式IPC
1).早期的进程间通信:
无名管道(pipe),有名管道(fifo),信号(signal)
2).system V IPC:
共享内存(share memory),消息队列(message queue),信号等集(semaphore set)
3).BSD:
套接字(socket)
1.无名管道
三种通信模式:
单工(Simplex)
单工通信是指数据传输只能在一个方向上进行,通信双方中一方固定为发送端,另一方固定为接收端。这种模式无法实现双向交互。
典型应用场景包括广播、电视信号传输或键盘向计算机发送指令。发送端无法接收数据,接收端也无法发送数据。
双工(Duplex)
双工通信允许数据在双方之间同时双向传输,类似于电话通话。根据实现方式,分为以下两种:
全双工(Full-Duplex)
通信双方可以同时发送和接收数据,需要两条独立的物理通道或通过技术手段实现双向并发传输。例如网线中的双绞线或光纤通信。
半双工(Half-Duplex)
允许双向传输,但不能同时进行。同一时间只能有一方发送数据,典型例子是对讲机或传统集线器网络。需要协议控制传输方向切换。
特性 单工 半双工 全双工 方向性 单向固定 双向交替 双向同时 通道要求 1条 1条 2条或复用技术 延迟 无反向延迟 存在切换延迟 无延迟 典型协议 HDMI CAN总线 TCP/IP
1.1 特点
(1)只能用于具有亲缘关系的进程之间才能通信
(2)半双工的通信模式,具有固定的读端fd[0]和写端fd[1]。
(3)管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数。
(4)管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符 fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。
(5)管道内没有数据的时候读阻塞
(6)管道内写满64k数据的时候写阻塞
(7)管道满后至少读出4k才能继续写操作
(8)关闭读端,写操作时会导致管道破裂
1.2 函数接口
int pipe(int fd[2])
头文件:#include <unistd.h>
功能:创建无名管道
参数:文件描述符 fd[0]:读端 fd[1]:写端
返回值:成功 0
失败 -1
1.3 练习
1.尝子进程向管道写数据,父进程从管道读数据:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main(int argc, const char *argv[])
{int fd[2] = {0};if(pipe(fd) < 0){perror("pipe err");return -1;}int pid = fork(); //两个进程进行pipe通信if(pid < 0){perror("fork err");exit(-1);}else if(pid == 0){waitpid(pid,NULL,0);close(fd[0]); //关闭读端write(fd[1],"hello",5); //向管道写入数据close(fd[1]); //关闭写端exit(0);}else{close(fd[1]); //关闭写端char buf[32] = "";read(fd[0],buf,32); //读取数据printf("%s\n",buf); close(fd[0]); //关闭读端wait(NULL); //等待子进程结束,并回收子进程资源exit(0);}//单个进程内对无名pipe管道内写入数据,读取数据/*write(fd[1],"hello",5); //写入数据char buf[32] = "";read(fd[0],buf,32); //读取数据printf("%s\n",buf); printf("%d %d\n",fd[0],fd[1]); //一般是3和4,因为0,1,2是输入,输出,错误标识符*/return 0;
}
为什么子进程我关了读端又往管道输入数据,管道没有破裂?因为这个时候我是关了子进程和管道的读端,但是我没有关父进程和管道的读端,还是有读端存在,所以不会破裂。
2.从父进程循环向管道输入数据,子进程循环从管道读取数据,直到输入quit,程序结束。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/types.h>int main(int argc, const char *argv[])
{int fd[2] = {0};if(pipe(fd) < 0){perror("pipe err");return -1;}char buf[32] = ""; //定义在创建子进程前面,因为子进程会继承父进程在创建子进程前的资源int pid = fork();if(pid < 0){perror("fork err");exit(-1);}else if(pid == 0){ //子进程close(fd[1]);while(1){read(fd[0],buf,32); //读取数据if(!strcmp(buf,"quit"))break;printf("%s\n",buf);}close(fd[0]);exit(0);}else{ //父进程close(fd[0]);while(1){scanf("%s",buf);write(fd[1],buf,strlen(buf)+1); //写入数据if(!strcmp(buf,"quit") )break;}close(fd[1]);wait(NULL); //等待释放子进程资源exit(0);}return 0;
}
2.有名管道
2.1 特点
(1)有名管道可以使互不相关的两个进程互相通信。
(2)有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中。但是读写数据不会存在文件中,而是在管道中(也就是内核空间中)。
(3)进程通过文件IO来操作有名管道
(4)不支持如lseek() 操作
(5)有名管道遵循先进先出规则
2.2 函数接口
int mkfifo(const char *filename,mode_t mode);
头文件:#include <sys/types.h>
#include <sys/stat.h>功能:创健有名管道
参数:filename:有名管道文件名
mode:权限
返回值:成功:0
失败:-1,并设置errno号
errno也需要头文件:#include <errno.h>
注意对错误的处理方式:
如果错误是file exist时,注意加判断,如:if(errno == EEXIST)
3.信号
kill -l: 显示系统中的信号
kill -num PID: 给某个进程发送信号
3.1概念:
在Linux系统中,信号是进程间通信的一种机制,用于通知进程发生了某种事件。C语言通过标准库和系统调用提供了信号处理的功能。
3.2特点
同步操作指的是任务按顺序执行,必须等待前一个任务完成后才能开始下一个任务。这种方式会阻塞当前线程,直到任务完成。
异步操作指的是任务可以同时进行,不需要等待前一个任务完成。这种方式不会阻塞当前线程,任务完成后通过回调、事件或通知机制处理结果。
(1)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式(就是你的进程在运行,没关系,我可以直接和它通信,命令进程做一些动作)
(2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
(3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
3.3 信号的响应方式
(1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
(2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
(3)执行缺省操作:Linux对每种信号都规定了默认操作(就比如当进程向一个读端已关闭的管道写入数据时,操作系统会向写入进程发送 SIGPIPE 信号,缺省行为是终止进程)
3.4信号种类
常用:
SIGINT(2):中断信号,Ctrl-C 产生,用于中断进程
SIGQUIT(3):退出信号, Ctrl-\ 产生,用于退出进程并生成核心转储文件
SIGKILL(9):终止信号,用于强制终止进程。此信号不能被捕获或忽略。
SIGALRM(14):闹钟信号,当由 alarm() 函数设置的定时器超时时产生。
SIGTERM(15):终止信号,用于请求终止进程。此信号可以被捕获或忽略。termination
SIGCHLD(17):子进程状态改变信号,当子进程停止或终止时产生。
SIGCONT(18):继续执行信号,用于恢复先前停止的进程。
SIGSTOP(19):停止执行信号,用于强制停止进程。此信号不能被捕获或忽略。
SIGTSTP(20):键盘停止信号,通常由用户按下 Ctrl-Z 产生,用于请求停止进程。
3.5函数接口
3.4.1信号发送和挂起
特性 | 阻塞 | 挂起 |
---|---|---|
触发原因 | 等待资源或事件(如I/O完成) | 外部干预(如操作系统、用户) |
控制方 | 进程自身行为 | 操作系统或用户请求 |
状态 | 等待状态(Waiting) | 挂起状态(Suspended) |
内存位置 | 保持在内存中 | 可能被换出到磁盘 |
恢复方式 | 资源就绪或事件发生自动恢复 | 需要显式恢复命令 |
持续时间 | 通常较短 | 可能很长 |
资源占用 | 占用内存 | 不占用或少量占用内存 |
典型场景 | 管道读写等待、锁获取 | 系统负载均衡、调试 |
int kill(pid_t pid, int sig);
头文件:#include <signal.h>
功能:信号发送
参数:pid:指定进程
sig:要发送的信号
返回值:成功 0
失败 -1
int raise(int sig);
头文件:#include <signal.h>
功能:进程向自己发送信号
参数:sig:信号
返回值:成功 0
失败 -1
相当于:kill(getpid(),sig);
int pause(void);
头文件:#include <unistd.h>
功能:用于将调用的进程挂起,直到收到被捕获处理的信号为止。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, const char *argv[])
{//kill(getpid(),SIGKILL); //杀死当前进程//raise(SIGKILL); //杀死当前进程pause();printf("ok\n");return 0;
}
3.4.2定时器
unsigned int alarm(unsigned int seconds)
头函数:#include <unistd.h>
功能:在进程中设置一个定时器。当定时器指定的时间到了时,它就向进程发送SIGALARM信号。
参数:seconds:定时时间,单位为秒
返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
注意:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替。
常用操作:取消定时器alarm(0),返回旧闹钟余下秒数。
#include <stdio.h>
#include <unistd.h>int main(int argc, const char *argv[])
{printf("%d\n",alarm(9)); //设置9秒闹钟,返回值是零sleep(3);printf("%d\n",alarm(2)); //返回值是之前闹钟剩余时间,是9-3pause();return 0;
}
3.4.3信号处理函数
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
头文件:#include <signal.h>
功能:信号处理函数
参数:signum:要处理的信号
handler:信号处理方式
SIG_IGN:忽略信号 (忽略 ignore)
SIG_DFL:执行默认操作 (默认 default)
handler:捕捉信号 (handler为函数名,可以自定义)
void handler(int sig){} //函数名可以自定义, 参数为要处理的信号
返回值:成功:设置之前的信号处理方式
失败:-1
#include <stdio.h>
#include <signal.h>void handler(int sig){printf("\nctrl + c:%d\n",sig);
}
int main(int argc, const char *argv[])
{signal(SIGINT,handler); //2signal(SIGQUIT,handler); //3while(1);return 0;
}
根据这个练习我简单讲一下这个函数逻辑:这个函数传入的参数是一个Int类型数据和一个函数指针,我们自定义一个函数handler(),传入参数就是void (*sighandler_t)(int) = handler,这样就可以把函数传入另一个函数,然后在另一个函数调用它,你就可以在handler函数内写上你想让进程接收到某个信号signum后进行你指定的操作,handler函数传入的参数也是固定的,能识别你前面的信号signum,在handler内可以使用这个信号值,就如代码中我们成功打印了这个信号的num。(把函数当做参数传入,然后函数写上你接收到信号后想要进程进行的操作)
练习
用信号的知识实现司机和售票员问题。
(1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)
(2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
(3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
(4)司机等待售票员下车,之后司机再下车。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
int fatherpid,childpid;void driver(int sig){if(sig == SIGUSR1){ //识别到SIGUSR1信号后打印let's gogogoprintf("\nlet's gogogo\n");}else if(sig == SIGUSR2){ //识别到SIGUSR2信号后打印stop the busprintf("\nstop the bus\n");}else if(sig == SIGTSTP){ //识别到SIGTSTP信号后向子进程(售货员)发送SIGUSR1信号kill(childpid,SIGUSR1);}
}
void salar(int sig){if(sig == SIGINT){ //识别到SIGINT信号后向父进程(司机)发送SIGUSR1信号kill(fatherpid,SIGUSR1);}else if(sig == SIGQUIT){ //识别到SIGQUIT信号后向父进程发送SIGUSR2信号kill(fatherpid,SIGUSR2);}else if(sig == SIGUSR1){ //识别到SIGUSR1信号后打印please get off the bus,并且退出子进程printf("\nplease get off the bus\n");exit(0);}
}int main(int argc, const char *argv[])
{fatherpid = getpid();childpid = fork();if(childpid < 0){perror("fork err");exit(0);}else if(childpid == 0){ //子进程充当售货员signal(SIGINT,salar); //售货员对信号SIGINT处理(ctrl c)signal(SIGQUIT,salar); //售货员对信号SIGQUIT处理(ctrl \)signal(SIGUSR1,salar); //售货员对信号SIGTSTP处理signal(SIGTSTP,SIG_IGN); //售货员要忽略键盘停止信号(ctrl z)while(1);}else{ //父进程充当司机signal(SIGUSR1,driver); //司机对信号SIGUSR1处理signal(SIGUSR2,driver); //司机对信号SIGUSR2处理signal(SIGTSTP,driver); //司机对信号SIGTSTP处理(ctrl z)signal(SIGINT,SIG_IGN); //司机要忽略中断信号(ctrl c)signal(SIGQUIT,SIG_IGN); //司机要忽略退出信号(ctrl \)wait(NULL); //等待子进程结束(售货员不下车司机不下车)}return 0;
}
4.共享内存
4.1 概念&特点:
(1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。
(2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程
将其映射到自己的私有地址空间。进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
(3)由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等
用户级页表定义
用户级页表是每个用户进程独有的数据结构,用于将进程的虚拟地址空间映射到物理内存地址。它由操作系统创建和管理,但对用户进程透明。
4.2 实现步骤
(1)创建key值
(2)创建或打开共享内存
(3)映射共享内存到用户空间
(4)撤销映射
(5)删除映射
4.3 函数接口
key_t ftok(const char *pathname, int proj_id);
头文件:#include <sys/ipc.h>
#include <sys/types.h>
功能:创建出来的具有唯一映射关系的一个key值,帮助操作系统用来标识一块共享内存
参数:Pathname:已经存在的可访问文件的名字
Proj_id:一个字符(因为只用低8位)
返回值:成功:key值
失败:-1
int shmget(key_t key, size_t size, int shmflg);
头文件:#include <sys/ipc.h>
#include <sys/shm.h>
功能:创建或打开共享内存
参数:key 键值
size 共享内存的大小
shmflg IPC_CREAT|IPC_EXCL|0777
作用 行为 IPC_CREAT
如果 IPC 对象不存在,则创建它
1.当对象不存在时:创建新对象
2.当对象已存在时:返回现有对象的标识符
IPC_EXCL
与 IPC_CREAT
配合使用,确保创建新对象1.当对象不存在时:创建新对象
2.当对象已存在时:失败,返回错误
0777(权限模式) 设置 IPC 对象的访问权限 0 : 特殊前缀(八进制)
7 (rwx): 所有者权限
7 (rwx): 组权限
7 (rwx): 其他用户权限
返回值:成功 shmid
出错 -1
注意对错误的处理方式:
如果错误是file exist光打开共享内存不用设IPC_CREAT|IPC_EXCL了,加判断,如:if(errno == EEXIST)
void *shmat(int shmid,const void *shmaddr,int shmflg); //attaches
头文件:#include <sys/types.h>
#include <sys/shm.h>
功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
参数:shmid 共享内存的id号
shmaddr 一般为NULL,表示由系统自动完成映射
如果不为NULL,那么有用户指定
shmflg:SHM_RDONLY就是对该共享内存只进行读操作
0 :可读可写
返回值:成功:完成映射后的地址,
出错:-1(地址)
用法:if((p = (char *)shmat(shmid,NULL,0)) == (char *)-1)
int shmdt(const void *shmaddr); //detaches
头文件:#include <sys/types.h>
#include <sys/shm.h>
功能:取消映射
参数:要取消的地址
返回值:成功0
失败的-1
int shmctl(int shmid,int cmd,struct shmid_ds *buf); //control
头文件:#include <sys/ipc.h>
#include <sys/shm.h>
功能:(删除共享内存),对共享内存进行各种操作
参数:shmid 共享内存的id号
cmd IPC_STAT 获得shmid属性信息,存放在第三参数
IPC_SET 设置shmid属性信息,要设置的属性放在第三参数
IPC_RMID:删除共享内存,此时第三个参数为NULL即可
buf shmid所指向的共享内存的地址,空间被释放以后地址就赋值为null
返回:成功0
失败-1
用法:shmctl(shmid,IPC_RMID,NULL);
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/shm.h>
#include <errno.h>int main(int argc, const char *argv[])
{//创建唯一的映射关系的key值key_t key;key = ftok("2",'a');if(key < 0){perror("ftok err");return -1;}printf("sucess!\n");//创建或者打开共享内存int shmid = shmget(key,128,IPC_CREAT|IPC_EXCL|0777); //创建共享内存if(shmid < 0){//当共享内存存在的时候会报错-1if(errno == EEXIST){ shmid = shmget(key,128,0777); //打开共享内存}else{perror("shmid err");return -1;}}printf("%d\n",shmid); //打印//映射共享内存到进程所在的地址空间int *p = (int *)shmat(shmid,NULL,0); //这个指针的类型是int,当然你也可以让他是别的类型(char float等)if(p == (int *) - 1){perror("shmat err");return -1;}scanf("%d",p);printf("%d\n",*p);//取消映射shmdt(p);//删除映射shmctl(shmid,IPC_RMID,NULL);return 0;
}
4.4 相关命令:
ipcs -m: 查看系统中的共享内存
ipcrm -m shmid:删除共享内存
ps: 可能不能直接删除掉还存在进程使用的共享内存。
这时候可以用ps -ef对进程进行查看,kill掉多余的进程后,再使用ipcs查看。
4.5 练习
两个进程实现通信,一个进程循环从终端输入,另一个进程循环打印,当输入quit时结束
input.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ipc.h>
#include <string.h>
typedef struct share{ //使用结构体,如果不使用结构体就无法让输出进程知道输入进程输入的数据是最新的还是原来的,输出进程就会一直循环输出int flag; //是否输出的标志,1的时候代表共享内存的数据是新输入的,就不再输入,需要等待输出;0的时候代表共享内存中的数据是输出过的,需要再输入char msg[64];
}*share;
int main(int argc, const char *argv[])
{key_t key = ftok("2",'a');if(key < 0){perror("ftok err");return -1;}int shmid = shmget(key,128,IPC_CREAT | IPC_EXCL | 0777);if(shmid < 0){if(errno == EEXIST)shmid = shmget(key,128,0777);else{perror("shmget err");return -1;}}printf("shmid:%d\n",shmid);share p = (share)shmat(shmid,NULL,0);if(p == (share)-1){perror("shmat err");return -1;}p->flag = 0; //初始化标志while(1){ if(p->flag == 0){ //当标志为0,输入数据fgets(p->msg,sizeof(p->msg),stdin);if(p->msg[strlen(p->msg) - 1] == '\n')p->msg[strlen(p->msg) - 1] = '\0';//scanf("%s",p->msg); //scanf有缺陷,不能输入空格,只能输入一个字符串,如果输入两个字符串,会分开输入,输出进程就会分两行输出这两个字符串p->flag = 1;if(strcmp(p->msg,"quit") == 0)break;}}
//结束映射shmdt(p);shmctl(shmid,IPC_RMID,NULL);return 0;
}
output.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ipc.h>
#include <string.h>
typedef struct share{ //和输入函数一样,不然数据不一致无法识别共享内存中的数据int flag;char msg[64];
}*share;
int main(int argc, const char *argv[])
{key_t key = ftok("2",'a');if(key < 0){perror("ftok err");return -1;}int shmid = shmget(key,128,IPC_CREAT | IPC_EXCL | 0777);if(shmid < 0){if(errno == EEXIST)shmid = shmget(key,128,0777);else{perror("shmget err");return -1;}}printf("shmid:%d\n",shmid);share p = (share)shmat(shmid,NULL,0);if(p == (share)-1){perror("shmat err");return -1;}p->flag = 0; //初始化标志while(1){if(p->flag ==1){ //当标志为1的时候输出if(strcmp(p->msg,"quit") == 0)break;printf("%s\n",p->msg);p->flag = 0;}}shmdt(p);shmctl(shmid,IPC_RMID,NULL);return 0;
}
5.信号灯集
5.1 特点
(1)信号灯(semaphore),也叫信号量,信号灯集是一个信号灯的集合。它是不同进程间或一个给定进程内部不同线程间同步的机制;
(2)而Posix信号灯指的是单个计数信号灯:无名信号灯、有名信号灯。(主要学的是无名信号灯)
(3)System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。
通过信号灯集实现共享内存的同步操作
5.2 步骤
(1).创建key值: ftok
(2).创建或打开信号灯集: semget
(3).初始化信号灯: semctl
(4).PV操作: semop
(5).删除信号灯集: semctl
5.3 相关命令
ipcs -s: 查看信号灯集
ipcrm -s: 删除信号灯集
注意:有时候可能会创建失败,或者semid为0,所以可以用命令查看,删除了重新创建就可以。
5.4 函数接口
int semget(key_t key, int nsems, int semflg); 功能:创建/打开信号灯 参数:key:ftok产生的key值nsems:信号灯集中包含的信号灯数目semflg:信号灯集的访问权限,通常为IPC_CREAT|IPC_EXCL|0666 返回值:成功:信号灯集ID失败:-1int semctl ( int semid, int semnum, int cmd…/*union semun arg*/); 功能:信号灯集合的控制(初始化/删除) 参数:semid:信号灯集IDsemnum: 要操作的集合中的信号灯编号,信号灯编号从0开始cmd: GETVAL:获取信号灯的值,返回值是获得值SETVAL:设置信号灯的值,需要用到第四个参数:共用体IPC_RMID:从系统中删除信号灯集合 返回值:成功 0失败 -1 用法: 1. 初始化信号灯集: 需要自定义共用体 union semun{int val; } mysemun; mysemun.val = 10; semctl(semid, 0, SETVAL, mysemun);2. 获取信号灯值:函数semctl(semid, 0, GETVAL)的返回值 3. 删除信号灯集:semctl(semid, 0, IPC_RMID);int semop ( int semid, struct sembuf *opsptr, size_t nops); 功能:对信号灯集合中的信号量进行PV操作 参数:semid:信号灯集IDopsptr:操作方式nops: 要操作的信号灯的个数 1个 返回值:成功 :0失败:-1 struct sembuf {short sem_num; // 要操作的信号灯的编号short sem_op; // 0 : 等待,直到信号灯的值变成0// 1 : 释放资源,V操作// -1 : 申请资源,P操作 short sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO };用法: 申请资源 P操作:mysembuf.sem_num = 0;mysembuf.sem_op = -1;mysembuf.sem_flg = 0;semop(semid, &mysembuf, 1); 释放资源 V操作:mysembuf.sem_num = 0;mysembuf.sem_op = 1;mysembuf.sem_flg = 0;semop(semid, &mysembuf, 1);
6.消息队列
6.1特点
(1)消息队列是IPC对象(活动在内核级别的一种进程间通信的工具)的一种
(2)一个消息队列由一个标识符 (即队列ID)来标识
(3)消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等
(4)消息队列可以按照类型(自己设一个值作为类型)来发送/接收消息
6.2步骤
(1)创建key值: ftok()
(2)创建或打开消息队列: msgget()
(3)添加消息: 按照类型把消息添加到已经打开的消息队列末尾msgsnd()
(4)读取消息:可以按照类型把消息从列表中取走 msgrcv()
(5)删除消息队列msgctl()
6.3相关命令
ipcs -q:查看消息队列
ipcrm -q msgid:删除消息队列
6.4函数接口
int msgget(key_t key, int flag);
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
功能:创建或打开一个消息队列
参数: key值
flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666
返回值:成功:msgid
失败:-1
int msgsnd(int msqid, const void *msgp, size_t size, int flag);
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
功能:添加消息
参数:msqid:消息队列的ID
msgp:指向消息的指针。常用消息结构msgbuf如下:
struct msgbuf{
long mtype; //消息类型,类型是大于0的
char mtext[N]}; //消息正文
size:发送的消息正文的字节数
flag:IPC_NOWAIT:消息没有发送完成函数也会立即返回
0:直到发送完成函数才返回
返回值:成功:0
失败:-1
使用:msgsnd(msgid, &msg,sizeof(msg)-sizeof(long), 0)
注意:消息结构除了第一个成员必须为long类型外,其他成员可以根据应用的需求自行定义。
int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag);
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
功能:读取消息
参数:msgid:消息队列的ID
msgp:存放读取消息的空间
size:接受的消息正文的字节数(sizeof(msgp)-sizeof(long))
msgtype:0:接收消息队列中第一个消息。
大于0:接收消息队列中第一个类型为msgtyp的消息.
小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
flag:0:若无消息函数会一直阻塞
IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG
返回值:成功:接收到的消息的长度
失败:-1
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
功能:对消息队列的操作,删除消息队列
参数:msqid:消息队列的队列ID
cmd:IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。
IPC_SET:设置消息队列的属性。这个值取自buf参数。
IPC_RMID:从系统中删除消息队列。
buf:消息队列缓冲区
返回值:成功:0
失败:-1
用法:msgctl(msgid, IPC_RMID, NULL)
例:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <errno.h>typedef struct msgbuf{ //自定义一个消息结构体long type;int num;char ch;
}msgbuf;int main(int argc, const char *argv[])
{key_t key = ftok("2",'c'); //创建keyif(key < 0){perror("ftok err");return -1;}int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0777); //使用key创建消息队列if(msgid < 0){if(errno = EEXIST)msgid = msgget(key,0777);else{perror("msgget err");return -1;}}printf("msgid:%d\n",msgid);//创建消息队列msgbuf msg;msg.type = 1;msg.num = 2;msg.ch = 'b';//向队列发送消息msgsnd(msgid,&msg,sizeof(msg) - sizeof(msg.type),0);msgbuf msg1;//从队列中接收消息msgrcv(msgid,&msg1,sizeof(msg1) - sizeof(msg.type),1,0);printf("%d %c\n",msg1.num,msg1.ch);return 0;
}
6.5练习:
一个进程循环向另一个进程发送消息,直到输入的num为-1。
msg1.c(发送端):
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>typedef struct msgbuf{long type;int num;char ch;
}msgbuf;int main(int argc, const char *argv[])
{key_t key;key = ftok("2",'d');if(key < 0){perror("ftok err");return -1;}int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0777);if(msgid < 0){if(errno == EEXIST)msgid = msgget(key,0777);else{perror("msgget err");return -1;}}printf("msgid:%d\n",msgid);msgbuf msg1;while(1){scanf("%ld",&msg1.type);scanf("%d",&msg1.num);getchar();scanf("%c",&msg1.ch);msgsnd(msgid,&msg1,sizeof(msg1) - sizeof(msg1.type),0);if(msg1.num == -1)break;}msgctl(msgid,IPC_RMID,NULL);return 0;
}
msg2.c(接受端):
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>typedef struct msgbuf{long type;int num;char ch;
}msgbuf;int main(int argc, const char *argv[])
{key_t key;key = ftok("2",'d');if(key < 0){perror("ftok err");return -1;}int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0777);if(msgid < 0){if(errno == EEXIST)msgid = msgget(key,0777);else{perror("msgget err");return -1;}}printf("msgid:%d\n",msgid);while(1){msgbuf msg2;msgrcv(msgid,&msg2,sizeof(msg2) - sizeof(msg2.type),0,0);if(msg2.num == -1)break;printf("type:%ld num:%d ch:%c\n",msg2.type,msg2.num,msg2.ch);}msgctl(msgid,IPC_RMID,NULL);return 0;
}