进程间通信之POSIX消息队列-mq_open,mq_send, mq_receive

POSIX IPC

这里是POSIX IPC,注意与System V相关的IPC不一样,主要区别在于,所有POSIX IPC都是线程安全的,而大多数SysV IPC不是。.POSIX是为了使基于UNIX的系统的接口标准化而创建的。

POSIX消息队列相关函数

(1)mq_open函数

功能:用来创建和访问一个消息队列
原型:

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:与open函数类似,可以是O_RDONLY,O_WRONLY, O_RDWR, 还可以按位或上O_CREAT, O_EXCL, O_NONBLOCK等。
mode:如果oflag指定了O_CREAT,需要设置mode

返回值:

成功返回消息队列文件描述符;失败返回-1

mq_open函数

1)封装一个创建消息队列的函数

#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>
 
 mqd_t mq_open_safe(char *name, int oflag)
 {
        mqd_t mqid;
        mqid = mq_open(name, oflag/*O_CREAT | O_RDWR*/, 0666, NULL);
        if(mqid == (mqd_t)-1)
        {
              perror("mq_open error");
              exit(EXIT_FAILURE);
        }
        return mqid;
 }

2)创建完之后,并不能像System V消息队列那样使用ipcs命令查看。那么该怎么查看呢?查看MAN手册:man 7 mq_overview

3)也就是说,创建完之后,消息队列其实已经存在了,不过是存在一个虚拟的文件系统中,但是这个文件系统需要挂载到某一个目录下面才可以,man手册中提示,可以创建挂载点到/dev/目录下:
$ mkdir /dev/mqueue
$ mount -t mqueue none /dev/mqeue

挂载之后,进入mqueue目录,即可看到创建的消息队列,并且可以使用cat命令查看该消息队列当前的状态:

4)POSIX IPC名字限定
必须以/开头,并且后续不能有其他/,形如/abc;长度不能超过NAME_MAX

mq_close函数

功能:关闭消息队列
原型:mqd_t mq_close(mqd_t mqdes);
参数:
mqdes:消息队列描述符
返回值:成功返回0;失败返回-1
例如,上一个示例,在程序返回前,调用mq_close(mqid);

mq_unlink函数

功能:删除消息队列
原型:mqd_t mq_unlink(const char *name);
参数:
name:消息队列名字,而不是消息队列描述符mqdes
返回值:成功返回0;失败返回-1

1)更准确的含义是删除一个连接数,直到连接数减为0的时候,才真正将文件删除。使用如下命令可以查看文件的连接数:

可以通过ls -l 对应文件名 查看文件的连接数

mq_getattr/mq_setattr两个函数

功能:获取/设置消息队列属性
原型:
mqd_t mq_getattr(mqd_t mqdes,struct mq_attr *attr);
mqd_t mq_setattr(mqd_t mqdes, struct mq_attr *newattr, struct mq_attr *oldattr);
返回值:成功返回0;失败返回-1

其中结构体mq_attr如下:

struct mq_attr {
               long mq_flags;       /* Flags: 0 or O_NONBLOCK */
               long mq_maxmsg;      /* Max. # of messages on queue */
               long mq_msgsize;     /* Max. message size (bytes) */
               long mq_curmsgs;     /* # of messages currently in queue */
           };

1)封装获取消息队列属性:

mqd_t mq_getattr_safe(mqd_t mqdes,struct mq_attr *attr)
{
     mqd_t mq_getattr;
     mq_getattr  = mq_getattr(mqid, &attr);
     if(mq_getattr == -1)
      {
              perror("mq_getattr error");
              exit(EXIT_FAILURE);
      }
      return mq_getattr ;
}

mq_send函数

功能:发送消息
原型:
mqd_t mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);
参数:
mqdes:消息队列描述符
msg_ptr:指向消息的指针
msg_len:消息长度
msg_prio:消息优先级 它是一个小于MQ_PRIO_MAX的数,数值越大,优先级越高。POSIX消息队列在调用mq_receive时总是返回队列中最高优先级的最早消息。如果消息不需要设定优先级,那么可以在mq_send是置msg_prio为0,mq_receive的msg_prio置为NULL。
返回值:成功返回0;失败返回-1

封装该函数,并尝试向消息队列中发送一条消息:

typedef struct stu
{
        char name[32];
        int age;
}STU;
 
mqd_t mq_send_safe(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned msg_prio)
{
     mqd_t check;
     check = mq_send(mqdes, (const char*)msg_ptr, msg_len, msg_prio);
      if(check = 1)
      {
            perror("mq_getattr error");
             exit(EXIT_FAILURE);
      }
      return check;
}

