System V消息队列

简介

这篇笔记中介绍了消息队列的基本知识和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);
  • msqidmsgget返回的消息队列标识符
  • 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值