Linux进程间通讯三--消息队列

一、消息队列IPC原理

1.消息队列基本属性
  消息队列基本属性数据结构为struct msqid_ds定义在文件/usr/include/linux/msg.h中:

/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct msqid_ds {
    struct ipc_perm msg_perm;                                               // 权限
    struct msg *msg_first;          /* first message on queue,unused  */    // 指向消息头
    struct msg *msg_last;           /* last message in queue,unused */      // 指向消息尾
    __kernel_time_t msg_stime;      /* last msgsnd time */                  // 最近发生消息时间
    __kernel_time_t msg_rtime;      /* last msgrcv time */                  // 最近接收消息时间
    __kernel_time_t msg_ctime;      /* last change time */                  // 最近修改时间
    unsigned long  msg_lcbytes;     /* Reuse junk fields for 32 bit */      // 
    unsigned long  msg_lqbytes;     /* ditto */                             // 
    unsigned short msg_cbytes;      /* current number of bytes on queue */  // 当前队列大小
    unsigned short msg_qnum;        /* number of messages in queue */       // 当前队列消息个数
    unsigned short msg_qbytes;      /* max number of bytes on queue */      // 队列最大值
    __kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */                // 最近发送消息进程pid
    __kernel_ipc_pid_t msg_lrpid;   /* last receive pid */                  // 最近接收消息进程pid
};

  第1个成员struct ipc_perm msg_perm为当前消息队列的权限。此结构体在我的博客《System V IPC基础》一节有说明。
  第2、3个成员分别指向消息队列的首位。struct msg_msg结构体在/usr/src/uname -r/include/linus/msg.h中定义:

/* one msg_msg structure for each message */
struct msg_msg {
    struct list_head m_list;
    long m_type;                                // 消息类型
    size_t m_ts;    /* message text size */     // 消息大小
    struct msg_msgseg *next;                    // 下一个消息位置
    void *security;                             // 真正消息位置
};

2、消息队列模型
  消息队列的本质为一个链式结构,但是,消息队列还可以基于类型处理,因此,消息队列的FIFO原则仅仅使用于同类型的消息。
  Linux系统对消息队列有一些限制值,不同Linux版本可能不同,下面列出我的系统的限制:

// come from /usr/include/linux/msg.h
#define MSGMNI    16   /* <= IPCMNI */     /* max # of msg queue identifiers */     // 最大消息队列个数 
#define MSGMAX  8192   /* <= INT_MAX */   /* max size of message (bytes) */         // 消息最大值
#define MSGMNB 16384   /* <= INT_MAX */   /* default max size of a message queue */ // 默认消息队列大小

  默认情况下,整个系统中最多允许有16个消息队列;
  每个消息队列最大为16384字节,即16K;
  消息队列中的每个消息最大为8192字节,即8K。

二、消息队列管理

1.创建消息队列
  在使用一个消息队列前,需要使用msgget函数创建该消息队列,其函数声明如下:

int msgget(key_t key, int msgflg);

  第1个参数key为有ftok()函数创建的key值,关于ftok函数在我的博客《System V IPC基础》一节有说明。
  第2个参数msgflg的低位用来确定消息队列的访问权限,其最终权限为当前进程的umask值与设置值perm相与所得,即最终值为perm&~umask。其高位包含以下项:

#define IPC_CREAT       01000   /* Create key if key does not exist. */ // 如果key不存在,则创建,存在,返回ID
#define IPC_EXCL        02000   /* Fail if key exists.  */              // 如果key存在返回失败
#define IPC_NOWAIT      04000   /* Return error on wait.  */            // 如果需要等待,直接返回错误

下面给出创建消息队列的代码:

key_t key = ftok("/", 3);
int msq_id = msgget(key, IPC_CREAT | 0666);

2.消息队列属性控制
  msgctl()函数可以对创建好的消息队列的基本属性进行控制(修改),其函数声明如下:

int msgctl(int msqid, int cmd, struct msqid_ds* buf);

  第1个参数msqid为消息队列标识符,该值为msgget()函数创建消息队列的返回值;
  第2个参数cmd为执行的控制命令,即要执行的操作,包括以下选项:

// come from /usr/include/linux/ipc.h
#define IPC_RMID 0     /* remove resource */        // 删除消息队列
#define IPC_SET  1     /* set ipc_perm options */   // 设置ipc_perm参数
#define IPC_STAT 2     /* get ipc_perm options */   // 获取ipc_perm参数
#define IPC_INFO 3     /* see ipcs */               // 获取限制信息

  IPC_RMID :删除消息队列。从系统中删除该消息队列以及仍在该队列上的所有数据,这种删除立即生效。仍在使用该消息队列的进程对消息队列的操作时,将出错返回EIDRM。
  IPC_STAT:读取消息队列属性。取得此队列的msqid_ds结构,并将其存放在第3个参数buf中;
  IPC_SET:设置消息队列属性。按由buf指向的结构中的值,设置此队列的属性;
  IPC_INFO:读取消息队列基本情况。

  第3个参数buf是一个临时的msqid_ds结构体类型的变量。用于存储读取的消息队列属性或者需要修改的消息队列属性。

下面给出删除消息队列代码:

msgctl(msq_id, IPC_RMID, NULL);

3.发送信息到消息队列
  msgsnd()函数将新的消息添加到消息队列尾端,此函数声明如下:

