《Linux应用进程间通信(三) — 消息队列》

1.消息队列

  消息队列:提供一种从一个进程向另一个进程发送一个数据块的方法。与FIFO相比,消息队列的优势在于,它独立于发送和接收进程而存在。 

  1.链表式结构组织,存放于内核。

  2.通过队列标识来引用。

  3.通过一个数据类型来索引指定的数据。

 

2.msgget函数

#include <sys/msg.h>

int msgget(key_t key, int msgflg);
第一个参数key:每一个IPC对象与一个key对应。也可以由函数ftok生成。
第二个参数msgflg:函数的行为(0666|IPC_CREAT表示用户具有读写权限)
          IPC_CREAT值,若没有该队列,则创建一个并返回新标识符;若已存在,则返回原标识符。
          IPC_EXCL值,若没有该队列,则返回-1;若已存在,则返回0。
返回值: 成功:非负队列ID; 失败:-1;

   作用 :创建或打开一个现有队列。

  可通过ipcs -q(只查看消息队列的状态):查看系统的IPC状态

 

3.msgsnd函数

#include<sys/msg.h>

int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);

第一个参数msqid是由msgget函数返回的消息队列标识符;
第二个参数msg_ptr是一个指向准备发送消息的指针,消息必须像刚才说的那样以一个长整型成员变量开始;
第三个参数msg_sz是msg_ptr指向的消息的长度。这个长度不能包括长整型消息类型成员变量的长度;
第四个参数msgfig:
  0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列。
  IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回。
  IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程
             返回值: 成功:0; 失败:-1;

  作用:把消息添加到消息队列中。

  消息的结构受两方面的约束。首先,它的长度必须小于系统规定的上限;其次,它必须以一个长整型成员变量开始。接收函数将用这个成员变量来确定消息的类型。当使用消息时,最好把消息结构定义为下面这样:

  struct my_message{

    long int message_type;   /* 必须大于0,消息类型 */

    char mtext[512];    //假如我们要发送512个字节的数据

  }

  由于在消息的接收中要用到message_type,所以你不能忽略它。你必须在声明自己的数据结构时包含它,并且最好将它初始化为一个已知值。

 

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

①    不满足消息队列满或个数满两个条件,即消息队列中有容纳该消息的空间。

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

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

 

4.msgrcv函数

#include<sys/msg.h>

int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
第一个参数msqid是由msgget函数返回的消息队列标识符;
第二个参数msg_ptr是一个指向准备接收消息的指针,消息必须像前面msgsnd函数中介绍的那样以一个长整型成员变量开始。
第三个参数msg_sz是msg_ptr指向的消息的长度,它不包括长整型消息类型成员变量的长度。
第四个参数msgtype是一个长整型,它可以实现一种简单形式的接收优先级。如果msgtype的值为0,就获取队列中的第一个可用消息。如果它的值大于零,将获取具有相同消息类型的第一个消息。如果它的值小于零,将获取消息类型等于或小于msgtype的绝对值的第一个消息。
这个函数看起来好像很复杂,但实际应用很简单。如果只想按照消息发送的顺序来接收它们,就把msgtype设置为0。如果只想获取某一特定类型的消息,就把msgtype设置为相应的类型值。如果想接收类型等于或小于n的消息,就把msgtype设置为-n。
第五个参数msgflg:
  0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待。
  IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG。
  IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息。
  IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部

成功:实际读取到的消息数据长度。 出错:-1,错误原因存于error中。

  作用:从队列中取用消息。

  其中对第四个参数举例说明:消息队列中要求通过一个数据类型来索引指定的数据。也就是在msgsng发送数据前会对long int message_type变量进行复制。通过这个变量来标识数据。而且这个变量必须要大于0.

  那么在msgrcv中,对第四个参数msgtype进行选值时。

  如果等于0,则按照队列的顺序去取,先到先取的原则。

  如果大于0,假如msgtype=1。就会取找队列中message_type等于1的队列的排在第一个的消息。

  如果小于0,假如msgtype=-2。就会-2取绝对值,也就是等于2。再取队列中message_type等于或者小于2的排在第一个的消息。

 

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

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

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

③    调用msgrcv()的进程被信号中断。

    

5.msgctl函数

#include<sys/msg.h>

int msgctl(int msqid, int command, struct msqid_ds *buf);
第一个参数msqid是由msgget返回的消息队列标识符;
第二个参数command是将要采取的动作,它可以取3个值。
IPC_STAT  取出队列的msqid_ds结构,并将它存放在buf指向的结构中。   
IPC_SET   如果进程有足够的权限,将buf指向的结构复制到与这个队列相关的msqid_ds结构中。
IPC_RMID  从系统中删除该消息队列以及仍在该队列中的所有数据。这种删除立即生效。仍在使用这一消息队列的其他进程在它们下一次试图对此队列操作,将得到EIDRM错误。

返回值: 成功:0; 失败:-1

 

