【Linux】学习记录_11_消息队列

发布于:2024-04-22 ⋅ 阅读:(146) ⋅ 点赞:(0)

11 消息队列

消息队列、共享内存 和 信号量 被统称为 system-V IPC,这些对象的操作接口都比较类似, 在系统中他们都使用一种叫做 key 的键值来唯一标识,而且他们都是“持续性”资源—即被创建后, 会持续地存在,除非调用特殊的函数或者命令删除他们。

Linux的IPC对象在内核内部使用链表维护, 不同的对象使用 IPC标识符 来标识,如消息队列标识符 msqid、共享内存标识符 shmid,信号量标识符 semid。

11.1 基本概念

消息队列提供了一种从进程间发送数据块的方法。 每个数据块都含有一个类型,接收进程可独立接收含不同类型的数据结构。 可通过发送消息来避免命名管道的同步和阻塞问题。

11.2 消息队列与信号/管道的对比

消息队列与信号的对比:

  • 信号承载的信息量少,消息队列可以承载大量自定义的数据。

消息队列与管道的对比:

  • 消息队列与命名管道一样,消息队列进行通信的进程可以是不相关的进程, 同时都是通过发送和接收的方式来传递数据的。命名管道中,发送数据用write(),接收数据用read(), 在消息队列中,发送数据用msgsnd(),接收数据用msgrcv(),消息队列对数据都有一个最大长度的限制。

  • 消息队列可独立于发送/接收进程存在,在进程终止时,消息队列及其内容不会被删除。

  • 管道只能承载无格式字节流,消息队列提供有格式的字节流,减少开发人员的工作量。

  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级,接收程序可以通过消息类型有选择地接收数据, 而不是像命名管道中那样,只能默认地接收。

  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的顺序接收,也可以按消息的类型接收。

消息队列的实现包括创建/打开消息队列、发送消息、接收消息和控制消息队列。

11.3 消息队列函数说明

Linux内核提供了一系列函数来使用消息队列:

  • 其中创建或打开消息队列使用的函数是msgget(),这里创建的消息队列的数量会受到系统可支持的消息队列数量的限制;

  • 发送消息使用的函数是msgsnd()函数,它把消息发送到已打开的消息队列末尾;

  • 接收消息使用的函数是msgrcv(),它把消息从消息队列中取走,与FIFO 不同的是,这里可以指定取走某一条消息;

  • 最后控制消息队列使用的函数是msgctl(),它可以完成多项功能。

11.3.1 msgget()获取函数

收发消息前需要具体的消息队列对象,msgget()函数的作用是创建或获取一个消息队列对象, 并返回消息队列标识符。函数原型如下:

int msgget(key_t key, int msgflg);

执行成功返回队列ID,失败返回-1。 它的两个输入参数说明如下:

  • key:消息队列的关键字值,多个进程可以通过它访问同一个消息队列。有个特殊值IPC_PRIVATE,它用于创建当前进程的私有消息队列。

  • msgflg:表示创建的消息队列的模式标志参数

    • 如果是 IPC_CREAT 为真表示:如果内核中不存在关键字与key相等的消息队列,则新建一个消息队列; 如果存在这样的消息队列,返回此消息队列的标识符。

    • 如果为 IPC_CREAT | IPC_EXCL 表示如果内核中不存在键值与key相等的消息队列,则新建一个消息队列; 如果存在这样的消息队列则报错。

    • mode指IPC对象存取权限,使用Linux文件的数字权限表示方式,如0600,0666等

这些参数是可通过“|”运算符联合起来的,因为它始终是int类型的参数。如msgflag使用参数 IPC_CREAT | 0666 时表示, 创建或返回已经存在的消息队列的标识符,且该消息队列的存取权限为0666, 即消息的所有者,所属组用户,其他用户均可对该消息进行读写。

  • 选项 msgflg 是一个位掩码,因此IPC_CREAT、IPC_EXCL和权限mode可用位或方式叠加

  • 权限只有读和写,执行权限是无效的,例如 0777 跟 0666 是等价的。

  • key被指定为IPC_PRIVATE时,系统会自动产生一个未用的key对应一个新的消息队列对象, 这个消息队列一般用于进程内部间的通信。

  • 该函数可能返回以下错误代码:

    • EACCES:指定的消息队列已存在,但调用进程没有权限访问它

    • EEXIST:key指定的消息队列已存在,msgflg中同时指定IPC_CREAT和IPC_EXCL标志

    • ENOENT:key指定的消息队列不存在同时msgflg中没有指定IPC_CREAT标志

    • ENOMEM:需要建立消息队列,但内存不足

    • ENOSPC:需要建立消息队列,但已达到系统的限制

11 .4 消息收发

