• 10.1 进程间通信--消息队列


      进程间通信的分类:

        文件

        文件锁

        管道和匿名管道pipe

        信号

        消息队列

        共享内存

        信号量

        互斥量

        条件变量

        读写锁

        套接字

    两个进程同时向管道中写数据,内核要保证它们是原子的,内核按照页做限制。

    进程间共享信息的三种方式:一个通过文件系统,一个通过内核,一个在应用空间

     

    IPC对象的持续性:

      随进程持续:一直存在,直到打开的最后一个进程结束。(如pipe和FIFO)

      随内核持续:一直存在,直到内核自举或者显式删除。(如System V消息队列、共享内存、信号量)

      随文件系统持续:一直存在,直到显式删除,即使内核自举还存在。(POSIX消息队列、共享内存、信号量(如果是使用映射文件来实现))

     查看系统的IPC对象以及它们的一些属性可以使用ipcs命令,如下:

     

    其中key表示键,semid表示这个对象的id,owner表示拥有者。共享内存还有个属性是nattch,表示有多少个人连接上共享内存了。

    ipcs -l可以查看IPC对象的一些系统限制:

     

     

    消息队列提供了从一个进程向另一个进程发送一块数据的方法

    每个数据块都有一个类型,接收者进程接收的数据块可以有不同的类型值

    消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)。

    管道是基于数据流的,先进先出。

    消息队列是有边界的,可以后进先出。

    消息队列有如下三个限制:

    cat  /proc/sys/kernel/msgmax      最大消息长度限制

    cat  /proc/sys/kernel/msgmnb      消息队列总的字节数

    cat  /proc/sys/kernel/msgmni        消息条目数

     使用man 2 msgctl可以查看消息队列的一些数据结构:

    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) */
    };

    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 */
    };

    消息队列在内核中的表示如下:

    msq_ids存储了消息的一些元信息,具体的消息由内核来管理。

    消息队列相关的一些API函数:

    msgget函数原型如下:

      int msgget(key_t key,  int  msgflg)

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

      参数:

        key表示某个消息队列的名字

        msgflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

      返回值:成功返回一个非负整数,即该消息队列的标识码;失败返回-1

     编写如下的msgget测试函数:

     1 #include <sys/types.h>
     2 #include <unistd.h>
     3 #include <stdio.h>
     4 #include <string.h>
     5 #include <stdlib.h>
     6 #include <errno.h>
     7 #include <sys/msg.h>
     8 
     9 int main()
    10 {
    11     int msgid;
    12     
    13     msgid = msgget(0x1234, 0666);
    14     if(msgid < 0)
    15     {
    16         perror("msgget error");
    17         exit(0);
    18     }
    19     
    20     return 0;
    21 }

    这段程序代表打开一个消息队列,0666代表权限,0代表8进制,执行结果如下:

    系统中没有key值为0x1234的消息队列,所以出错返回。我们可以根据errno错误码进行更精确的控制。

    我们修改msgget函数的第二个参数,程序如下:

    #include <sys/types.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <sys/msg.h>
    
    int main()
    {
        int msgid;
        
        msgid = msgget(0x1234, 0666 | IPC_CREAT);
        if(msgid < 0)
        {
            perror("msgget error");
            exit(0);
        }
        
        printf("create msg queue success
    ");
        
        return 0;
    }

    现在的参数表示,如果这个消息队列存在就打开旧的,如果不存在就创建新的。

    现在我们的系统中是没有消息队列的,执行结果如下:

    使用ipcs查看系统中的消息队列如下:

    可以看到确实出现了一个key值为0x1234的消息队列IPC对象。

    进一步加参数,如下:

     1 #include <sys/types.h>
     2 #include <unistd.h>
     3 #include <stdio.h>
     4 #include <string.h>
     5 #include <stdlib.h>
     6 #include <errno.h>
     7 #include <sys/msg.h>
     8 
     9 int main()
    10 {
    11     int msgid;
    12     
    13     msgid = msgget(0x1234, 0666 | IPC_CREAT | IPC_EXCL);
    14     if(msgid < 0)
    15     {
    16         if(errno == EEXIST)
    17         {
    18             printf("msg is exist
    ");
    19         }
    20         perror("msgget error");
    21         exit(0);
    22     }
    23     
    24     printf("create msg queue success
    ");
    25     
    26     return 0;
    27 }

    加上IPC_EXCL表示,如果文件存在,若还要创建(加了IPC_CREAT)则返回错误。单独用IPC_EXCL没有意义。

    执行结果如下:

    我们验证key取IPC_PRIVATE的情况,如下:

     1 #include <sys/types.h>
     2 #include <unistd.h>
     3 #include <stdio.h>
     4 #include <string.h>
     5 #include <stdlib.h>
     6 #include <errno.h>
     7 #include <sys/msg.h>
     8 
     9 int main()
    10 {
    11     int msgid;
    12     
    13     msgid = msgget(IPC_PRIVATE, 0666 );
    14     if(msgid < 0)
    15     {
    16         if(errno == EEXIST)
    17         {
    18             printf("msg is exist
    ");
    19         }
    20         perror("msgget error");
    21         exit(0);
    22     }
    23     
    24     printf("create msg queue success
    ");
    25     
    26     return 0;
    27 }

    这时候,flag参数的IPC_CREAT和IPC_EXCL都没有作用了,执行结果如下:

    此时创建出来的消息队列的key值为0,多次调用这个函数可以创建出多个key为0的消息队列,如下:

     私有的消息队列只能用在本进程或者和本进程有血缘关系的进程(通过fork创建)。将msgid传给其他没有血缘的进程也不能用。

     创建一个IPC对象的逻辑如下:

    msgctl函数:

      原型:int msgctl(int msqid,  int cmd,  struct  msqid_ds  *buf)

      参数:

        msqid:由msgget函数返回的消息队列标识码

        cmd:将要采取的动作

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

    cmd可取的三个值如下:

    IPC对象,消息队列是linux负责持久化,我们可以从linux内核中获取消息队列的信息,或者修改消息队列。

    msgctl的测试函数如下:

     1 #include <sys/types.h>
     2 #include <unistd.h>
     3 #include <stdio.h>
     4 #include <string.h>
     5 #include <stdlib.h>
     6 #include <errno.h>
     7 #include <sys/msg.h>
     8 
     9 int main()
    10 {
    11     int msgid;
    12     int ret = 0;
    13     
    14     msgid = msgget(0x1234, 0666);
    15     if(msgid < 0)
    16     {
    17         if(errno == EEXIST)
    18         {
    19             printf("msg is exist
    ");
    20         }
    21         perror("msgget error");
    22         exit(0);
    23     }
    24     
    25     printf("msgid = %d
    ", msgid);
    26     
    27     struct msqid_ds buf;
    28     memset(&buf, 0, sizeof(buf));
    29     ret = msgctl(msgid, IPC_STAT, &buf);
    30     
    31     if(ret == -1)
    32     {
    33         perror("msgctl error");
    34         exit(0);
    35     }
    36     
    37     printf("mode = %o
    ", buf.msg_perm.mode);
    38     
    39     return 0;
    40 }

    系统中已经存在一个我们以前创建的消息队列,key为0x1234,msgid为0,执行程序,结果如下:

     msgsnd函数,消息队列的发送函数:

      原型:int msgsnd(int msqid,  const void* msgp, size_t  msgsz, int  msgflg)

      参数:

        msgid:由msgget返回的消息队列标识码

        msgp:是一个指针,指针指向准备发送的消息

        msgsz:是msgp指向的消息的长度,这个长度不包含保存消息类型的那个long int长类型

        msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情

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

      msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误

      消息结构在两方面受到制约,首先,它必须小于系统规定的上限值;其次,它必须以一个long int长整数开始,接收者将利用这个长整数确定消息的类型

      消息结构参考形式如下:

    struct msgbuf

    {

      long  mtype;

      char  mtext[1];

    }

    测试函数如下:

     1 #include <sys/types.h>
     2 #include <unistd.h>
     3 #include <stdio.h>
     4 #include <string.h>
     5 #include <stdlib.h>
     6 #include <errno.h>
     7 #include <sys/msg.h>
     8 
     9 struct msgbuf
    10 {
    11     long mtype;
    12     char mtext[1024];
    13 };
    14 
    15 int main()
    16 {
    17     int msgid;
    18     int ret = 0;
    19     
    20     msgid = msgget(0x1234, 0666);
    21     if(msgid < 0)
    22     {
    23         if(errno == EEXIST)
    24         {
    25             printf("msg is exist
    ");
    26         }
    27         perror("msgget error");
    28         exit(0);
    29     }
    30     
    31     printf("msgid = %d
    ", msgid);
    32     
    33     struct msgbuf buf;
    34     memset(&buf, 0, sizeof(struct msgbuf));
    35     buf.mtype = 1;
    36     strcpy(buf.mtext, "1111111222222333333");    
    37     
    38     ret = msgsnd(msgid, &buf, 10, IPC_NOWAIT);
    39     if(ret < 0)
    40     {
    41         perror("msgsnd error");
    42         exit(0);
    43     }
    44     
    45     return 0;
    46 }

    消息类型我们定义为1类型,这个是自己选的。系统中已经存在key值为0x1234的消息队列,我们执行完这个函数后,消息就已经写入到了这个消息队列中,我们怎么验证呢?msgctl可以读出消息队列的信息,我们写如下的验证程序:

     1 #include <sys/types.h>
     2 #include <unistd.h>
     3 #include <stdio.h>
     4 #include <string.h>
     5 #include <stdlib.h>
     6 #include <errno.h>
     7 #include <sys/msg.h>
     8 
     9 int main()
    10 {
    11     int msgid;
    12     int ret = 0;
    13     
    14     msgid = msgget(0x1234, 0666);
    15     if(msgid < 0)
    16     {
    17         if(errno == EEXIST)
    18         {
    19             printf("msg is exist
    ");
    20         }
    21         perror("msgget error");
    22         exit(0);
    23     }
    24     
    25     printf("msgid = %d
    ", msgid);
    26     
    27     struct msqid_ds buf;
    28     memset(&buf, 0, sizeof(buf));
    29     ret = msgctl(msgid, IPC_STAT, &buf);
    30     
    31     if(ret == -1)
    32     {
    33         perror("msgctl error");
    34         exit(0);
    35     }
    36     
    37     printf("mode = %o
    ", buf.msg_perm.mode);
    38     printf("bytes in msg queue : %d
    ", buf.__msg_cbytes);
    39     printf("msg numbers : %d
    ", buf.msg_qnum);
    40     
    41     return 0;
    42 }

    执行结果如下:

    这和我们写到消息队列中的消息是吻合的。

     msgrcv接收消息函数:

      原型:ssize_t  msgrcv(int  msqid,  void *msgp,  size_t  msgsz,  long  msgtyp,  int  msgflg)

      参数:

        msgid:由msgget函数返回的消息队列标识码

        msgp:是一个指针,指针指向准备接收的消息

        msgsz:是msgp指向的消息的长度,这个长度不含保存消息类型的那个long int长整型

        msgtype:它可以实现接收优先级的简单形式

        msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事

      返回值:成功返回实际放到接收缓冲区中的字符个数,失败返回-1

    msgtype=0  返回队列第一条信息

    msgtype>0 返回队列第一条类型等于msgtype的消息

    msgtype<0 返回队列第一条类型小于等于msgtype绝对值的消息,并且是满足条件的消息类型最小的消息

    msgflg=IPC_NOWAIT,队列没有可读消息时不等待,返回ENOMSG错误。

    msgflg=MSG_NOERROR,消息大小超过msgsz时被截断

    msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息。

     上面我们已经将消息写到了消息队列中,这个消息队列由内核维护,现在我们下一个读消息的程序,如下:

     1 #include <sys/types.h>
     2 #include <unistd.h>
     3 #include <stdio.h>
     4 #include <string.h>
     5 #include <stdlib.h>
     6 #include <errno.h>
     7 #include <sys/msg.h>
     8 
     9 struct msgbuf
    10 {
    11     long mtype;
    12     char mtext[1024];
    13 };
    14 
    15 int main()
    16 {
    17     int msgid;
    18     int ret = 0;
    19     
    20     msgid = msgget(0x1234, 0666);
    21     if(msgid < 0)
    22     {
    23         if(errno == EEXIST)
    24         {
    25             printf("msg is exist
    ");
    26         }
    27         perror("msgget error");
    28         exit(0);
    29     }
    30     
    31     printf("msgid = %d
    ", msgid);
    32     
    33     struct msgbuf buf;
    34     memset(&buf, 0, sizeof(struct msgbuf));
    35 
    36     ret = msgrcv(msgid, &buf, 1024, 0, IPC_NOWAIT);
    37     if(ret < 0)
    38     {
    39         perror("msgrcv error");
    40         exit(0);
    41     }
    42     
    43     buf.mtext[ret] = '';
    44     printf("recv msg : %s
    ", buf.mtext);
    45     
    46     return 0;
    47 }

    执行结果如下:

    可以看到,读出的消息正是我们写入的。再次执行我们的验证程序(读取消息队列的状态),结果如下:

    可以看到,消息已经被我们读走了,现在的队列中已经没有消息了。

    综合案例如下:

     1 #include <sys/types.h>
     2 #include <unistd.h>
     3 
     4 #include <stdio.h>
     5 #include <string.h>
     6 #include <stdlib.h>
     7 #include <errno.h>
     8 
     9 #include <sys/msg.h>
    10 
    11 struct msg_buf
    12 {
    13     long mtype;
    14     char data[255];
    15 };
    16  
    17 /* 注意long 和 int 在32bit 和 64bit系统之下是不一样的
    18 struct msg_buf
    19 {
    20     long mtype;
    21     char data[255];
    22 };
    23 */
    24  
    25 int main()
    26 {
    27     key_t key;
    28     int msgid;
    29     int ret;
    30     struct msg_buf msgbuf;
    31     int msgtype =  getpid();
    32 
    33     key=ftok("./msgfile",'a');
    34     printf("key =[%x]
    ",key);
    35     
    36     printf("sizeof(long):%ld, sizeof(int):%d 
    ", sizeof(long), sizeof(int));
    37     
    38     msgid=msgget(key, IPC_CREAT |IPC_EXCL|0666); //通过文件对应
    39 
    40     if(msgid==-1)
    41     {
    42         if (errno == EEXIST)
    43         {
    44             printf("EEXIST:.....
    ");
    45             key=ftok("./msgfile",'a');
    46             msgid=msgget(key, IPC_CREAT|0666); //通过文件对应
    47         }
    48         else
    49         {
    50              printf("create error
    ");
    51             perror("msget: 
    ");
    52             return -1;
    53         }
    54         
    55     }
    56     printf("msgid:%d 
    ", msgid);
    57 
    58        msgbuf.mtype = msgtype; //        getpid();
    59 
    60     printf("getpid(): %d 
    ", getpid());
    61     strcpy(msgbuf.data,"test haha");
    62     ret = msgsnd(msgid,&msgbuf, sizeof(msgbuf.data), IPC_NOWAIT);
    63     if(ret==-1)
    64     {
    65             printf("send message err
    ");
    66             perror("senderr");
    67             return -1;
    68     }
    69     sleep(1);
    70 
    71     memset(&msgbuf,0,sizeof(msgbuf));
    72     
    73     ret=msgrcv(msgid, &msgbuf, sizeof(msgbuf.data), msgtype, IPC_NOWAIT);
    74     if(ret==-1)
    75     {
    76             printf("recv message err
    ");
    77             perror("dd");
    78             return -1;
    79     }
    80     printf("recv msg =[%s]
    ",msgbuf.data);
    81  
    82 }

    ftok是一个产生key值的函数,消息队列、共享内存、信号量都可以使用这个key。ftok第一个参数是路径,应用中可以将某个路径设置成一个环境变量。应用程序中就可以统一用getenv获取这个环境变量的值,得到路径名,然后再将路径名传给ftok函数。

  • 相关阅读:
    【转】【MFC】 StretchBlt绘图图像失真
    【转】MFC 各类型相互转换
    【转】MFC CListCtrl 使用技巧
    【数学】关于已知线段长度获取某一点对应线段的百分比
    【MySQL】MySQL 常用语法之锁表与解锁表
    C#通用类库
    WPF Knowledge Points
    WPF中的WndProc
    C# 重写WndProc 拦截 发送 系统消息 + windows消息常量值
    c#正则获取html里面a标签href的值
  • 原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9425001.html
Copyright © 2020-2023  润新知