3 消息队列
- 消息队列的本质是一个存放消息的链表,该链表由内核来维护。一个消息队列由一个标识符(即队列key)来标识。
- 消息队列的通信机制传递的数据具有某种结构,而不是简单的字节流;
- 向消息队列中写数据,实际上是向这个数据结构中插入一个新结点;
- 从消息队列中读数据,实际上是从这个数据结构中删除一个结点;
- 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法;
- 消息队列具有和管道一样的不足,每个数据块的最大长度是有上限的,系统上全体队列的最大总长度也是有上限的。
使用消息队列实现进程间通信的步骤如下:
- (1)创建消息队列;
- (2)发送消息到消息队列;
- (3)从消息队列中读取数据;
- (4)删除消息队列。
Linux内核提供了4个系统调用:
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);//接受信息,msgtyp需要指明接受的数据type
key_t ftok(const char *pathname, int proj_id);//为队列随机附加key,pathename为路径,id号可随意(1-255)
int msgctl(int msqid, int cmd, struct msqid_ds *buf);//对指定消息队列进行控制
3.1 用户消息缓冲区
无论发送进程还是接收进程,都需要在进程空间中用消息缓冲区来暂存消息。该消息缓冲区的结构定义如下:
struct msgbuf{long int msgtype; //消息类型anytype data; //要发送的数据,可以为任意类型
};
通过msgtype区分数据类型,同过判断msgtype,是否为需要接收的数据;
data为存放消息的正文。
3.2 msgget函数
#include <sys/msg.h>int msgget(key_t key, int msgflg); //创建消息队列,返回值为该队列号
功能:创建一个消息队列或获取一个已经存在的消息队列。
参数说明:
- key:传入参数,消息队列的键值,通常为一个整数,若键值为IPC_PRIVATE,将会创建一个只能被创建消息队列的进程读写的消息队列;
- msgflg:类似于open函数中标志位的功能,用于设置消息队列的创建方式或权限,通常是一个9位的权限与如下值进行位操作后获得:
– msgflg = mask | IPC_CREAT,若内核中不存在指定消息队列,则它会被创建;若已存在,则获取该消息队列;
– msgflg = mask | IPC_CREAT | IPC_EXCL时,若消息队列不存在,则它会被创建;若已存在,则msgget函数调用失败,返回-1,并设置errno为EEXIST。
返回值说明:
- 成功:返回消息队列的标识符;
- 不成功:返回-1并设置errno。
3.3 msgsnd函数
#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//发送消息
功能:向指定消息队列发送一个消息;如果msgflg = 0,调用函数的进程会被挂起,直到消息写入消息队列为止。
参数说明:
- msqid:消息队列标识符,即msgget函数调用成功后的返回值;
- msgp:指向消息缓冲区的指针;
- msgsz:消息中数据的长度,这个长度不包括长整型成员变量的长度;
- msgflg:标志位,可以设置为0或IPC_NOWAIT。
返回值说明:
- 成功:返回消息队列的标识符;
- 不成功:若消息队列已满或系统中消息数量达到上限,返回-1并设置errno。
3.4 msgrcv函数
#include <sys/msg.h>ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);//接受信息,msgtyp需要指明接受的数据type
功能:从消息队列中读取消息,被读取的消息会从消息列表中移除。
参数说明:
- msqid:消息队列标识符,即msgget函数调用成功后的返回值;
- msgp:指向所读取消息的结构体指针;
- msgsz:消息中数据的长度,这个长度不包括长整型成员变量的长度;
- msgtyp:从消息队列中读取的消息类型:
– msgtyp = 0:获取队列中的第一个可用消息;
– msgtyp > 0:获取队列中与该值类型相同的第一个消息;
– msgtyp < 0:获取队列中消息类型小于或等于其绝对值的第一个消息。 - msgflg:标志位:
– msgflg = 0:进程将阻塞等待消息的读取;
– msgflg = IPC_NOWAIT:进程未读取到指定消息时将立刻返回-1。
返回值说明:
- 成功:返回消息队列的标识符;
- 不成功:返回-1并设置errno。
3.5 msgctl函数
#include <sys/msg.h>
#include <sys/ipc.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);//对指定消息队列进行控制
//内核为每个消息队列维护一个msqid_ds结构,用于消息队列的管理
struct msqid_ds {struct ipc_perm msg_perm; //所有者和权限标识time_t msg_stime; //最后一次发送消息的时间time_t msg_rtime; //最后一次接收消息的时间time_t msg_ctime; //最后改变的时间unsigned long __msg_cbytes; //队列中当前数据字节数msgqnum_t msg_qnum; //队列中当前消息数msglen_t msg_qbytes; //队列中允许的最大字节数pid_t msg_lspid; //最后发送消息的进程pidpid_t msg_lrpid; //最后接收消息的进程pid
};
功能:对指定消息队列进行控制。
参数说明:
- msqid:消息队列标识符,即msgget函数调用成功后的返回值;
- cmd:消息队列的处理命令:
– cmd = IPC_RMID:从系统内核中删除指定命令,使用命令ipcrm -q id可实现同样的功能;
– cmd = IPC_SET:若进程有权限,将内核管理的消息队列的当前属性值设置为参数buf各成员的值;
– cmd = IPC_STAT:将内核所管理的消息队列的当前属性值复制给参数buf。 - buf:一个缓冲区,用于传递属性值给指定消息队列或从指定消息队列中获取属性值,其功能视cmd而定。
返回值说明:
- 成功:返回消息队列的标识符;
- 不成功:返回-1并设置errno。
【案例1】使用消息队列实现不同进程间的通信。
- msgsend.c:消息发送端
- msgrcv.c:消息接收端
msgrcv.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#define MAX_TEXT 512
struct my_msg_st{long int my_msg_type;char anytext[MAX_TEXT];
};
int main(){int tempIdx = 1;int tempMsgid;struct my_msg_st tempData;long int tempMsgToRcv = 0;//rcv msgtempMsgid = msgget((key_t)1000, 0664 | IPC_CREAT);//获取消息队列if (tempMsgid == -1){perror("msgget err");exit(-1);}//of ifwhile (tempIdx < 5){//接收消息if (msgrcv(tempMsgid, (void*)&tempData, BUFSIZ, tempMsgToRcv, 0) == -1){perror("msgrcv err");exit(-1);}//of if//打印消息printf("msg type:%ld\n", tempData.my_msg_type);printf("msg content is:%s", tempData.anytext);tempIdx ++;}//of while//删除消息队列if (msgctl(tempMsgid, IPC_RMID, 0) == -1){perror("msgctl err");exit(-1);}//of ifexit(0);
}//of main
msgsend.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <string.h>
#define MAX_TEXT 512
//消息结构体
struct my_msg_st{long int my_msg_type; //消息类型char anytext[MAX_TEXT]; //消息数据
};
int main() {int tempIdx = 1;int tempMsgid;struct my_msg_st tempData;char tempBuf[BUFSIZ]; //设置缓存变量tempMsgid = msgget((key_t)1000, 0664 | IPC_CREAT);//创建消息队列if (tempMsgid == -1){perror("msgget err");exit(-1);}//of ifwhile (tempIdx < 5){ //发送消息printf("enter some text:");fgets(tempBuf, BUFSIZ, stdin);tempData.my_msg_type = rand() % 3 + 1; //随机获取消息类型strcpy(tempData.anytext, tempBuf);//发送消息if (msgsnd(tempMsgid, (void*)&tempData, sizeof(tempData), 0) == -1){perror("msgsnd err");exit(-1);}//of iftempIdx ++;}//of whilereturn 0;
}//of main
- BUFSIZ:Linux系统定义在stdio.h的宏,表示默认缓冲区大小;
3.6 键值和标识符
-
键值(ID):ID是msgget函数的返回值,一个非负整数,属于进程间通信的内部名,用来确保使用同一个消息队列。内部名即在进程内部使用,是消息队列在进程级别的唯一标识,这样的标识方法是不能支持进程间通信的。
-
标识符(key): key是实现进程与消息队列关联的关键,属于进程间通信的外部名,是消息队列在内存级别的唯一标识。当多个进程,针对同一个key调用msgget函数,这些进程得到的ID其实是标识了同一个进程间通信的结构。多个进程间就可以通过这个进程间通信的结构进行通信。