Unix网络编程-Posix消息队列

消息队列的创建

  1. 命名只能是/queuename.
  2. 默认存放在/dev/mqueue里,(posixIPC都在这里,System V 可以用ipcs查看)
  3. 与内核相同的持续时间——除非删除队列,否则里面的消息不丢失
  4. 存在引用计数,最后一个计数为0且unlink可以删除

创建:

/**
 * @file 1-mqcreate.c
 * 创建一个消息队列
 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
// 注意Posxi消息队列的命名只能是/queuename.
// 不可以用路径,默认都是放在/dev/mqueue里
#define MQNAME "/temp.1234"
// 创建的文件的权限
#define MODE (S_IRWXU | S_IRWXG | S_IRWXO)
int main(int argc, char **argv)
{
    mqd_t mqd;
    mqd = mq_open(MQNAME, O_RDWR | O_CREAT, MODE, NULL);
    if (mqd == (mqd_t)-1)
    {
        perror("mq_open");
        exit(-1);
    }
    mq_close(mqd);
    // 不加这句,temp.1234 不会被删除,这了这句话,就会被删除
    // mq_unlink(MQNAME);
    exit(0);
}

设置和获取队列属性

设置属性:

/**
 * @file 2-mqsetattr.c
 * 设置或修改消息队列的属性
 * 属性有四项:flag, maxmsg, msgsize, curmsgs;
 * 其中:
 * 1.消息队列的最大容量和每个消息的最大长度只能在创建的时候设置,手动检查是否为0,不能超过规定值(文件中)
 * 2.flag只有是否阻塞两个选项,可以随意随时设置
 * 3.当前消息队列的消息的个数,任何时候不能设置,只能获取
 */
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MQNAME "/temp.1"
#define MODE (S_IRWXU | S_IRWXG | S_IRWXO)
int main(int argc, char **agrv)
{
    if (argc < 2)
    {
        fprintf(stderr, "Using...\n");
        exit(0);
    }
    mqd_t mqd;
    struct mq_attr attr;
    memset(&attr, 0, sizeof(attr));
    attr.mq_maxmsg = atol(agrv[1]);
    attr.mq_msgsize = atol(agrv[2]);
    mqd = mq_open(MQNAME, O_RDWR | O_CREAT | O_EXCL, MODE, &attr);
    if (mqd == (mqd_t)-1)
    {
        perror("mq_open");
        exit(-1);
    }
    mq_close(mqd);
    exit(0);
}

获取属性:

/**
 * @file 2-mqgetattr.c
 * 获取消息队列的属性
 * 属性有四项:flag, maxmsg, msgsize, curmsgs;
 * 其中:
 * 1.消息队列的最大容量和每个消息的最大长度只能在创建的时候设置,手动检查是否为0
 * 2.flag只有是否阻塞两个选项,可以随意随时设置
 * 3.当前消息队列的消息的个数,任何时候不能设置,只能获取
 */
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#define MQNAME "/temp.1"
#define MODE (S_IRWXU | S_IRWXG | S_IRWXO)
int main(int argc, char **agrv)
{
    mqd_t mqd;
    struct mq_attr attr;
    mqd = mq_open(MQNAME, O_RDWR, MODE, NULL);
    mq_getattr(mqd, &attr);
    printf("flag:%ld\tmaxmsg:%ld\tmsgsize:%ld\tcurmsgs:%ld\n", attr.mq_flags, attr.mq_maxmsg, attr.mq_msgsize, attr.mq_curmsgs);
    mq_close(mqd);
    exit(0);
}

取消息和推送消息

推送消息:

