消息队列
消息队列一、消息队列的概念
Linux系统中消息队列(Message Queue)是进程间通信的一种方式,这种通信机制的好处是可以传输指定类型(用户可以自行定义)的数据,相同类型的数据根据到达顺序在队列中进行排队。
当然,不同类型的数据不能处于同一个队列中,也就是说系统中可能存在多个消息队列,每个消息队列中的数据类型都是不同的,所以用户打算读取消息队列中的数据时也需要指定数据类型,才可以从存储该类型数据的消息队列中读取有效数据。
Linux系统每个创建的消息队列都具有一个唯一的键值key,进程可以通过指定消息队列的键值来向消息队列发送数据。Linux系统中提供了一个shell命令:ipcs-a来查看系统中所有的IPC对象的信息。
二、创建消息队列
Linux系统提供了一个名称叫做msgget()的函数接口,用户利用该接口可以创建或者打开一个消息队列,同样Linux系统中提供了一个shell命令:ipcmk 可以用于创建IPC对象。
(1) ipcmk命令
ipcmk-Q创建一个消息队列
(2) msgget()函数
msgget函数有两个参数,第一个参数需要传入一个key_t类型的值,该值指的是要创建的消息队列的key键值,key也可以称为密钥。 键值类型key_t其实在内核源码中指的是int类型。msgget()函数的第二个参数指的是创建消息队列的标志,其中*IPC_CREAT*指的是如果消息队列不存在则创建,IPC_EXCL指的是如果消息队列存在则表示函数调用失败。另外,也可以指定消息队列的权限,权限的结构和open函数的mode类型,采用八进制表示,只不过消息队列不需要设置执行权限,所以权限设置为0644即可。
返回值:msgget()函数调用成功,则返回消息队列的标识符,如果调用失败,则返回-1并设置错误码。
(3)生成键值key
Linux系统中提供了一个名称叫做ftok()的函数接口,利用该接口可以生成IPC对象的键值key
ftok()函数的第一个参数指的是系统中已经存在并且可以访问的一个文件的路径,用户可以指定一个文件,但是该文件必须存在且可以被访问,其实就是为了得到文件的属性信息中的inode编号和设备编号,因为Linux系统中一个文件的inode编号是唯一的。
ftok()函数的第二个参数指的是项目ID,这个可以由用户进行指定,虽然参数proj_id的类型是整型int,但是只会用到最低8it,所以这个参数的范围其实是1~255,因为这个参数的值必须是非0值。
返回结果:ftok()函数的返回值:当函数调用成功后,则返回生成的键值key,如果调用失败,则返回-1。
注意事项:在man手册中提到ftok()函数生成的键值key的组成:proj_id的低8位+ 设备编号的低8位+ inode编号的低16位。
练习:设计程序,在Linux系统中创建一个消息队列,并测试消息队列的键值key的组成是否正确。提示:可以通过stat()函数获取文件的属性信息。
int main()
{
//1.创建消息队列,设置权限为0664
key_t key=ftok(".",10);
int msg_fd=msgget(key,IPC_CREAT|IPC_EXCL|0664);
if(-1=msg_fd)
{
fprintf(stderr,"meget error,errno=%d%s\n",errno,strerror(errno));
msg_fd=msgget(fotok(".",10),IPC_CREAT|0664);
}
//2.输出创建成功的消息队列的key
printf("msg key=%d\n",key);
//3.验证key的生成算法 project_id()8bit+device_number(8bit)+inode_number(16bit)
struct stat file_stat;
stat(".",&file_stat);
printf("device_number:%#lx\n",file_stat.st_dev);
printf("inode_number:%#lx\n",file_stat.st_ino);
return 0;
}三、访问消息队列
Linux系统中提供了一个名称叫做msgsnd()的函数接口,用户利用该函数可以向指定的消息队列发送消息,同时提供了一个名称叫做msgrcv()的函数接口,用户利用该接口可以从指定的消息队列中读取消息。
(1)发送消息
msgsnd函数的第一个参数msgid指的是消息队列的标识符,该标识符可以通过msgget函数得到。
msgsnd函数的第二个参数msgp指的是一个指向struct msgbuf类型的结构体指针,该结构体中有两个成员,其中一个成员mtype指的是消息类型,必须是一个大于0的正整数,另一个成员mtext指的是消息正文,类型可以是数组或者其他结构。
msgsnd函数的第三个参数msgsz指的是消息正文的大小,按字节计算,当然msgsz的值必须是非负整数,可以设置为0,表示消息正文的长度为0。
msgsnd函数的第四个参数msgflg指的是消息队列的标志,如果该标志设置为IPC_NOWAIT,则表示不阻塞,此时如果待写入的消息的长度大于消息队列剩余空间,则直接返回并报错。
注意:消息队列默认的属性是阻塞的,也就是当待写入的消息的长度大于消息队列剩余空间时,默认阻塞,直到消息队列的容量足够容纳时会解除阻塞。
(2)读取消息
第一个参数:msgqid指的是MSG对象的标识符ID,MSG标识符可以通过msgget()函数获取。
第二个参数:msgp指的是存放消息的缓存地址,该地址下存储的是structmsgbuf结构体。
第三个参数:msgsz指的是存放消息的缓存的大小,按照字节计算,如果消息正文的大小大于用户设置的缓存大小,则根据msgflg是否为MSG_NOERROR进行判断,如果msgflg设置为MSG_NOERROR ,则可以读取对应字节的消息,如果msgflg没有设置,则无法读取消息并报错。
第四个参数:msgtyp指的是要接收消息的类型,在调用msgsnd函数时构造的消息结构体中有该成员的值。
[*]等于0:指的是不区分类型,直接读取MSG中的第一个消息。
[*]大于0:读取类型为指定msgtyp的第一个消息(若msgflg被配置了MSG_EXCEPT则读取除了类型为msgtyp的第一个消息)。
[*]小于0:读取类型小于等于msgtyp绝对值的第一个具有最小类型的消息。例如当MSG对象中有类型为3、1、5类型消息若干条,当msgtyp为-3时,类型为3的第一个消息将被读取。
第五个参数:msgflg指的是接收消息选项,如果msgflg设置为0,指的是默认接收模式,在MSG中无指定类型消息时阻塞。
[*]IPC_NOWAIT :指的是非阻塞接收模式,当MSG中没有指定类型消息时直接退出函数
[*]MSG_EXCEPT :指的是读取除msgtyp之外的第一个消息。
[*]MSG_NOERROR:如果待读取的消息尺寸比msgsz大只返回msgsz部分,其余部分丢弃
练习:要求进程A创建一条消息队列之后向进程B发送SIGUSR1信号,进程B收到该信号之后打开消息队列并把进程的PID作为消息写入到消息队列中,要求进程B在写入消息之后,发SIGUSR2信号给进程A,进程A收到该信号则从消息队列中读取消息并输出消息正文的内容。
进程A
#include <stdio.h>
#include <signal.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>
//定义全局变量
int msg_id=0;
key_t key;
int rcv_val;
typedef struct mymsg {
long mtype; /* Message type. */
int mtext; /* Message text. */
}msg_t;
msg_t msg;
void sign_hander(int signum)
{
//0.打开一个消息队列
key=ftok(".",0x00000001);
msg_id=msgget(key,IPC_CREAT|IPC_EXCL|0644);
if(msg_id=-1)
{
fprintf(stderr,"msgger error,errno=%d%s\n",errno,strerror(errno));
msg_id=msgget(key,IPC_CREAT|0644);
}
//1.把结构体初始化
bzero(&msg,sizeof(msg));
//2.取走消息队列中的数据
rcv_val=msgrcv(msg_id,&msg,4,0,0);
if (-1 == rcv_val)
{
fprintf(stderr, "msgrcv fail,error = %d, %s\n", errno, strerror(errno));
}
//3.输出读取到的值
printf("this is what I read from message queue:%d\n", msg.mtext);
}
int main()
{
//1.创建一个消息队列
key=ftok(".",0x00000001);
msg_id=msgget(key,IPC_CREAT|IPC_EXCL|0644);
if(msg_id=-1)
{
fprintf(stderr,"msgger error,errno=%d%s\n",errno,strerror(errno));
msg_id=msgget(key,IPC_CREAT|0644);
}
//2.手动输入进程b的pid,向进程B发送SIGUSR1信号
int pid_b=0;
scanf("请输入进程B的进程标识符:%d\n",&pid_b);
kill(pid_b,SIGUSR1);
//3.接收进程B发送的信号量
signal(SIGUSR2,sign_hander);
while(1);
return 0;
}进程B
#include <stdio.h>
#include <signal.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>
//定义全局变量
int msg_id=0;
key_t key;
int snd_val;
typedef struct mymsg {
long mtype; /* Message type. */
int mtext; /* Message text. */
}msg_t;
msg_t msg;
void sign_hander(int signum)
{
//1.创建一个消息队列
key_t key=ftok(".",0x00000001);
int msg_id=msgget(key,IPC_CREAT|IPC_EXCL|0644);
if(msg_id=-1)
{
fprintf(stderr,"msgger error,errno=%d%s\n",errno,strerror(errno));
msg_id=msgget(key,IPC_CREAT|0644);
}
//2.把进程的PID作为消息写入到消息队列中
bzero(&msg,sizeof(msg));
msg.mtype=1;
snd_val=msgsnd(msg_id,&msg,4,0);
if (-1 == snd_val)
{
fprintf(stderr, "msgsnd fail,error = %d, %s\n", errno, strerror(errno));
}
//3.手动输入进程a的pid,向进程A发送SIGUSR2信号
int pid_a=0;
scanf("请输入进程B的进程标识符:%d\n",&pid_a);
kill(pid_a,SIGUSR1);
}
int main()
{
//接受进程A发送的信号量
signal(SIGUSR1,sign_hander);
while(1);
return 0;
}四、控制消息队列
IPC对象是一种持久性资源,如果没有明确的删除掉IPC对象,则IPC对象是不会自动从内存中消失的。用户除了可以使用命令的方式删除,也可以使用函数来删除。
Linux系统中提供了一个名称叫做msgctl的函数接口,用户可以利用该函数实现获取消息队列的属性信息、设置消息队列的属性信息、删除消息队列等操作。
(1)指令删除
ipcrm
(2)函数删除
int msgctl(int msqid,int cmd,struct msqid_ds buf);
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]