简介
之前的几个笔记,主要介绍了IPC的管道和FIFO通信方式。在这里再次明确一点,IPC的本质作用是用于进程之间的通信。管道的使用方式简单,生命周期是跟随进程的;但是,管道有自身的缺陷,个人总结为3点:
- 管道更适合进程端对端的通信,即在两个进程之间建立一个通信管道,如果有多个进程想要通过管道进行通信,那么需要建立多个管道。
- 如果建立管道,那么必须先有管道的读出端进程,否则仅仅有写入端的管道是无意义,或者说是不存在的。
- 管道的消息是没有优先级的,即数据是先进先出(FIFO)的,这样如果有紧急数据,无法立刻进行处理,只能等到前面的数据处理完毕后,才可以处理当前紧急消息
假象下面一个情景:存在一个进程A,A需要不断向外界报告自身的状态消息,但是A不知道哪个进程会处理自己的消息;而且A可能会发出一些更加紧急的消息,这些消息必须在其他消息前进行处理。
那么,上述场景肯定不能通过管道进行,因为管道必须知道通信的双方,而且管道处理数据只能是FIFO的,最佳的方式之一
是使用消息队列。
注意:Posix消息队列编译需要添加参数 -lrt
Posix消息队列
概览
消息队列的根本目的是用于进程间通信(IPC)的,消息队列可以在进程之间进行共享数据,Posix消息队列有几个主要的特性:
- 消息队列的每个消息都有自己的优先级,而且每次从中获取消息,总是得到优先级最高的一个
- 消息队列通过
msg_open
进行创建,而且每个消息队列通过/somename
的方式进行唯一标识。 - 进程通过
mq_close
关闭本进程的队列的引用,通过mq_unlink
删除内核中的消息队列 - 使用
fork()
函数后,子进程复制父进程的文件描述符,那么共享同一个消息队列,而且也共享mq_flags
- 一个消息队列描述符可以通过
select
、poll
和epoll
进行监听 - 与Posix相对的还有一个更古老的System V消息队列。Posix消息队列接口更好用,但是相对的,System V的接口更灵活。根据需要合理选取即可。如果没有特殊需求,使用Posix即可
基本操作
创建消息队列
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <mqueue.h>
mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode,
struct mq_attr *attr);
name
:消息队列名称标识符oflag
:消息队列的权限,如果使用了O_CREAT | O_EXCL
,那么如果已经存在消息队列,则errno
置为EEXIST
mode
:消息队列的模式attr
:设置的消息队列的属性,是一个结构体,结构如下:
在调用struct mq_attr { long mq_flags; /* Flags (ignored for mq_open()) */ long mq_maxmsg; /* Max. # of messages on queue */ long mq_msgsize; /* Max. message size (bytes) */ long mq_curmsgs; /* # of messages currently in queue (ignored for mq_open()) */ };
mq_open
函数的时候,只有mq_maxmsg
和mq_msgsize
会使用到,其余的会被忽略。如果指定空指针,那么使用系统默认的参数
注意一个坑:消息队列的名称只能有开头一个/
符号!!!!
发送消息函数
#include <mqueue.h>
#include <time.h>
int mq_send(mqd_t mqdes, const char *msg_ptr,
size_t msg_len, unsigned int msg_prio);
int mq_timedsend(mqd_t mqdes, const char *msg_ptr,
size_t msg_len, unsigned int msg_prio,
const struct timespec *abs_timeout);
mqdes
:消息队列的描述符msg_ptr
:需要发送的消息msg_len
:发送消息的长度msg_prio
:发送消息的优先级abs_timeout
:超时参数Return value
:成功返回0,失败返回-1
mq_send
函数向消息队列发送消息,如果消息队列满了,那么该函数阻塞;如果队列满了,且消息队列设置为O_NONBLOCK
模式,那么函数返回-1,同时errno
设置为EAGAIN
。
mq_timedsend
基本同上,唯一区别在于如果阻塞情况下超时,则立刻返回-1.
接收消息函数
#include <mqueue.h>
#include <time.h>
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr,
size_t msg_len, unsigned int *msg_prio);
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,
size_t msg_len, unsigned int *msg_prio,
const struct timespec *abs_timeout);
mqdes
:消息队列描述符msg_ptr
:指向放置消息的缓冲区msg_len
:放置消息缓冲区的长度msg_prio
:返回消息的优先级abs_timeout
:超时参数Return Value
:成功返回消息长度,失败返回-1
mq_receive
函数接受消息队列消息,如果队列是阻塞模式,而且队列空,那么该函数阻塞;如果队列空,但是设置为O_NONBLOCK
模式,则返回-1,同时errno
设置为EAGAIN
。
ma_timedreceive
唯一的区别在于,如果队列空且超时,则立刻返回-1
消息通知函数
#include <mqueue.h>
int mq_notify(mqd_t mqdes, const struct sigevent *sevp);
mqdes
:消息队列标识符sevp
:一个sigevent
的结构
消息通知函数使用方式比较复杂,单独在这篇笔记中进行介绍。
关闭消息通知函数
#include <mqueue.h>
int mq_close(mqd_t mqdes); // 类似close
int mq_unlink(mqd_t mqdes); // 直接移除
代码应用实例
第一个简单的消息读取,多个客户端发送消息,服务器接收消息。
客户端代码:
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <time.h>
#include <wait.h>
#include <vector>
#include <cstring>
#include <random>
const int FLAG = O_CREAT | O_RDWR;
const char *NAME = "/my_msg_que";
const int MAX_MESSAGES = 50;
const int MAX_MSG_SIZE = 30;
const size_t MSG_BUFFER_SIZE = 30;
const int CLIENT_NUM = 10;
bool stop_client = false;
mqd_t mqd;
std::vector<pid_t> vec;
inline void handle_error(const char *err) {
perror(err);
exit(EXIT_FAILURE);
}
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: vec) {
kill(it, SIGINT);
}
puts("main process gets stop signal");
}
void sig_int_child(int sig) {
if (sig != SIGINT) {
return;
}
printf("client %d end...\n", getpid());
stop_client = true;
}
void child_process() {
int n = 5;
register_signal(SIGINT, sig_int_child);
while (!stop_client) {
std::random_device rd; //Will be used to obtain a seed for the random number engine
std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
std::uniform_int_distribution<> dis_p(1, 10);
std::uniform_int_distribution<> dis_t(1, 3);
int prio = dis_p(gen);
int t = dis_t(gen);
char buf[MAX_MSG_SIZE];
long pid = getpid();
snprintf(buf, MAX_MSG_SIZE, "I am client %ld", pid);
mq_send(mqd, buf, strlen(buf) + 1, prio);
printf("prio = %d, t = %d, msg = %s\n", prio, t, buf);
sleep(t);
if (--n <= 0) {
return;
}
}
}
int main() {
srand(time(0)); // 随机种子
mqd = mq_open(NAME, FLAG);
if (mqd < (mqd_t) 0) {
handle_error("mq_open() error\n");
}
for (int i = 0; i < CLIENT_NUM; ++i) {
pid_t pid = fork();
if (pid == 0) {
child_process();
exit(EXIT_SUCCESS);
} else {
vec.push_back(pid);
}
}
if (register_signal(SIGINT, sig_int) < 0) {
handle_error("register_signal() error\n");
}
wait(nullptr); // 等待所有子进程结束
puts("stop all clients");
exit(EXIT_SUCCESS);
}
服务端代码:
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
const int MODE = 0777;
const auto FLAG = O_CREAT | O_RDWR;
const char *NAME = "/my_msg_que";
const int MAX_MESSAGES = 50;
const int MAX_MSG_SIZE = 30;
const size_t MSG_BUFFER_SIZE = 30;
bool stop_server = false;
inline void handle_error(const char *err) {
perror(err);
mq_unlink(NAME);
exit(EXIT_FAILURE);
}
void sig_int(int sig) {
if (sig != SIGINT) {
return;
}
puts("stop server...");
stop_server = true;
}
int main() {
mqd_t mqd;
struct mq_attr attr;
bzero(&attr, sizeof(attr));
attr.mq_flags = 0;
attr.mq_maxmsg = MAX_MESSAGES;
attr.mq_msgsize = MAX_MSG_SIZE;
attr.mq_curmsgs = 0;
mqd = mq_open(NAME, FLAG, MODE, &attr);
if (mqd < (mqd_t) 0) {
handle_error("mq_open() 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");
}
char buf[MSG_BUFFER_SIZE];
unsigned int prio = 0;
while (!stop_server) {
auto ret = mq_receive(mqd, buf, MAX_MSG_SIZE, &prio);
if (ret < 0) {
handle_error("mq_receive() error\n");
}
printf("get client msg: %s, priority = %d\n", buf, prio);
sleep(1);
}
mq_unlink(NAME);
exit(EXIT_SUCCESS);
}