/**
 * @file mqsend.c
 * 往消息队列上发送消息,消息有优先级
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
#define MQNAME "/temp.1234"
#define MSG1 "this is a msg, prio = 10."
#define MSG2 "this is a msg, prio = 20."
int main(int argc, char **argv)
{
    char *buf;
    size_t buf_len = 1024;
    unsigned int prio1 = 10;
    unsigned int prio2 = 20;
    mqd_t mqd;

    // 打开消息队列
    mqd = mq_open(MQNAME, O_RDWR);
    if (mqd == (mqd_t)-1)
    {
        perror("mq_open");
        exit(-1);
    }

    // 申请缓冲区内存
    buf = (char *)malloc(buf_len);
    if (buf == NULL)
    {
        fprintf(stderr, "malloc\n");
        exit(-1);
    }

    
    // 发送
    strncpy(buf, MSG1, sizeof(MSG1));
    if (mq_send(mqd, buf, buf_len, prio1) < 0)
    {
        perror("mq_send");
        exit(-1);
    }
    strncpy(buf, MSG2, sizeof(MSG2));
    if (mq_send(mqd, buf, buf_len, prio2) < 0)
    {
        perror("mq_send");
        exit(-1);
    }

    free(buf);
    mq_close(mqd);
    exit(0);
}

接受消息:

/**
 * @file 3-mqreceive.c
 * 接受消息,阻塞
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
#include <strings.h>
#define MQNAME "/temp.1234"
int main(int argc, char **argv)
{
    char *buf;
    long buf_len;
    mqd_t mqd;
    struct mq_attr attr;
    // 打开消息队列
    mqd = mq_open(MQNAME, O_RDONLY);
    if (mqd == (mqd_t)-1)
    {
        perror("mq_open");
        exit(-1);
    }

    // 获取消息队列的属性
    bzero(&attr, sizeof(attr));
    mq_getattr(mqd, &attr);
    buf_len = attr.mq_msgsize;

    // 给buf申请内存
    buf = (char *)malloc(buf_len);
    if (buf == NULL)
    {
        fprintf(stderr, "malloc\n");
        mq_close(mqd);
        exit(-1);
    }

    // 接受消息
    // prio == 0, 表示接受优先级最高的,可以指定优先级
    // 如果指定的优先级不存在,就接受最高优先级
    unsigned int prio = 20;
    bzero(buf, buf_len);
    mq_receive(mqd, buf, buf_len, &prio);
    printf("msg:%s\n", buf);
    bzero(buf, buf_len);
    mq_receive(mqd, buf, buf_len, &prio);
    printf("msg:%s\n", buf);

    exit(0);
}

使用mq_notify()通知接收消息

/**
 * @file 4-1mqnotify.c
 * 简单的信号通知
 * 
 * 使用消息队列异步通知
 * 1.mq_notify是一次性的,想要多次使用,必须重复注册
 * 2.任意时刻任何进程,只能有一个注册接受通知
 * 3.空指针代表注销通知
 * 4.receive的阻塞比通知优先级高
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <signal.h>
#include <string.h>
#include <strings.h>
#include <mqueue.h>
#include <unistd.h>
#define MQNAME "/temp.1234"

mqd_t mqd;
void *buf;
long buf_len;
struct mq_attr attr;
struct sigevent sigev;

/**
 * 信号处理函数
 */
static void sig_usr(int p)
{
    // 重新注册
    mq_notify(mqd, &sigev);

    // 收到信号就去消息队列里取消息
    mq_receive(mqd, (char*)buf, buf_len, NULL);
    printf("msg:%s\n", buf);

    // 清空
    bzero(buf, buf_len);

    return;
}
    
int main(int argc, char **argv)
{
    // 打开消息队列
    mqd = mq_open(MQNAME, O_RDWR);
    if (mqd == (mqd_t)-1)
    {
        perror("mq_open");
        exit(-1);
    }

    // 获取属性
    mq_getattr(mqd, &attr);

    // 获取最大size
    buf_len = attr.mq_msgsize;

    // buf申请内存
    buf = (void *)malloc(buf_len);
    if (buf == NULL)
    {
        fprintf(stderr, "malloc\n");
        exit(-1);
    }

    // 注册信号
    signal(SIGUSR1, sig_usr);

    // 设置信号响应
    bzero(&sigev, sizeof(sigev));
    sigev.sigev_notify = SIGEV_SIGNAL;
    sigev.sigev_signo = SIGUSR1;

    // 注册通知者
    if (mq_notify(mqd, &sigev) < 0)
    {
        perror("mq_notify");
        exit(-1);
    }

    for ( ;; )
    {
        sleep(1);
    }

    exit(0);
}

由于非实时信号会丢失,下列代码使用阻塞信号改进,并改用非阻塞读取:

/**
 * @file 4-2mqnotify.c
 * 非阻塞的mq_notify的信号通知
 * 使用阻塞信号临界区
 * 注意信号的很多东西得是gnuc标准
 */
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <errno.h>
#define MQNAME "/temp.1234"
volatile sig_atomic_t mqflag;
static void sig_user1(int p);

