1、消息队列

(1)消息队列产生原因
Unix早期通信机制之一的信号能够传送的信息量有限,管道则只能传送无格式的字节流,这无疑会给应用程序开发带来不便。而消息队列(也叫报文队列)则克服了这些缺点。

(2)什么是消息队列
消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的。也就是说进程的退出,如果不自主去释放资源,消息队列是会悄无声息的存在的。所以较管道来说,消息队列的生命周期更加持久。消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。我们可以通过发送消息 来避免命名管道的同步和阻塞问题。消息队列与管道不同的是,消息队列是基于消息的, 而管道是基于字节流的,且消息队列的读取不一定是先入先出。消息队列与命名管道有一 样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI).

系统V消息队列是随内核持续的,只有在内核重起或者显示删除一个消息队列时,该消息队列才会真正被删除(对比管道,管道的生命周期是随进程的)。


2、键值获取

键值:消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获取一个消息队列的描述字,必须提供该消息队列的键值。

#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathneme, int id);

功能:返回文件名对应的键值。
pathname:文件名。
id:项目名(不为0即可)。


3、创建/打开消息的队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);

返回值:与键值相对应的消息队列描述字。
key:键值,由ftok获得。
msgflg:标志位,有以下几个标志位。
(1)IPC_CREAT:创建新的消息队列。
(2)IPC_ECCL:一般与IPC_CREAT一起使用,表示如果要创建的消息队列已经存在,则返回错误。
(3)IPC_NOWAIT:读写消息队列要求无法得到满足时,不阻塞。


4、消息发送

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgsnd(int msgid, struct msg_buf* msgp, int msgsz, int msgflg);

功能:向消息队列中发送一条消息。
msgid:一打开的消息队列id。
msgp:存放消息的结构。
msgsz:消息数据长度。
msgflg:发送标志,有意义的msgflg表示为IPC_NOWAIT,指明在消息队列中没有足够空间容纳要发送的消息时,msgsnd是否等待。

消息格式

struct msg_buf{
    long mtype;    /* 消息类型,需要根据此类型来读取此类型的数据 */
    char mtext[];  /* 要发送的消息 */
};

5、消息接收

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgrcv(int msgid, struct msg_buf* msgq, int msgsz, long msgtyp, int msgflg);

功能:从msgid代表的消息队列中读取一个 msgtyp 类型的消息,并把消息存储在 msgp 指向的 msg_buf 结构中。在成功读取了这条消息后,在队列中的这条消息将被删除。


6、消息队列属性设置

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msgid, int cmd, struct magid_gs *buf);

功能:该函数设置消息队列的属性。
struct magid_gs *buf:一般为NULL即可。
cmd:msgctl系统调用对msgid标识的消息队列执行cmd操作,系统定义了3种cmd操作:
(1)IPC_STAT : 该命令⽤用来获取消息队列对应的 msqid_ds 数据结构,并将其保存到 buf 指 定的地址空间。

(2)IPC_SET : 该命令⽤用来设置消息队列的属性,要设置的属性存储在buf中。
(3)IPC_RMID : 从内核中删除 msqid 标识的消息队列。


7、消息队列简单通信

demo:

#include <apue.h>
#include <sys/msg.h>

struct msg_buf{
    int mtype;
    char data[256];
};

int main(int argc, const char *argv[])
{
    key_t key;
    int msgid;
    int ret;
    char dt[256];
    struct msg_buf my_mag_buf;

    /* 获取文件键值 */
    key = ftok("/tmp/test", 0x6666);
    printf("key = [%x]\n", key);

    /* 创建消息队列 */
    msgid = msgget(msgid, IPC_CREAT | 0666);
    if (msgid == -1){
        printf("Create messge error\n");
        exit(1);
    }

    /* 发送消息队列 */
    printf("Please input messge what you want to send : ");
    fgets(dt, 256, stdin);
    printf("%s\n", dt);
    my_mag_buf.mtype = getpid();                                                    //用进程号作为消息类型
    strncpy(my_mag_buf.data, dt, sizeof(my_mag_buf.data));
    printf("Send messge ...    %s\n", my_mag_buf.data);
    ret = msgsnd(msgid, &my_mag_buf, sizeof(my_mag_buf.data), IPC_NOWAIT);
    if (ret == -1){
        printf("Send messge error\n");
        exit(1);
    }

    /* 接收消息队列 */
    memset(&my_mag_buf, '\0', sizeof(my_mag_buf));
    ret = msgrcv(msgid, &my_mag_buf, sizeof(my_mag_buf.data), getpid(), IPC_NOWAIT);//获取消息类型为进程号的消息
    if (ret == -1){
        printf("Receive messge errot\n");
        exit(1);
    }

    printf("Receive messge ... %s\n", my_mag_buf.data);

    if(msgctl(msgid, IPC_RMID, NULL) != 0){
        printf("Destroy messge error\n");
        exit(1);
    }

    return 0;
}