11.4.1 msgsnd()发送函数
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数说明:

  • msqid:消息队列标识符。

  • msgp:发送给队列的消息。msgp可以是任何类型的结构体,但第一个字段必须为long类型, 即表明此发送消息的类型,msgrcv()函数则根据此接收消息。msgp定义的参照格式如下:

    /* msgp定义的参照格式 */
    struct s_msg
    {
        long type;  /* 必须大于0,消息类型 */
        char mtext[];  /* 消息正文,可以是其他任何类型 */
    } msgp;
    
  • msgsz:发送消息的大小,不包含消息类型占用的4个字节,即mtext的长度。

  • msgflg:为0表示:当消息队列满时msgsnd()函数阻塞,直到消息能写进消息队列; 为IPC_NOWAIT:当消息队列已满时,msgsnd()不等待立即返回; 为IPC_NOERROR:若发送的消息大于size,则把该消息截断,截断部分被丢弃,且不通知发送进程。

  • 返回值:成功返回0,失败返回-1,并且错误原因存于error中。错误代码:

    • EAGAIN:参数msgflg设为IPC_NOWAIT,而消息队列已满。

    • EIDRM:标识符为msqid的消息队列已被删除。

    • EACCESS:无权限写入消息队列。

    • EFAULT:参数msgp指向无效的内存地址。

    • EINTR:队列已满而处于等待情况下被信号中断。

    • EINVAL:无效的参数msqid、msgsz或参数消息类型type小于0。

msgsnd()为阻塞函数,当消息队列容量满或消息个数满会阻塞。若消息队列已被删除,则返回EIDRM错误; 若被信号中断返回E_INTR错误。

如果设置IPC_NOWAIT消息队列满或个数满时会返回-1,并且置EAGAIN错误。

msgsnd()解除阻塞的条件有以下三个条件:

  • 消息队列中有容纳该消息的空间。

  • msqid代表的消息队列被删除。

  • 调用msgsnd函数的进程被信号中断。

11.4.2 msgrcv()发送函数
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数说明:

  • msqid:消息队列标识符。

  • msgp:存放消息的结构体,结构体类型要与msgsnd()函数发送的类型相同

  • msgsz:接收消息的大小,不包含消息类型占用的4个字节

  • msgtyp有多个可选值:为0则表示接收第一个消息,大于0则表示接收类型等于msgtyp的第一个消息, 小于0则表示接收类型等于或者小于msgtyp绝对值的第一个消息。

  • msgflg用于设置接收的处理方式,取值情况如下:

    • 0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待

    • IPC_NOWAIT:若消息队列中没有相应类型的消息,函数立即返回,此时错误码为ENOMSG

    • IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息

    • IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃

  • 返回值:msgrcv()函数如果接收消息成功则返回实际读取到的消息数据长度,否则返回-1,错误原因存于error中。错误代码:

    • E2BIG:消息数据长度大于msgsz而msgflag没有设置IPC_NOERROR

    • EIDRM:标识符为msqid的消息队列已被删除

    • EACCESS:无权限读取该消息队列

    • EFAULT:参数msgp指向无效的内存地址

    • ENOMSG:参数msgflg设为IPC_NOWAIT,而消息队列中无消息可读

    • EINTR:等待读取队列内的消息情况下被信号中断

msgrcv()函数解除阻塞的条件也有三个:

  • 消息队列中有了满足条件的消息。

  • msqid代表的消息队列被删除。

  • 调用msgrcv()函数的进程被信号中断。

11.4.3 msgctl()操作消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);参数说明:
  • msqid:消息队列标识符。

  • cmd 用于设置使用什么操作命令,它的取值有多个:

    • IPC_STAT获取MSG的信息,获取到的信息会储存在结构体msqid_ds类型的buf中。

    • IPC_SET设置消息队列的属性,要设置的属性需先存储在结构体msqid_ds类型的buf中, 可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,储存在结构体msqid_ds中。

    • IPC_RMID立即删除该MSG,并唤醒所有阻塞在MSG上的进程,同时忽略第三个参数

    • IPC_INFO 获得关于当前系统中 MSG 的限制值信息。

    • MSG_INFO 获得关于当前系统中 MSG 的相关资源消耗信息。

    • MSG_STAT 同 IPC_STAT,但 msgid为该消息队列在内核中记录所有消息队列信息的数组的下标, 因此通过迭代所有的下标可以获得系统中所有消息队列的相关信息。

  • buf:相关信息结构体缓冲区。

    • 返回值:

      • 成功:0

      • 出错:-1,错误原因存于error中,错误代码:

        • EACCESS:参数cmd为IPC_STAT,确无权限读取该消息队列。

        • EFAULT:参数buf指向无效的内存地址

        • EIDRM:标识符为msqid的消息队列已被删除。

        • EINVAL:无效的参数cmd或msqid。

        • EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行。