int main(int argc, char **argv)
{
    mqd_t mqd;
    void *buf;
    sigset_t zeromask, newmask, oldmask;
    struct mq_attr attr;
    struct sigevent sigev;

    // 非阻塞打开消息队列
    mqd = mq_open(MQNAME, O_RDONLY | O_NONBLOCK);
    if (mqd == (mqd_t)-1)
    {
        perror("mq_open");
        exit(-1);
    }

    // 获取消息队列的属性
    mq_getattr(mqd, &attr);

    // 给存储区申请内存
    buf = malloc(attr.mq_msgsize);

    // 清空信号屏蔽字
    sigemptyset(&zeromask);
    sigemptyset(&newmask);
    sigemptyset(&oldmask);

    // 添加阻塞信号
    sigaddset(&newmask, SIGUSR1);

    // 注册信号处理函数
    signal(SIGUSR1, sig_user1);

    // 设置通知信号
    sigev.sigev_notify = SIGEV_SIGNAL;
    sigev.sigev_signo = SIGUSR1;

    // 注册信号通知
    mq_notify(mqd, &sigev);

    // 循环等待信号
    while (1)
    {
        // 阻塞信号,形成信号临界区代码,这个函数不能在多线程中使用,因为我们应该只改变当前线程的掩码
        sigprocmask(SIG_BLOCK, &newmask, &oldmask);

        // 这中间得代码不能被信号打断,来的信号全部排队阻塞

        // 从阻塞队列里取出一个信号处理
        while (mqflag == 0)
        {
            // 暂停阻塞,暂停线程,直到某个某个信号从处理函数返回
            // 相当用从信号临界区,取出一个阻塞的信号,原子地等待它的处理函数执行完毕
            // 然后再将临界区封闭,继续阻塞信号
            sigsuspend(&zeromask);
        }

        // 重新设置等待条件
        mqflag = 0;
        mq_notify(mqd, &sigev);

        // 读数据
        ssize_t n;
        while ((n = mq_receive(mqd, (char*)buf, attr.mq_msgsize, NULL)) >= 0)
        {
            printf("msg:%s\n", buf);
        }

        // 判断错误,因为mqd是非阻塞的,所以如果的读的时候没有数据,就以EAGAIN错误返回
        // 但是这个错误不是程序错误
        if (errno != EAGAIN) 
        {
            perror("mq_receive");
            mq_close(mqd);
            exit(-1);
        }

        // 接触阻塞,临界区代码下边界
        sigprocmask(SIG_UNBLOCK, &newmask, NULL);
    }
    exit(0);
}
void sig_user1(int p)
{
    mqflag = 1;
    return;
}

使用sigwait改进上述代码,提升效率。

/**
 * @file 4-3mqnotify.c
 * 使用sigwait代替sigsuspend
 * 注意,我们一直使用的是signal
 * 这个函数是有缺陷的,因为它会在执行一个信号处理函数的时候,被另一个信号打断而跑去执行另一个,
 * 虽然可以返回来,但是返回的环境可能会不同。
 * 如果不想信号在处理函数中被别的信号打断,应该使用sigaction()函数。
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <signal.h>
#include <errno.h>
#define MQNAME "/temp.1234"
int main(int agrc, char **argv)
{
    mqd_t mqd;
    void *buf;
    struct mq_attr attr;

    mqd = mq_open(MQNAME, O_RDONLY | O_NONBLOCK);
    if (mqd == (mqd_t)-1)
    {
        perror("mq_open");
        exit(-1);
    }

    mq_getattr(mqd, &attr);

    buf = malloc(attr.mq_msgsize);
    if (buf == NULL)
    {
        fprintf(stderr, "malloc failed.\n");
        exit(-1);
    }

    sigset_t newmask;
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGUSR1);

    sigprocmask(SIG_BLOCK, &newmask, NULL);

    struct sigevent sigev;
    sigev.sigev_notify = SIGEV_SIGNAL;
    sigev.sigev_signo = SIGUSR1;

    mq_notify(mqd, &sigev);

    while (1)
    {
        int signo;
        // 这个参数是信号集,信号集和信号屏蔽字本质都是sigset_t类型的位图,这里信号集中只有SIGUSR1
        // 而做阻塞用的时候,也是只阻塞SIGUSR1
        // 这个函数可以用在多线程里,因为它执行的时候会暂时挂起线程,只返回响应的信号,并把信号编号填充signo
        sigwait(&newmask, &signo);

        if (signo == SIGUSR1)
        {
            // 读
            mq_notify(mqd, &sigev);
            int n;
            while ((n = mq_receive(mqd, (char *)buf, attr.mq_msgsize, NULL)) >= 0)
            {
                printf("msg:%s\n", buf);
            }
            if (errno != EAGAIN)
            {
                perror("mq_receive");
                exit(-1);
            }
        }
    }
}

使用select改进代码:

/**
 * @file 4-4mqnotify.c
 * 使用select
 * 因为mqd_t文件描述符不是普通的文件描述符,不能直接用于select
 * 所以我们需要桥接,管道描述符可以用与select,可以使用管道描述符和信号进行桥接
 */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <mqueue.h>