6. 消息存在性以及性能

   一旦接收进程成功接收了某个消息,该消息会从消息队列中移除。因此,消息队列中不再存在该消息。因此消息队列无法实现一发多收场景。

  多个进程写入到消息队列,消息队列会按照FIFO的原则进行排序,直到队列满。接收进程可以根据设置不同的type来提取消息。

 消息队列的瓶颈场景:

  • 高频消息收发:
    • 例如每秒发送/接收数万条消息,系统调用的开销会显著降低吞吐量。
  • 小消息频繁传输:
    • 每次消息的数据量很小(如几个字节),但系统调用的开销占主导。
  • 高并发:
    • 多个进程同时调用 msgsnd/msgrcv,可能导致内核锁竞争。

优化:

  • 批量发送/接收:
    • 合并多条小消息为一条大消息,减少系统调用次数。
  • 使用非阻塞模式:
    • 结合 IPC_NOWAIT 和重试机制,避免阻塞。
  • 替代方案:
    • 高频场景下,考虑使用 ZeroMQ、Redis Streams 或共享内存。

 

 

 

7.msgctl.c实例

msgctl.c源代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <error.h>

#define TEXT_SIZE  512
struct msgbuf
{
    long mtype;
    char mtext[TEXT_SIZE];
};

int main(int argc, char **argv)
{
    int msqid ;
    struct msqid_ds info ;
    struct msgbuf buf ;
    struct msgbuf buf1 ;
    int flag ;
    int sendlength, recvlength ;
 
    msqid = msgget( IPC_PRIVATE, 0666 ) ;
    if ( msqid < 0 )
    {
        perror("get ipc_id error") ;
        return -1 ;
    }
 
    buf.mtype = 1 ;
    strcpy(buf.mtext, "happy new year!") ;
    sendlength = sizeof(struct msgbuf) - sizeof(long) ;
    flag = msgsnd( msqid, &buf, sendlength , 0 ) ;
    if ( flag < 0 )
    {
        perror("send message error") ;
        return -1 ;
    }
    buf.mtype = 3 ;
    strcpy(buf.mtext, "good bye!") ;
    sendlength = sizeof(struct msgbuf) - sizeof(long) ;
    flag = msgsnd( msqid, &buf, sendlength , 0 ) ;
    if ( flag < 0 )
    {
        perror("send message error") ;
        return -1 ;
    }
 
    flag = msgctl( msqid, IPC_STAT, &info ) ;
    if ( flag < 0 )
    {
        perror("get message status error") ;
        return -1 ;
    }
    printf("uid:%d, gid = %d, cuid = %d, cgid= %d\n" ,
           info.msg_perm.uid,  info.msg_perm.gid,  info.msg_perm.cuid,  info.msg_perm.cgid  ) ;
    printf("read-write:%03o, cbytes = %lu, qnum = %lu, qbytes= %lu\n" ,
           info.msg_perm.mode&0777, info.msg_cbytes, info.msg_qnum, info.msg_qbytes ) ;
    system("ipcs -q") ;
    recvlength = sizeof(struct msgbuf) - sizeof(long) ;
    memset(&buf1, 0x00, sizeof(struct msgbuf)) ;
 
    flag = msgrcv( msqid, &buf1, recvlength ,3,0 ) ;
    if ( flag < 0 )
    {
        perror("recv message error") ;
        return -1 ;
    }
    printf("type=%ld, message=%s\n", buf1.mtype, buf1.mtext) ;
 
    flag = msgctl( msqid, IPC_RMID,NULL) ;
    if ( flag < 0 )
    {
        perror("rm message queue error") ;
        return -1 ;
    }
    system("ipcs -q") ;
 
   return 0 ;
}

编译 gcc msgctl.c –o msgctl。

执行 ./msg,执行结果如下:

uid:1008, gid = 1003, cuid = 1008, cgid= 1003

read-write:666, cbytes = 1024, qnum = 2, qbytes= 163840

------ Message Queues --------

key                 msqid      owner      perms      used-bytes   messages   

0x00000000   65536      zjkf          666          1024            2      

type=3, message=good bye!

 

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages

 

8.两进程通过消息队列收发消息

(1)发送消息队列程序

msgsnd.c源代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <time.h>

#define TEXT_SIZE  512
struct msgbuf
{
    long mtype ;
    int  status ; 
    char time[20] ;
    char mtext[TEXT_SIZE] ;
}  ;
char  *getxtsj()
{  
    time_t  tv ;
    struct  tm   *tmp ;
    static  char  buf[20] ;
    tv = time( 0 ) ;
    tmp = localtime(&tv) ;
    sprintf(buf,"%02d:%02d:%02d",tmp->tm_hour , tmp->tm_min,tmp->tm_sec);
    return   buf ;
}