// come from /usr/include/sys/msg.h
int msgsnd(int msqid, const void* msqp, size_t msgsz, int msgflg);

  第1个参数msqid为消息队列标识符,由msgget创建消息队列是返回;
  第2个参数msgp指向用户定义缓冲区,下面是用户定义缓冲区结构:

// come from /usr/include/linux/msg.h
/* message buffer for msgsnd and msgrcv calls */
struct msgbuf {
    long mtype;         /* type of message */   // 消息类型
    char mtext[1];      /* message text */      // 消息内容,在使用时自己重新定义此结构
};

  第3个参数为实际信息的大小。其大小为0到系统对消息队列的限制值。可以表示为sizeof(msgbuf) - sizeof(long)
  第4个参数msgflg用来指定在达到系统为消息队列所定的界限(如消息队列所占空间满时)时采取的操作。

如果设置为IPC_NOWAIT,如果需要等待,则不发送消息且立即返回错误信息EAGIN;
如果设置为0,则阻塞调用进程。

  该函数成功调用后返回0,否则返回-1,同时将对消息队列msqid数据结构的成员执行下列操作:

msg_qnum自增1;
msg_lspid设置为调用进程PID;
msg_stime设置为当前时间。

下面给出msgsnd发送消息的代码:

struct msgbuf {
    long mtype;
    char mtext[100];
};

struct msgbuf buf;
int ret = msgsnd(msq_id, &buf, sizeof(buf.mtext), 0);

4.从消息队列接收信息
  msgrcv()函数用于从消息队列中读取消息,其函数声明如下:

int msgrcv(int msqid, void* msgp, size_t msgsz, long int msgtype, int msgflg);

  第1个参数msqid为消息队列标识符,即从哪个消息队列获得消息;
  第2个参数为一个临时消息数据结构,用来保存读取的信息,其定义如下:

// come from /usr/include/linux/msg.h
/* message buffer for msgsnd and msgrcv calls */
struct msgbuf {
    long mtype;         /* type of message */   // 消息类型
    char mtext[1];      /* message text */      // 消息内容,在使用时自己重新定义此结构
};

  第3个参数msgsz用于指定接收缓冲中mtext的大小(以字节为单位)。如果收到的消息大于msgsz,并且msgflg&MSG_NOERROR为真,则将该消息截至msgsz,消息的截断部分将丢失,并且不向调用进程提供截断的提示。
  第4个参数msgtype用于指定请求的消息类型,具体如下:

msgtype=0:接收队列中的第一天消息,任意类型;
msgtype>0:接收第一天msgtype类型的消息;
msgtype<0:接收第一条最低类型(小于或等于msgtype的绝对值)的消息。

  第5个参数msgflg用于指定所需类型消息不在队列上时将要采取的操作。具体如下所示:

  如果设置IPC_NOWAIT,如果没有消息,调用进程立即返回,同时返回-1,并将errno设置为ENOMSG;
  如果未设置IPC_NOWAIT(即设置0),则阻塞调用进程,直到下面任何一种情况发生:

所需信息被放到消息队列中;
msqid从系统中删除;
收到信号。

  接收消息成功时,该消息将自动从消息队列中删除,并返回接收到的消息大小,并将对整个消息队列msqid数据结构的成语执行下列操作:

msg_qnum自减1;
msg_lrpid设置为调用进程PID;
msg_rtime设置为当前时间。

下面给出msgrcv发送消息的代码:

struct msgbuf {
    long mtype;
    char mtext[100];
};

struct msgbuf buf;
int ret = msgrcv(msq_id, &buf, sizeof(buf.mtext), 1, 0);
三、示例

下面给出消息队列应用示例:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUF_SIZE 128
#define PARENT_SND 1
#define CHILD_SND 2

typedef struct msg_buf
{
    long type;
    char buf[BUF_SIZE];
}MSG;

int main(void)
{
    key_t key;
    pid_t pid;

    key = ftok("/", 10);

    pid = fork();
    if (pid < 0) {
        perror("fork failed");
        return -1;
    }

    if (pid > 0) {  // 父进程
        MSG msg;
        msg.type = PARENT_SND;
        memset(msg.buf, '\0', sizeof(msg.buf));
        memcpy(msg.buf, "hello", BUF_SIZE-1);

        int msg_id = msgget(key, IPC_CREAT | 0666);     // 创建消息队列
        msgsnd(msg_id, &msg, BUF_SIZE-1, 0);            // 发送消息到消息队列

        msgrcv(msg_id, &msg, BUF_SIZE-1, CHILD_SND, 0); // 从消息队列接收数据
        printf("parent: %s\n", msg.buf);

        msgctl(msg_id, IPC_RMID, NULL);                 // 销毁消息队列
    } else if(0 == pid) {   // 子进程      
        MSG msg;

        int msg_id = msgget(key, IPC_CREAT | 0666);
        msgrcv(msg_id, &msg, BUF_SIZE-1, PARENT_SND, 0);
        printf("child: %s\n", msg.buf);

        msg.type = CHILD_SND;
        memset(msg.buf, '\0', sizeof(msg.buf));
        memcpy(msg.buf, "thank you", BUF_SIZE-1);
        msgsnd(msg_id, &msg, BUF_SIZE-1, 0);

        msgctl(msg_id, IPC_RMID, NULL);
    }   

    return 0;
}

你可能感兴趣的:(Linux,Linux,消息队列,进程间通讯,msgq)