简介
这篇笔记中介绍了消息队列的基本知识和Posix消息队列。这篇笔记主要学习记录System V消息队列,并对比两个消息队列。
System V消息队列是更早的一个消息队列的实现。Posix消息队列接口简单,但是缺陷之一是只能获取最高优先级的消息;而System V队列与之的最大的区别就是在消息类型和优先级上更加灵活。System V消息队列也是随内核持续的,而且也是链式结构实现的。
基本操作
创建或者获取消息队列:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
key
:可以是IPC_PRIVATE
或者ftok()
函数返回的参数。msgflg
:指定创建的参数,第一次创建使用IPC_CREAT
,其余的都是按照比特位或进行操作。如果IPC_CREAT | IPC_EXCL
,则参照open
函数的笔记,两者用法一致。Return Value
:成功返回消息队列的id;失败返回-1,并设置errno
发送消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid
:msgget
返回的消息队列标识符msgp
:需要发送的消息,该消息具有以下结构:struct msgbuf { long mtype; char mtext[msgsz]; };
mtype
:表示消息的类型,必须是正数!!!mtext
:表示存储消息的数组,由参数指定大小
msgsz
:指定消息存储数据区的大小msgflg
:指定接收的行为参数。正常设置为0,如果是IPC_NOWAIT
,则立刻返回。Return Value
:成功返回0;失败返回-1,并设置错误码errno
接收消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *prt, size_t length, long type, int flag);
msgqid
:消息队列的标识符ptr
:存储消息的地址length
:缓冲区数据部分的长度,参照msgsnd
函数介绍type
:指定消息的返回类型,见下文flag
:指定接受行为的参数,见下文Return Value
:接收消息的字节数,不包括长整型占用的字节
在这里着重介绍type
,有3个取值:
- 0:返回队列中第一个消息
- 大于0:返回类型为
type
的第一个消息 - 小鱼0,返回类型值小于等于
type
绝对值的最小的一个消息
介绍flag
参数:
操作消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid
:消息队列标识符cmd
:命令参数buf
:需要更新的数据
这个函数主要是更改内核维护的消息队列的队头表,表的结构如下:
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
其中ipc_perm
结构如下:
struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
命令参数:
IPC_STAT
:拷贝消息队列的命令参数到buf
中IPC_SET
:设置队列参数,具体参照手册。IPC_RMID
:移除整个消息队列,如果有写入或者读取,会得到EIDRM
错误IPC_INFO
:返回系统级别的消息队列的限制,参考手册MSG_INFO
:类似IPC_INFO
,但是有几个域排除掉,参考手册MSG_STAT
:与IPC_STAT
类似,区别参考手册
代码实例
实现一个和上一篇笔记的Posix队列,实现一个类似的功能。一个服务端接收,多个客户端发送消息。暂时有些小的bug。
注意一个点,因为System V消息队列是跟随内核同步的,所以应该可以获取一个全局的队列标识符。这通过ftok()
系统调用实现。
客户端:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <signal.h>
#include <cstdio>
#include <unistd.h>
#include <strings.h>
#include <wait.h>
#include <vector>
#include <time.h>
const int MAX_BUFFER_SIZER = 50;
const int CLIENT_NUM = 10;
bool stop_client = false;
key_t msqkey;
int msqid;
std::vector<pid_t> childProcess;
struct usermsg {
long mtype;
char mtext[MAX_BUFFER_SIZER];
};
int register_signal(int sig, void(*sig_handler)(int));
void sig_int(int sig);
void child_process();
void sig_int_child(int sig);
inline void handle_error(const char *err) {
perror(err);
exit(EXIT_FAILURE);
}
int main() {
srand(time(0));
key_t msqkey = ftok("/tmp/msg_example", 1);
int msqid = msgget(msqkey, IPC_CREAT);
if (msqid < 0) {
handle_error("msgget() error\n");
}
for (int i = 0; i < CLIENT_NUM; ++i) {
pid_t pid = fork();
if (pid == 0) {
child_process();
exit(EXIT_SUCCESS);
} else {
childProcess.push_back(pid);
}
}
if (register_signal(SIGINT, sig_int) < 0) {
handle_error("register_signal() error\n");
}
wait(nullptr);
puts("stop all client");
exit(EXIT_SUCCESS);
}
void child_process() {
int n = 5;
register_signal(SIGINT, sig_int_child);
usermsg msg;
while (!stop_client) {
msg.mtype = rand() % 2;
if (msg.mtype == 0) {
snprintf(msg.mtext, 50, "msgtype = 0");
} else {
snprintf(msg.mtext, 50, "msgtype = 1");
}
if (msgsnd(msqid, (usermsg *) &msg, 50, 0) < 0) {
handle_error("msgsnd() error\n");
}
sleep(rand() % 3);
if (--n <= 0) {
return;
}
}
}
int register_signal(int sig, void(*sig_handler)(int)) {
struct sigaction sa;
bzero(&sa, sizeof(sa));
sa.sa_handler = sig_handler;
sa.sa_flags = SA_RESTART;
return sigaction(sig, &sa, nullptr);
}
void sig_int(int sig) {
if (sig != SIGINT) {
return;
}
for (const auto &it: childProcess) {
kill(it, SIGINT);
}
puts("main proess gets stop signal");
}
void sig_int_child(int sig) {
if (sig != SIGINT) {
return;
}
printf("client %d end...\n", getpid());
stop_client = true;
}
服务器:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <signal.h>
#include <cstdio>
#include <unistd.h>
#include <strings.h>
const int MAX_BUFFER_SIZER = 50;
bool stop_server = false;
struct usermsg {
long mtype;
char mtext[MAX_BUFFER_SIZER];
};
void sig_int(int sig);
inline void handle_error(const char *msg) {
perror(msg);
exit(EXIT_FAILURE);
}
int main() {
// 创建一个消息队列
key_t msqkey = ftok("/tmp/msg_example", 1); // 获取全局标识符
int msqid = msgget(msqkey, IPC_CREAT);
if (msqid < 0) {
handle_error("msgget() error\n");
}
struct sigaction sa;
bzero(&sa, sizeof(sa));
sa.sa_handler = sig_int;
sa.sa_flags = SA_RESTART;
if (sigaction(SIGINT, &sa, nullptr) < 0) {
handle_error("sigaction() error\n");
}
usermsg msg;
msg.mtype = 1;
while (!stop_server) {
ssize_t ret = msgrcv(msqid, (usermsg *) &msg, MAX_BUFFER_SIZER, msg.mtype, 0);
printf("get message %s, type = %ld", msg.mtext, msg.mtype);
sleep(1);
}
exit(EXIT_SUCCESS);
}
void sig_int(int sig) {
if (sig != SIGINT) {
return;
}
puts("stop server...");
stop_server = true;
}