【Ubuntu学习】Linux多进程通信之消息队列

在操作系统课中,需要进行进程间通信的实验,我就写了一个小的程序,通过消息队列进行通信。除了消息队列外,还有共享内存,管道,信号等通信方式。


【Linux学习】多进程通信之消息队列

什么是消息队列

摘自维基百科:
在计算机科学中,消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,它使用队列(queue)来处理一系列的输入(通常是来自用户)。消息队列提供了异步的通信协议,每一个贮列中的纪录包含详细说明的数据,包含发生的时间,输入设备的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列互交。消息会保存在队列中,直到接收者取回它。

简单点,就是可以实现异步通信的进程间或同一线程间的通信方式

消息队列部分函数介绍

英语水平较高者,请看: http://linux.die.net/man/2 下的msgget,msgsnd,msgrcv,msgctl等函数。

msgget函数

  • 函数声明:int msgget(key_t key, int msgflg)
  • 函数功能:打开或创建一个消息队列
  • 函数变量:
    • key_t key是一个整型变量,一般通过ftok函数获得。
    • int msgflg是一个控制变量,可以取IPC_CREAT和IPC_EXCL
      • 仅使用IPC_CREAT,如果用key唯一标识消息队列(以下同)不存在,则创建新队列;如果队列存在,则打开队列,函数执行成功则返回队列的id。
      • 仅使用IPC_EXCL,若队列存在,则打开队列。若队列不存在,则函数执行失败,同时错误为ENOENT
      • 同时使用IPC_CREAT和IPC_EXCL,如果队列不存在,则创建新队列,若队列存在,则函数返回-1,同时错误为EEXIST
      • 标志使用|来相连,同时可以和系统权限相或,如IPC_CREAT|0666,数字表示的权限同linux。
  • 函数返回值:
    • 成功返回消息队列的唯一标识符
    • 失败则返回-1,同时将错误类型保存进errno变量中。(errno数值对应错误

msgsnd函数

  • 函数声明:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
  • 函数功能:用来向消息队列发送消息,调用该函数的进程需要队列的写w权限
  • 函数变量:

    • int msqid是msgget返回的唯一标识符。
    • const void *msgp是一个指向准备发送的消息的结构体的指针。该结构体需要特定的结构,结构如下:

      struct msgbuf {
      long mtype; //message type, must be > 0
      char mtext[256]; // message data
      };

    • size_t msgsz是用来指明结构体中,mtext的大小(注意是mtext不是msgbuf),是一个非负整数。
    • int msgflg是一个控制变量,用来控制当出现异常(队列满、无队列等)时的事件,一般是传0。详情请见之前说的英文页面。
  • 函数返回值:

    • 成功返回0,成功则将结构体中的mtext的拷贝copy加入消息队列。
    • 失败则返回-1,同时将错误类型保存进errno变量中。(errno数值对应错误

msgrcv函数

  • 函数声明:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
    int msgflg)
    
  • 函数功能:用来读取消息队列的消息,调用该函数的进程需要队列的读r权限
  • 函数变量:

    • int msqid、void *msgp、size_t msgsz意义同msgsnd函数。
    • long msgtyp是一个用来表示消息类型的长整型。
      • 若为0,则读取消息队列的第一个消息。
      • 若为大于0的数n,则读取消息队列中类型(即结构体中的mtype变量)为n的第一个消息,不读取其他类型消息
    • int msgflg用处同上,不过可取的值和事件不同,一般取0就可以了。
  • 函数返回值:

    • 成功返回读取的字节数。
    • 失败则返回-1,同时将错误类型保存进errno变量中。(errno数值对应错误

msgctl函数

  • 函数声明:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • 函数功能:用来对消息队列进行一些操作
  • 函数变量:

    • int msqid同上。
    • int cmd用来制定对消息队列执行的操作,可取IPC_STAT、IPC_SET、IPC_RMID、IPC_INFO等。详情请见之前说的英文页面。
    • struct msqid_ds *buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限等的结构,结构如下:

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

  • 函数返回值:

使用消息队列

这里使用消息队列实现了一个猜拳的小游戏。
代码如下:

msgdata.h

#ifndef _MSGDATA_H
#define _MSGDATA_H
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h> 
#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>  

typedef struct  msgbuf  
{  
    long    mtype;  
    char    mdata[256];  
}MSGBUF;  
#endif

referee.c

#include "msgdata.h"

int main(void)  
{
    int     msgid,res;
    key_t   msgkey;  
    char msg[3][20] = {"剪刀","石头","布"};
    int p1,p2;

    MSGBUF msgdata , *p;
    p = &msgdata;

    msgkey = ftok ( "/home/green" , 1);
    if(msgkey == -1)  
    {  
        fprintf(stderr, "ftok failed with error: %d\n", errno);  
        exit(EXIT_FAILURE);  
    }

    msgid = msgget( msgkey , IPC_CREAT | 0666 ) ;              
    if(msgid == -1)  
    {  
        fprintf(stderr, "msgget failed with error: %d\n", errno);  
        exit(EXIT_FAILURE);  
    }

    while(1)  
    {    
        p1 = -1;
        p2 = -1;
        res = -1; 
        while(1)
        {
            res = msgrcv( msgid , p , sizeof(p->mdata) , 1 , 0); 
            if(res == -1)
            {
                fprintf(stderr, "msgrcv failed with error: %d\n", errno);
            }else
            {
                p1 = p->mdata[0] - '0';
                printf("Player one gives %s \n", msg[p1-1]);
            }

            res = msgrcv( msgid , p , sizeof(p->mdata) , 2, 0 );
            if(res == -1)    /*读奇数消息*/  
            {
                fprintf(stderr, "msgrcv failed with error: %d\n", errno);
            }else
            {
                p2 = p->mdata[0] - '0';
                printf("Player two gives %s \n",msg[p2-1]);
            }

            if(p1 > 0 && p2 > 0)
            {
                break;
            }
        } 

        if(p1 == 1 && p2 == 3)
        {
            printf("Player one wins!\n");
        }else if(p1 == 3 && p2 == 1)
        {
            printf("Player two wins!\n");
        }else if(p1 == p2)
        {
            printf("This is tie game!\n");
        }else if(p1 > p2)
        {
            printf("Player one wins!\n");
        }else{
            printf("Player two wins!\n");
        }

        p1 = -1;
        p2 = -1;
    }  
    return 0;  
}  

player1.c

#include "msgdata.h"
int main(void)
{
    int     msgid,res;  
    key_t   msgkey;  
    MSGBUF msgdata , *p ;                                            
    p = &msgdata;

    msgkey = ftok ( "/home/green" , 1); 
    if(msgkey == -1)  
    {  
        fprintf(stderr, "ftok failed with error: %d\n", errno);  
        exit(EXIT_FAILURE);  
    }

    msgid = msgget( msgkey , IPC_CREAT | 0666 ) ;              
    if(msgid == -1)  
    {  
        fprintf(stderr, "msgget failed with error: %d\n", errno);  
        exit(EXIT_FAILURE);  
    }

    while(1)  
    {  
        while(1)
        {
            printf("请输入数字(1.剪刀2.石头3.布):\n  ");  
            fflush( stdin );                              
            gets( p->mdata );                             
            if(p->mdata[0] >'0' && p->mdata[0] < '4')
            {
                break;
            }else{
                printf("输入非法,请重新输入!\n");
            }
        }

        p->mtype = 1;
        res = -1;
        while(res == -1)
        {
            res = msgsnd( msgid , p , sizeof(p->mdata) , 0 );
        }
    }  

    return 0;   
}

player2.c

代码跟player1.c,就p->mtype = 1;这行1的值,改为2.
可以只写一个文件,然后运行的时候,通过stdin传值进去。