int main(int argc, char *argv[])
{
        if (argc != 2){
                fprintf(stderr, "Usage: %s <prior>\n", argv[0]);
        }
 
        mqd_t mqid;
        mqid = mq_open("/abc", O_WRONLY);
        if (mqid == (mqd_t)-1)
                ERR_EXIT("mq_open");
 
        STU stu;
        strcpy(stu.name, "Leo");
        stu.age = 25;
 
        unsigned prio = atoi(argv[1]);
        mq_send_safe(mqid, (const char*)&stu, sizeof(stu), prio);
        mq_close(mqid);
 
        return 0;
}
mq_receive函数

功能:接收消息
原型:
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio);
参数:
mqdes:消息队列描述符
msg_ptr:可接收的消息
msg_len:消息长度
msg_prio:返回接受到的消息优先级
返回值:成功返回接收到的消息字节数;失败返回-1
注意:返回指定消息队列中最高优先级的最早消息

使用该函数,实现对消息的接收:

typedef struct stu
{
        char name[32];
        int age;
}STU;
 
int main(int argc, char *argv[])
{
        mqd_t mqid;
        mqid = mq_open("/abc", O_RDONLY);
        if (mqid == (mqd_t)-1)
                ERR_EXIT("mq_open");
 
        struct mq_attr attr;
        mq_getattr(mqid, &attr);
        size_t size = attr.mq_msgsize;
        STU stu;
 
        unsigned prio;
        if (mq_receive(mqid, (char*)&stu, size, &prio) == (mqd_t)-1)
                ERR_EXIT("mq_receive");
 
        printf("name=%s age=%d prio=%u\n", stu.name, stu.age, prio);
        mq_close(mqid);
        return 0;
 }
mq_notify函数

功能:建立或删除消息到达通知时间
原型
mqd_t mq_notify(mqd_t mqdes, const struct sigevent *notification);
参数:
mqdes:消息队列描述符
notification:
非空表示当消息到达且消息队列先前为空,那么将得到通知
NULL表示撤销已注册的通知
返回值:成功返回0;失败返回-1
通知方式:
产生一个信号
创建一个线程执行一个指定的函数

1)其中结构体如下:

 union sigval {          /* Data passed with notification */
           int     sival_int;         /* Integer value */
           void   *sival_ptr;         /* Pointer value */
       };
 
       struct sigevent {
           int          sigev_notify; /* Notification method */
           int          sigev_signo;  /* Notification signal */
           union sigval sigev_value;  /* Data passed with
                                         notification */
           void       (*sigev_notify_function) (union sigval);
                            /* Function used for thread
                               notification (SIGEV_THREAD) */
           void        *sigev_notify_attributes;
                            /* Attributes for notification thread
                               (SIGEV_THREAD) */
           pid_t        sigev_notify_thread_id;
                            /* ID of thread to signal (SIGEV_THREAD_ID) */
       };

sigev_notify有三个取值:
SIGEV_NONE:表示不会又通知;
SIGEV_SIGNAL:表示以信号的方式来通知;需要指定sigev_signo和sigev_value
SIGEV_THREAD:表示以线程的方式来通知。需要指定结构体中最后两个参数

2)使用该函数,以信号的方式来通知
a)为简单起见,使用SIGUSR1信号
signal(SIGUSR1, handle_sigusr1);
b)注册一个消息到达的通知:
struct sigevent sigev;
sigev.sigev_notify = SIGEV_SIGNAL;
sigev.sigev_signo = SIGUSR1;
mq_notify(mqid, &sigev);

3)实现如下

#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
 
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>
#include <string.h>
#include <signal.h>
 
#define ERR_EXIT(m)\
        do \
        { \
                perror(m);\
                exit(EXIT_FAILURE);\
        }while(0)
 
typedef struct stu
{
        char name[32];
        int age;
}STU;
size_t size;
 
mqd_t mqid;
 
struct sigevent sigev;
 
void handle_sigusr1(int sig)
{
        mq_notify(mqid, &sigev);
        STU stu;
        unsigned int prio;
        if (mq_receive(mqid, (char*)&stu, size, &prio) == (mqd_t)-1)
                ERR_EXIT("mq_receive");
 
        printf("name=%s age=%d prio=%u\n", stu.name, stu.age, prio);
 
}
 
int main(int argc, char *argv[])
{
        mqid = mq_open("/abc", O_RDONLY);
        if (mqid == (mqd_t)-1)
                ERR_EXIT("mq_open");
        struct mq_attr attr;
        mq_getattr(mqid, &attr);
        size = attr.mq_msgsize;
 
        signal(SIGUSR1, handle_sigusr1);
 
        sigev.sigev_notify = SIGEV_SIGNAL;
        sigev.sigev_signo = SIGUSR1;
 
        mq_notify(mqid, &sigev);
 
        for(;;)
                pause();
 
        mq_close(mqid);
        return 0;
}

在消息队列中没有消息的情况下,运行该程序,将阻塞。而当另一个程序通过向消息队列中发送消息之后,notify程序将会得到通知,并将结果打印输出:

4)mq_notify注意

