1.消息队列基本属性
消息队列基本属性数据结构为struct msqid_ds
定义在文件/usr/include/linux/msg.h
中:
/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct msqid_ds {
struct ipc_perm msg_perm; // 权限
struct msg *msg_first; /* first message on queue,unused */ // 指向消息头
struct msg *msg_last; /* last message in queue,unused */ // 指向消息尾
__kernel_time_t msg_stime; /* last msgsnd time */ // 最近发生消息时间
__kernel_time_t msg_rtime; /* last msgrcv time */ // 最近接收消息时间
__kernel_time_t msg_ctime; /* last change time */ // 最近修改时间
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */ //
unsigned long msg_lqbytes; /* ditto */ //
unsigned short msg_cbytes; /* current number of bytes on queue */ // 当前队列大小
unsigned short msg_qnum; /* number of messages in queue */ // 当前队列消息个数
unsigned short msg_qbytes; /* max number of bytes on queue */ // 队列最大值
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */ // 最近发送消息进程pid
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */ // 最近接收消息进程pid
};
第1个成员struct ipc_perm msg_perm
为当前消息队列的权限。此结构体在我的博客《System V IPC基础》一节有说明。
第2、3个成员分别指向消息队列的首位。struct msg_msg
结构体在/usr/src/
uname -r/include/linus/msg.h
中定义:
/* one msg_msg structure for each message */
struct msg_msg {
struct list_head m_list;
long m_type; // 消息类型
size_t m_ts; /* message text size */ // 消息大小
struct msg_msgseg *next; // 下一个消息位置
void *security; // 真正消息位置
};
2、消息队列模型
消息队列的本质为一个链式结构,但是,消息队列还可以基于类型处理,因此,消息队列的FIFO原则仅仅使用于同类型的消息。
Linux系统对消息队列有一些限制值,不同Linux版本可能不同,下面列出我的系统的限制:
// come from /usr/include/linux/msg.h
#define MSGMNI 16 /* <= IPCMNI */ /* max # of msg queue identifiers */ // 最大消息队列个数
#define MSGMAX 8192 /* <= INT_MAX */ /* max size of message (bytes) */ // 消息最大值
#define MSGMNB 16384 /* <= INT_MAX */ /* default max size of a message queue */ // 默认消息队列大小
默认情况下,整个系统中最多允许有16个消息队列;
每个消息队列最大为16384字节,即16K;
消息队列中的每个消息最大为8192字节,即8K。
1.创建消息队列
在使用一个消息队列前,需要使用msgget函数创建该消息队列,其函数声明如下:
int msgget(key_t key, int msgflg);
第1个参数key为有ftok()函数创建的key值,关于ftok函数在我的博客《System V IPC基础》一节有说明。
第2个参数msgflg的低位用来确定消息队列的访问权限,其最终权限为当前进程的umask值与设置值perm相与所得,即最终值为perm&~umask。其高位包含以下项:
#define IPC_CREAT 01000 /* Create key if key does not exist. */ // 如果key不存在,则创建,存在,返回ID
#define IPC_EXCL 02000 /* Fail if key exists. */ // 如果key存在返回失败
#define IPC_NOWAIT 04000 /* Return error on wait. */ // 如果需要等待,直接返回错误
下面给出创建消息队列的代码:
key_t key = ftok("/", 3);
int msq_id = msgget(key, IPC_CREAT | 0666);
2.消息队列属性控制
msgctl()函数可以对创建好的消息队列的基本属性进行控制(修改),其函数声明如下:
int msgctl(int msqid, int cmd, struct msqid_ds* buf);
第1个参数msqid为消息队列标识符,该值为msgget()函数创建消息队列的返回值;
第2个参数cmd为执行的控制命令,即要执行的操作,包括以下选项:
// come from /usr/include/linux/ipc.h
#define IPC_RMID 0 /* remove resource */ // 删除消息队列
#define IPC_SET 1 /* set ipc_perm options */ // 设置ipc_perm参数
#define IPC_STAT 2 /* get ipc_perm options */ // 获取ipc_perm参数
#define IPC_INFO 3 /* see ipcs */ // 获取限制信息
IPC_RMID :删除消息队列。从系统中删除该消息队列以及仍在该队列上的所有数据,这种删除立即生效。仍在使用该消息队列的进程对消息队列的操作时,将出错返回EIDRM。
IPC_STAT:读取消息队列属性。取得此队列的msqid_ds结构,并将其存放在第3个参数buf中;
IPC_SET:设置消息队列属性。按由buf指向的结构中的值,设置此队列的属性;
IPC_INFO:读取消息队列基本情况。
第3个参数buf是一个临时的msqid_ds结构体类型的变量。用于存储读取的消息队列属性或者需要修改的消息队列属性。
下面给出删除消息队列代码:
msgctl(msq_id, IPC_RMID, NULL);
3.发送信息到消息队列
msgsnd()函数将新的消息添加到消息队列尾端,此函数声明如下:
// come from /usr/include/sys/msg.h
int msgsnd(int msqid, const void* msqp, size_t msgsz, int msgflg);
第1个参数msqid为消息队列标识符,由msgget创建消息队列是返回;
第2个参数msgp指向用户定义缓冲区,下面是用户定义缓冲区结构:
// come from /usr/include/linux/msg.h
/* message buffer for msgsnd and msgrcv calls */
struct msgbuf {
long mtype; /* type of message */ // 消息类型
char mtext[1]; /* message text */ // 消息内容,在使用时自己重新定义此结构
};
第3个参数为实际信息的大小。其大小为0到系统对消息队列的限制值。可以表示为sizeof(msgbuf) - sizeof(long)
;
第4个参数msgflg用来指定在达到系统为消息队列所定的界限(如消息队列所占空间满时)时采取的操作。
如果设置为IPC_NOWAIT,如果需要等待,则不发送消息且立即返回错误信息EAGIN;
如果设置为0,则阻塞调用进程。
该函数成功调用后返回0,否则返回-1,同时将对消息队列msqid数据结构的成员执行下列操作:
msg_qnum自增1;
msg_lspid设置为调用进程PID;
msg_stime设置为当前时间。
下面给出msgsnd发送消息的代码:
struct msgbuf {
long mtype;
char mtext[100];
};
struct msgbuf buf;
int ret = msgsnd(msq_id, &buf, sizeof(buf.mtext), 0);
4.从消息队列接收信息
msgrcv()函数用于从消息队列中读取消息,其函数声明如下:
int msgrcv(int msqid, void* msgp, size_t msgsz, long int msgtype, int msgflg);
第1个参数msqid为消息队列标识符,即从哪个消息队列获得消息;
第2个参数为一个临时消息数据结构,用来保存读取的信息,其定义如下:
// come from /usr/include/linux/msg.h
/* message buffer for msgsnd and msgrcv calls */
struct msgbuf {
long mtype; /* type of message */ // 消息类型
char mtext[1]; /* message text */ // 消息内容,在使用时自己重新定义此结构
};
第3个参数msgsz用于指定接收缓冲中mtext的大小(以字节为单位)。如果收到的消息大于msgsz,并且msgflg&MSG_NOERROR为真,则将该消息截至msgsz,消息的截断部分将丢失,并且不向调用进程提供截断的提示。
第4个参数msgtype用于指定请求的消息类型,具体如下:
msgtype=0:接收队列中的第一天消息,任意类型;
msgtype>0:接收第一天msgtype类型的消息;
msgtype<0:接收第一条最低类型(小于或等于msgtype的绝对值)的消息。
第5个参数msgflg用于指定所需类型消息不在队列上时将要采取的操作。具体如下所示:
如果设置IPC_NOWAIT,如果没有消息,调用进程立即返回,同时返回-1,并将errno设置为ENOMSG;
如果未设置IPC_NOWAIT(即设置0),则阻塞调用进程,直到下面任何一种情况发生:
所需信息被放到消息队列中;
msqid从系统中删除;
收到信号。
接收消息成功时,该消息将自动从消息队列中删除,并返回接收到的消息大小,并将对整个消息队列msqid数据结构的成语执行下列操作:
msg_qnum自减1;
msg_lrpid设置为调用进程PID;
msg_rtime设置为当前时间。
下面给出msgrcv发送消息的代码:
struct msgbuf {
long mtype;
char mtext[100];
};
struct msgbuf buf;
int ret = msgrcv(msq_id, &buf, sizeof(buf.mtext), 1, 0);
下面给出消息队列应用示例:
#include
#include
#include
#include
#include
#include
#include
#define BUF_SIZE 128
#define PARENT_SND 1
#define CHILD_SND 2
typedef struct msg_buf
{
long type;
char buf[BUF_SIZE];
}MSG;
int main(void)
{
key_t key;
pid_t pid;
key = ftok("/", 10);
pid = fork();
if (pid < 0) {
perror("fork failed");
return -1;
}
if (pid > 0) { // 父进程
MSG msg;
msg.type = PARENT_SND;
memset(msg.buf, '\0', sizeof(msg.buf));
memcpy(msg.buf, "hello", BUF_SIZE-1);
int msg_id = msgget(key, IPC_CREAT | 0666); // 创建消息队列
msgsnd(msg_id, &msg, BUF_SIZE-1, 0); // 发送消息到消息队列
msgrcv(msg_id, &msg, BUF_SIZE-1, CHILD_SND, 0); // 从消息队列接收数据
printf("parent: %s\n", msg.buf);
msgctl(msg_id, IPC_RMID, NULL); // 销毁消息队列
} else if(0 == pid) { // 子进程
MSG msg;
int msg_id = msgget(key, IPC_CREAT | 0666);
msgrcv(msg_id, &msg, BUF_SIZE-1, PARENT_SND, 0);
printf("child: %s\n", msg.buf);
msg.type = CHILD_SND;
memset(msg.buf, '\0', sizeof(msg.buf));
memcpy(msg.buf, "thank you", BUF_SIZE-1);
msgsnd(msg_id, &msg, BUF_SIZE-1, 0);
msgctl(msg_id, IPC_RMID, NULL);
}
return 0;
}