system V IPC --消息队列

System V IPC – 消息队列

  1. 消息队列的创建
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    int msgget(key_t key, int msgflg);
    

    返回值:成功返回消息队列的标识符,失败返回-1并置错误码

    参数1:key值(通常是IPC_PRIVATE或者使用ftok生成:https://blog.csdn.net/weixin_48617416/article/details/120435007)

    参数2:消息队列的标志位,可以加消息队列的权限和标记,如

    1. IPC_CREAT:如果没有与指定的key对应的消息队列,那么就创建一个消息队列,并返回该队列的标识符;如果找到那么就返回该对象的标识符。
    2. IPC_EXCL:在指定IPC_CREAT的前提下使用该标记,并且与指定的key对应的队列已经存在,那么就会调用失败,并返回EEXIST错误。

    代码示例:

    #include <stdio.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    #include <sys/types.h>
    
    int main(int argc, char **argv)
    {
        //可以使用ftok生成key值
        /* key_t key = ftok("./msgque.c", 1); */
        
        //创建消息队列,key值也可以指定某个整型值,第一次调用是创建消息队列,
        //当消息队列存在之后,再执行该代码,不会再创建新的消息队列
        int msgid = msgget(128, IPC_CREAT|0660);
        printf("msgid = %d\n", msgid);
        
        return 0;
    }
    输出结果:
    msgid = 131072
    (ps:环境:Ubuntu18.04,经过测试第一次创建返回的id是0,删除后再重新创建就是一个非零值了)
    

    通过ipcs命令可以查看是否创建成功

    [wy@wy ~/textCode/text/ipc]$  ipcs
    --------- 消息队列 -----------
    键          msqid      拥有者  权限     已用字节数    消息      
    0x00000080 131072       wy    660        0          0 
    

    可以看到,msqid是和程序输出的结果是相同的,说明创建成功。仔细观察键值0x00000080是十六进制的值,转换成10进制后是128,正好是msgget的第一个参数!

    创建的消息队列可以通过ipcrm命令删除

    1. ipcrm -Q + 键值
    2. ipcrm -q + msqid

    也可以使用msgctl()函数删除

  2. 消息的发送
    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    

    返回值:成功返回0,失败返回-1并置错误码

    参数1:msgget返回值

    参数2:指向一个结构体的指针,结构体的定义如下

    struct msgbuf {
    	long mtype;       /* message type, must be > 0 */
     	char mtext[1];    /* message data */
    };
    
    1. 第一个成员是消息类型,值必须大于0,
    2. 第二个成员存储发送的消息,可以看到此时数组大小为1,显然不太满足实际的需求,所以需要程序员复写该结构体,为保证程序健壮性,建议只修改结构体数组大小即可!

    参数3:指定mtext字段包含的字节数

    参数4:标记位,控制msgsnd的操作

    代码示例:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    #include <sys/types.h>
    
    //复写msgbuf结构体
    struct mbuf {
        long mtype;
        char mtext[64];
    };
    
    int main(int argc, char **argv)
    {
        //创建消息队列,key值也可以指定某个整型值
        int msgid = msgget(128, IPC_CREAT|0660);
        printf("msgid = %d\n", msgid);
        
        struct mbuf msg;
        memset(&msg, 0, sizeof(msg));
        
        //接收消息类型
        msg.mtype = atoi(argv[1]);
    
        //接收消息,保存到数组当中
        strcpy(msg.mtext, argv[2]);
    
        msgsnd(msgid, &msg, strlen(msg.mtext), 0);
    
        return 0;
    }
    
    [wy@wy ~/textCode/text/ipc]$ ./msgque 1 hello
    [wy@wy ~/textCode/text/ipc]$ ipcs -q
    
    --------- 消息队列 -----------
    键          msqid     拥有者  权限     已用字节数   消息      
    0x00000080 131072      wy    660        5         1 
    

    向消息对列里发送了5个字节的数据,数据类型为1,可以看到通过ipcs命令查看到的信息有了变化!

  3. 消息的接收
    ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
    

    返回值:成功返回接收到的字节数,失败返回-1,并置错误码

    参数1、2同msgsnd

    参数3:最大接收的数据量

    参数4:接收的数据类型

    参数5:标志位

    代码示例:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    #include <sys/types.h>
    
    //复写msgbuf结构体
    struct mbuf {
        long mtype;
        char mtext[64];
    };
    
    int main(int argc, char **argv)
    {
        int msgid = msgget(128, IPC_CREAT|0660);
        
        struct mbuf msg;
        memset(&msg, 0, sizeof(msg));
        
        //接收消息类型(不同于msgsnd,msgrcv函数的mtype没有必须大于0的限制
        msg.mtype = atoi(argv[1]);
    
        msgrcv(msgid, &msg, sizeof(msg.mtext), msg.mtype, 0);
    
        printf("msg = %s\n", msg.mtext);
    
        return 0;
    }
    
    [wy@wy ~/textCode/text/ipc]$ ./msgrcv 1
    msg = hello
    
    [wy@wy ~/textCode/text/ipc]$ ipcs -q
    --------- 消息队列 -----------
    键        msqid      拥有者  权限     已用字节数   消息      
    0x00000080 131072     wy    660        0         0 
    

    如果执行接收代码,传入的消息类型不在消息队列中,msgrcv是会阻塞的,可以将msgflg设置为IPC_NOWAIT使其变成非阻塞的!

  4. 消息队列和FIFO很像,但是两者差异很大,最大的莫过于,管道是基于字节流进行通信的,数据之间是没有边界的,而消息队列是有的,如下

    [wy@wy ~/textCode/text/ipc]$ ./msgque 1 hello 
    [wy@wy ~/textCode/text/ipc]$ ./msgque 1 world
    [wy@wy ~/textCode/text/ipc]$ ipcs -q
    
    --------- 消息队列 -----------
    键        msqid      拥有者  权限     已用字节数  消息      
    0x00000080 131072     wy    660        10      2 
    

    向消息队列里写入了两条数据类型为1的数据

    [wy@wy ~/textCode/text/ipc]$ ./msgrcv 1 
    msg = hello
    [wy@wy ~/textCode/text/ipc]$ ./msgrcv 1
    msg = world
    

    两次接收,每次只接收一次传入的数据,而且接收顺序是按照消息入队顺序(先进先出)接收的

  5. 下面传入一些不同类型的数据,以及使用不同方式的接收,来看一下消息队列的特性

    1. [wy@wy ~/textCode/text/ipc]$ ./msgque 1 hello 
      [wy@wy ~/textCode/text/ipc]$ ./msgque 2 world 
      [wy@wy ~/textCode/text/ipc]$ ./msgrcv 2 
      msg = world
      [wy@wy ~/textCode/text/ipc]$ ./msgrcv 1
      msg = hello
      

      传入了类型1、2的数据,先接收2类型的数据,并不会造成阻塞,此时队列的特点是只对相同类型的数据起作用

    2. [wy@wy ~/textCode/text/ipc]$ ./msgque 1 hello 
      [wy@wy ~/textCode/text/ipc]$ ./msgque 2 world
      [wy@wy ~/textCode/text/ipc]$ ./msgrcv 0
      msg = hello
      [wy@wy ~/textCode/text/ipc]$ ./msgrcv 0
      msg = world
      

      接收时,数据类型填成了0,接收的数据是按入队顺序无差别接收的

    3. ./msgque 2 hello 
      [wy@wy ~/textCode/text/ipc]$ ./msgque 3 nihao 
      [wy@wy ~/textCode/text/ipc]$ ./msgque 1 world 
      [wy@wy ~/textCode/text/ipc]$ ./msgrcv -2 
      msg = world
      [wy@wy ~/textCode/text/ipc]$ ./msgrcv -2 
      msg = hello
      [wy@wy ~/textCode/text/ipc]$ ./msgrcv -2 
      /* 阻塞 */
      

      接收时,数据类型填成了负值,接收时是按照所有小于等于该负值的绝对值的数据类型的大小顺序接收的!所以先接收了类型为1的数据,再接收数据类型为2的数据,第三次接收时,由于对列中没有小于等于该负数绝对值的数据类型了,所以msgrcv阻塞

    msgrcv接收数据总结:

    mtype接收数据行为
    mtype > 0按数据类型接收,如果存在多个相同的数据类型的数据,那么按入队先后顺序接收
    mtype = 0无差别数据类型接收,且按入队顺序接收
    mtype < 0接收所有 ≤ |mtype|的类型数据,且按照数据类型的由小到大的顺序依次接收
  6. 消息队列的控制
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    

    返回值:不同的cmd,成功返回值不同,但是失败都是返回-1,并置错误码

    参数1:msgget返回值

    参数2:cmd指定在队列上的操作

    参数3:指向存储消息队列相关信息的结构体,定义如下

    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) */
    };
    
    The ipc_perm structure is defined as follows (the uid、gid、mode fields  are
           settable using IPC_SET):
    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 */
    };
    

    代码示例:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    #include <sys/types.h>
    
    int main(int argc, char **argv)
    {
        int msgid = msgget(128, IPC_CREAT|0660);
        
        //删除消息队列,此时忽略第三个参数
        /* msgctl(msgid, IPC_RMID, NULL) ; */  
    
        struct msqid_ds ds;
        memset(&ds, 0, sizeof(ds));
    	
        //获取消息队列的相关信息
        msgctl(msgid, IPC_STAT, &ds);
        printf("msg_perm = %d, msg_qbytes = %ld\n", ds.msg_perm.mode, ds.msg_qbytes);
    
        return 0;
    }
    
    运行结果:
    msg_perm = 432(权限转换成8进制正好是660), msg_qbytes = 16384(队列中允许的最大字节数)
    
  7. system V的消息队列存在几个缺点:

    1. 消息队列通过标识符来使用的,所以没办法使用select、epoll等这个I/O操作!
    2. 通过key值来生成消息队列,增加了程序的复杂性,无论使用ftok还是IPC_PRIVATE方式都不是最完美的。ftok有生成相同key值的风险,尽管概率很小;IPC_PRIVATE方式随可以生成唯一的队列标识符,但是会对非亲缘关系的进程不可见!
    3. 消息队列的总数、消息大小、单个队列容量都是有限制的。可以通过ipcs -lq查看
  8. 总结:实际开发当中最好不要使用System V的消息队列!如果在多进程编程中碰到需要按照类型选择消息的工作时,应该采用其它替代方式,如POSIX的消息队列(本文章点赞超过5,将出POSIX的消息队列教程),或者使用基于文件描述符的通信方式。

能力有限,如有错误望各位大佬不吝指正,原创不易,转载请注明出处

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值