消息队列

消息队列是消息的链接表,存储在内核中,,由消息队列标识符标识。

在linux下查看当前所有的消息队列

ipcs -q

删除一个消息队列

ipcrm -q 消息队列的id

消息队列的特点

  1. 面向数据报
  2. 全双工
  3. 支持一个进程向另一个进程发送一块数据的方法
  4. 每个数据块都可以是不同的类型,接收者进程接收的数据块可以有不同的类型值。
  5. 消息队列中的每个消息的大小是有上限的,每个消息队列的总的字节数是有上限的,系统上消息队列可以创建的总数也是有上限的。
  6. 创建的消息队列的生命周期是随内核的。不会随着进程的结束而销毁,需要人为显式的终止。

内核为每个IPC对象分配的一个结构体

struct ipc_perm                     
{                             
    __kernel_key_t  key;        
    __kernel_uid_t  uid;         
    __kernel_gid_t  gid;        
    __kernel_uid_t  cuid;       
    __kernel_gid_t  cgid;       
    __kernel_mode_t mode;       
    unsigned short  seq;                        
};

消息队列的数据结构,每个消息队列有一个结构体用来描述该消息队列。

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 */
    __kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
};

消息队列的几个常用接口函数

  1. msgget函数—用于创建和访问一个消息队列
    原型: int msgget(key_t key, int msgflg);
    参数:
    key :消息队列对应的唯一id,一般用ftok函数获得
    msgflag : 常用的就是IPC_CREAT(不存在就创建,存在就打开) 、IPC_EXCL(若不存在就创建,否则就创建失败)
  2. msgctl函数—消息队列的控制函数
    原型:int msgctl(int msgid, int cmd, struct msqid_ds *buf);
    参数:
    msgid : 要控制的消息队列的id,一般是msgget的返回值
    cmd : 对消息队列要采取的操作(IPC_SET、IPC_STAT、IPC_RMID)
    buf :是一个输出型参数,返回的是消息队列的一些性质
    cmd 操作:
  1. IPC_STAT:把msqid_ds结构中的数据设置为消息队列的当前关联值
  1. IPC_SET :在进程有足够权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值
  1. IPC_RMID:删除消息队列
  1. msgsnd函数—把一条消息添加到消息队列中
    原型:int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);
    参数:
    msgid:要添加到的消息队列的id
    msgp:是一个指向准备发送的的消息
    msgsz:准备发送数据的长度,即是msgp所指向的消息的长度
    msgflg:标志位
    返回值:
    成功返回 0 ,失败返回 -1 。
  2. msgrcv函数—从一个消息队列接收消息
    原型: ssize_t msgrcv(int msgid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
    参数:
    msgid:需要接收的消息的消息队列的id
    msgp:指向准备接收到的消息
    msgsz:要接受的消息的长度,不包括用于指定消息类型的long的长度
    msgtyp:接收消息的优先级
    msgflg:标志位
    返回值:
    成功返回接收到的消息队列的长度,失败返回 -1。
    优先级说明:
  • msgtyp == 0返回队列的第一条消息
  • > 0 返回队列的第一条类型等于msgtype的消息
  • < 0返回队列第一条类型小于等于msgtype绝对值的消息,并且是满足条件的消息类型最小的消息
  • msgflg = IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误
  • msgflg = MSG_NOERROR,消息大小超过msgsz时被截断
  • msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息

用消息队列实现一个简单的服务器/客户端

客户端是主动发送请求。服务器被动接收请求。

服务器先启动,7 x 24小时工作。

客户端发送的请求不同,服务器会根据不同的请求计算不同的结果,并将结果给客户端。


我们首先需要将系统提供的函数进行封装。得到更完整的接口函数,让服务器端和客户端的代码写起来比较简便。

common.h

#pragma once
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    #include <string.h>

    #define PATHNAME "."
    #define PROJ_ID 0x6666

    #define SERVER_TYPE 1
    #define CLIENT_TYPE 2

    typedef struct Msgbuf{
        long mtype;        // 数据的类型 
        char mtext[1024];  // 存储数据的字符串数组
    }Msgbuf;

    //创建一个消息队列,如果已经存在,就调用失败
    int CreateMsg();

    //打开一个已有的消息队列,如果不存在,也返回失败
    int GetMsg();

    //销毁一个消息队列
    int DestroyMsg(int msgid);

    int sendMsg(int msgid, long type, char *buf, size_t size);

    int recvMsg(int msgid, long type, char *buf, size_t max_size);

common.c

#include "common.h"
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    #include <string.h>

    int CommonMsg(int flags)
    {
        key_t key = ftok(PATHNAME, PROJ_ID);
        if(key == -1)
        {
            perror("ftok");
            return -1;
        }
        int msgid = msgget(key, flags); 
        if(msgid < 0)
        {
            perror("msgget");
            return -1;
        }
        return msgid;
    }

    int CreateMsg()
    {
        // 0666表示权限,所有用户可读可写 
        //IPC_EXCL要搭配IPC_CREAT使用,意思是要是消息队列不存在就创建, 存在就创建失败。
        return CommonMsg(IPC_CREAT | IPC_EXCL | 0666);
    }

    int GetMsg()
    {
        return CommonMsg(IPC_CREAT);
    }

    int DestroyMsg(int msgid)
    {
        int ret = msgctl(msgid, IPC_RMID, NULL);
        if(ret < 0)
        {
            perror("msgctl");
            return -1;
        }
        return 0;
    }

    int sendMsg(int msgid, long type, char* buf, size_t size)
    {
       Msgbuf msgbuf;
       if(size >= sizeof(msgbuf.mtext))
       {
           printf("size is to large!\n");
           return -1;
       }
       msgbuf.mtype = type;
       strcpy(msgbuf.mtext, buf);
       int ret = msgsnd(msgid, &msgbuf, sizeof(msgbuf.mtext), 0);
       if(ret < 0)
       {
           perror("msgsnd");
           return -1;
       }
       return 0;
    }

    int recvMsg(int msgid, long type, char *buf, size_t max_size)
    {
        Msgbuf msgbuf;
        ssize_t ret = msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), type, 0);
        if(ret < 0)
        {
            perror("msgrcv");
            return -1;
        }
        if(max_size < sizeof(msgbuf.mtext))
        {
            printf("buf is too small");
            return -1;
        }
        strcpy(buf, msgbuf.mtext);
        return 0;
    }

服务器端代码

#include <stdio.h>
    #include <unistd.h>
    #include "common.h"

    int main()
    {
        int msgid = CreateMsg();
        printf("msgid = %d\n", msgid);
        while(1)
        {
            char buf[2048] = {0};
            int ret = recvMsg(msgid, CLIENT_TYPE, buf, sizeof(buf) - 1);
            if(ret < 0)
            {
                perror("recvMsg");
                return 1;
            }
            printf("client say: %s",buf);
            sendMsg(msgid, CLIENT_TYPE, buf, strlen(buf));
        }
        //DestroyMsg(msgid);
        return 0;
    }

客户端

#include <stdio.h>
    #include <unistd.h>
    #include "common.h"
    int main()
    {
        int msgid = CreateMsg();
        printf("msgid = %d\n", msgid);
        while(1)
        {
            char buf[2048] = {0};
            int ret = recvMsg(msgid, CLIENT_TYPE, buf, sizeof(buf) - 1);
            if(ret < 0)
            {
                perror("recvMsg");
                return 1;
            }
            printf("client say: %s",buf);
            sendMsg(msgid, CLIENT_TYPE, buf, strlen(buf));
        }
        //DestroyMsg(msgid);
        return 0;
    }