#include <signal.h>
#include <errno.h>
#define MQNAME "/temp.1234"
int pipefd[2];
static void sig_usr(int p)
{
    // 写的目的就是为了触发select的活动
    write(pipefd[1], " ", 1);
    return;
}

int main(int argc, char **argv)
{
    mqd_t mqd;
    void *buf;
    struct mq_attr attr;

    mqd = mq_open(MQNAME, O_RDONLY | O_NONBLOCK);
    if (mqd == (mqd_t)-1)
    {
        perror("mq_open");
        exit(-1);
    }

    mq_getattr(mqd, &attr);

    buf = malloc(attr.mq_msgsize);
    if (buf == NULL)
    {
        fprintf(stderr, "malloc failed.\n");
        exit(-1);
    }

    // 新建一个管道
    pipe(pipefd);

    signal(SIGUSR1, sig_usr);

    struct sigevent sigev;
    sigev.sigev_notify = SIGEV_SIGNAL;
    sigev.sigev_signo = SIGUSR1;
    mq_notify(mqd, &sigev);

    // 文件描述符集,防止需要select监听的描述符
    fd_set fset;
    // 先清空
    FD_ZERO(&fset);

    while (1)
    {
        // 把管道的读端添加到文件描述符集合
        FD_SET(pipefd[0], &fset);

        // 最大描述符, 监听读,监听写,监听错误,时间
        select(pipefd[0] + 1, &fset, NULL, NULL, NULL);

        // 检查响应的是不是pipefd[0]
        // 活动的描述符被保留,未活动的会被select设置为0,所以每次循环都要重置fset
        // 同样这个fset也会被整个拷贝道内核中
        if(FD_ISSET(pipefd[0], &fset))
        {
            char ch;
            read(pipefd[0], &ch, 1);    // 把标记字符读出来

            mq_notify(mqd, &sigev);

            int n;
            while ((n = mq_receive(mqd, (char*)buf, attr.mq_msgsize, NULL)) >= 0)
            {
                printf("msg:%s\n", buf);
            }
            if (errno != EAGAIN)
            {
                perror("mq_receive");
                exit(-1);
            }
        }
    }
    exit(0);
}

正统的mq_notify()用法——调用线程

/**
 * @file 4-5mqnotify.c
 * 使用mq_notify()的正统用法——调用线程
 */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>
#include <mqueue.h>
#define MQNAME "/temp.1234"

mqd_t mqd;
struct mq_attr attr;
struct sigevent sigev;

static void notify_thread(union sigval sv)
{
    int n;
    void *buf;
    buf = malloc(attr.mq_msgsize);
    if (buf == NULL)
    {
        fprintf(stderr, "malloc failed.\n");
        exit(-1);
    }
    mq_notify(mqd, &sigev);

    while ((n = mq_receive(mqd, (char *)buf, attr.mq_msgsize, NULL)) >= 0)
    {
        printf("msg:%s\n", buf);
    }
    if (errno != EAGAIN)
    {
        perror("mq_receive");
        exit(-1);
    }
    free(buf);
    pthread_exit(NULL);
}

int main()
{
    mqd = mq_open(MQNAME, O_RDONLY | O_NONBLOCK);
    if (mqd == (mqd_t)-1)
    {
        perror("mq_open");
        exit(-1);
    }

    mq_getattr(mqd, &attr);

    sigev.sigev_notify = SIGEV_THREAD;
    sigev.sigev_value.sival_ptr = NULL;
    sigev.sigev_notify_function = notify_thread;
    sigev.sigev_notify_attributes = NULL;

    mq_notify(mqd, &sigev);
    
    while (1)
    {
        pause();
    }
    exit(0);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虚知道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值