任何时刻只能有一个进程可以被注册为接收某个给定队列的通知
当有一个消息到达某个先前为空的队列,而且已有一个进程被注册为接收该队列的通知时,只有没有任何线程阻塞在该队列的mq_receive调用的前提下,通知才会发出。
当通知被发送给它 的注册进程时,其注册被撤销。进程必须再次调用mq_notify以重新注册(如果需要的话),重新注册要放在从消息队列读出消息之前而不是之后。

<think>好的,用户的问题是关于使用POSIXmq_open和execlp的。首先,我需要确认用户的需求是什么。看起来他们可能是在尝试创建一个使用消息队列的父子进程程序,其中父进程通过execlp执行另一个程序,而子进程处理消息队列。 首先,我应该回顾一下POSIX消息队列的基本函数。mq_open用于创建或打开一个消息队列,需要包含<mqueue.h>头文件,链接时加上-lrt。参数方面,name必须以斜杠开头,oflag是标志位比如O_CREAT或O_RDWR,mode是权限设置,attr是消息队列的属性结构体,包括mq_maxmsg和mq_msgsize等。 接下来是execlp函数,它属于exec家族,用于替换当前进程映像。参数是文件名、命令行参数列表,最后以NULL结束。注意execlp会在PATH环境变量中查找可执行文件,所以需要确保路径正确。 用户可能需要一个示例代码,说明如何在父子进程中使用mq_open和execlp。父进程创建消息队列,fork子进程,子进程中调用execlp执行另一个程序,父进程等待子进程结束后关闭和删除消息队列。同时,另一个程序(比如child_program)需要打开同一个消息队列进行操作。 需要注意的是,消息队列的名字在父子进程和execlp调用的程序中必须一致,且权限要正确设置。编译时要链接实时库-lrt,并且两个程序都需要处理消息队列的打开和关闭。 可能用户在实际操作中遇到了问题,比如权限错误、消息队列无法打开,或者execlp执行失败。需要提醒他们检查路径是否正确,是否有足够的权限创建消息队列,以及是否正确处理了错误返回。 总结来说,用户需要明确如何将mq_open和execlp结合使用,特别是在父子进程间传递消息队列,并确保execlp调用的程序能正确访问同一队列。提供示例代码和关键点说明会有助于他们理解和解决问题。</think>好的,我将从POSIX标准相关函数的使用角度为您逐步解释 `mq_open` 和 `execlp` 的应用场景及注意事项: 1. **POSIX消息队列 (mq_open)** - **作用**:创建/打开一个POSIX消息队列 - **函数原型**: ```c mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr); ``` - **关键参数**: - `name`: 必须以`/`开头的队列名称(如`"/my_queue"`) - `oflag`: 标志位组合(`O_CREAT | O_RDWR` 等) - `mode`: 权限设置(如`0644`) - `attr`: 队列属性结构体(可指定`mq_maxmsg`和`mq_msgsize`) 2. **进程执行 (execlp)** - **作用**:替换当前进程映像,执行新程序 - **函数原型**: ```c int execlp(const char *file, const char *arg, ..., (char *) NULL); ``` - **特性**: - 在`PATH`环境变量中搜索可执行文件 - 参数列表必须以`NULL`结尾 3. **典型使用场景**(父子进程通信): ```c /* 父进程代码示例 */ int main() { // 创建消息队列 mqd_t mq = mq_open("/test_queue", O_CREAT | O_RDWR, 0644, NULL); if (fork() == 0) { // 子进程 execlp("./child_program", "child_program", NULL); perror("execlp failed"); // 执行失败才会到达这里 exit(1); } else { // 父进程 wait(NULL); // 等待子进程结束 mq_close(mq); mq_unlink("/test_queue"); } return 0; } ``` 4. **配套程序示例**(child_program.c): ```c int main() { // 打开已存在的队列 mqd_t mq = mq_open("/test_queue", O_RDWR); // 消息操作(mq_send/mq_receive)... mq_close(mq); return 0; } ``` 5. **关键注意事项**: - **编译链接**:需要添加`-lrt`选项 ```bash gcc parent.c -o parent -lrt gcc child_program.c -o child_program -lrt ``` - **权限问题**:确保对`/dev/mqueue`目录有写权限 - **资源清理**:父进程最后需要调用`mq_unlink` - **参数传递**:execlp执行的程序需要通过其他方式(如消息队列本身)获取必要参数 6. **错误处理建议**: ```c mqd_t mq = mq_open("/test_queue", O_CREAT | O_RDWR, 0644, NULL); if (mq == (mqd_t)-1) { perror("mq_open failed"); exit(EXIT_FAILURE); } ``` 这种组合常用于需要跨进程通信的场景:父进程创建资源,子进程通过执行外部程序访问共享资源。消息队列提供异步通信能力,而`execlp`使系统具备更好的模块化特性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

举世无双勇

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

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

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

打赏作者

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

抵扣说明:

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

余额充值