int main(int argc, char **argv)
{
    int msqid ;
    struct msqid_ds info ;
    struct msgbuf buf ;
    struct msgbuf buf1 ;
    int flag ;
    int sendlength, recvlength ;
    int key ;
 
    key = ftok("msg.txt", 0x01 ) ;
    if ( key < 0 )
    {
        perror("ftok key error") ;
        return -1 ;
    }
 
    msqid = msgget( key, 0600|IPC_CREAT ) ;
    if ( msqid < 0 )
    {
        perror("create message queue error") ;
        return -1 ;
    }
 
    buf.mtype = 1 ;
    buf.status = 9 ; 
    strcpy(buf.time, getxtsj()) ;
    strcpy(buf.mtext, "happy new year!") ;
    sendlength = sizeof(struct msgbuf) - sizeof(long) ;
    flag = msgsnd( msqid, &buf, sendlength , 0 ) ;
    if ( flag < 0 )
    {
        perror("send message error") ;
        return -1 ;
    }
    buf.mtype = 3 ;
    buf.status = 9 ; 
    strcpy(buf.time, getxtsj()) ;
    strcpy(buf.mtext, "good bye!") ;
    sendlength = sizeof(struct msgbuf) - sizeof(long) ;
    flag = msgsnd( msqid, &buf, sendlength , 0 ) ;
    if ( flag < 0 )
    {
        perror("send message error") ;
        return -1 ;
    }
    system("ipcs -q") ;
   return 0 ;
}

(2)接收消息队列程序

msgrcv.c源代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define TEXT_SIZE  512
struct msgbuf
{
    long mtype ;
    int  status ; 
    char time[20] ;
    char mtext[TEXT_SIZE] ;
};
int main(int argc, char **argv)
{
    int msqid ;
    struct msqid_ds info ;
    struct msgbuf buf1 ;
    int flag ;
    int  recvlength ;
    int key ;
    int mtype ;
 
    key = ftok("msg.txt", 0x01 ) ;
    if ( key < 0 )
    {
        perror("ftok key error") ;
        return -1 ;
    }
 
    msqid = msgget( key, 0 ) ;
    if ( msqid < 0 )
    {
        perror("get ipc_id error") ;
        return -1 ;
    }
 
    recvlength = sizeof(struct msgbuf) - sizeof(long) ;
    memset(&buf1, 0x00, sizeof(struct msgbuf)) ;
    mtype = 1 ;
    flag = msgrcv( msqid, &buf1, recvlength ,mtype,0 ) ;
    if ( flag < 0 )
    {
        perror("recv message error") ;
        return -1 ;
    }
    printf("type=%ld,time=%s, message=%s\n", buf1.mtype, buf1.time,  buf1.mtext) ;
    system("ipcs -q") ;
    return 0 ;
}

(3)编译与执行程序

①    在当前目录下利用>msg.tmp建立空文件msg.tmp。

②    编译发送消息队列程序 gcc msgsnd.c -o  msgsnd。

③    执行./msgsnd,执行结果如下:

  ----- Message Queues --------

key                  msqid        owner      perms      used-bytes   messages   

0x0101436d    294912      zjkf          600          1072            2          

④    编译接收消息程序 gcc msgrcv.c -o msgrcv

⑤    执行./msgrcv,执行结果如下:

 type=1,time=03:23:16, message=happy new year!

 

------ Message Queues --------

key                  msqid        owner      perms      used-bytes   messages   

0x0101436d    294912     zjkf           600          536              1         

⑥    利用ipcrm -q 294912删除该消息队列。因为消息队列是随内核持续存在的,在程序中若不利用msgctl函数或在命令行用ipcrm命令显式地删除,该消息队列就一直存在于系统中。另外信号量和共享内存也是随内核持续存在的。

 

 消息队列的优缺点:

  • 优点:
    • 进程无关性,支持异步通信。
    • 消息持久化,避免数据丢失。
    • 支持消息类型,便于优先级处理。
  • 缺点:
    • 消息大小受限(通常受系统限制,如8KB)。
    • 频繁的系统调用可能影响性能。
    • 需要手动处理队列的创建和删除。

适用场景:

  • 异步任务处理(如日志记录、任务分发)。
  • 进程间解耦(如生产者-消费者模型)。
  • 需要消息持久化的场景(如系统重启后恢复任务)。
  • IoT 数据采集:传感器设备(生产者)高速上报数据,服务器(消费者)按需处理。

 

消息队列与管道/共享内存的区别:

  • 管道:
    • 半双工,数据只能单向流动。
    • 通常用于父子进程,数据不持久化。
  • 共享内存:
    • 速度最快,但需要同步机制(如信号量)。
    • 无消息边界,需手动处理数据。
  • 消息队列:
    • 支持异步、持久化、消息类型。
    • 适合松耦合的进程通信。

消息队列的性能:

  • 性能低于共享内存,但高于管道。
  • 适合消息量不大但需要异步通信的场景。
  • 高频通信场景建议使用共享内存+信号量。

 

消息队列与信号量、共享内存如何配合使用:

  • 典型场景:
    • 使用共享内存存储大量数据。
    • 使用信号量控制对共享内存的访问。
    • 使用消息队列通知其他进程数据已更新。
// 进程A:更新共享内存并发送消息
sem_p(semid); // 获取信号量
update_shared_memory();
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
sem_v(semid); // 释放信号量

// 进程B:接收消息并处理共享内存
msgrcv(msqid, &msg, sizeof(msg.mtext), 0, 0);
sem_p(semid); // 获取信号量
process_shared_memory();
sem_v(semid); // 释放信号量

 

posted @ 2020-06-17 18:14  一个不知道干嘛的小萌新  阅读(624)  评论(0)    收藏  举报