进程间通信之消息队列篇

System V包含三种IPC机制(消息队列、信号量和共享内存),通常称为System V IPC。后来被收录到Unix的XSI标准之中故又称为XSI IPC。所以当你看到System V IPC 和 XSI IPC的时候实际上指的是同一种东西。
在Linux中,描叙这几种System V IPC对象的数据结构中都包含一个ipc_perm结构,这个结构中包含了对象的所有者,创建者和进程的用户ID,组ID,还包括对象的访问权限和IPC对象关键字(键值).__key是ipc对象(消息队列、信号量和共享内存)对应的句柄,是IPC对象的唯一标识符。
三种IPC机制都有对应的结构体,这些结构体中有一个共同的成员就是这个ipc_perm,用来标识IPC对象的权限。

ipc_perm
        这是一个结构体。他的英文含义是:ipc permission(IPC权限)
struct ipc_perm {
    key_t          __key; /* key */
    uid_t          uid;   /* 所有者的有效用户ID */
    gid_t          gid;   /* 所有者的有效组ID */
    uid_t          cuid;  /* 创造者的有效用户ID */
    gid_t          cgid;  /* 创造者的有效组ID */
    unsigned short mode;  /* 权限 */
    unsigned short __seq; /* 可忽略 */
};

消息队列的结构

struct msqid_ds {
        struct ipc_perm msg_perm;    /* IPC对象结构体 */
        struct msg *msg_first;       /*消息队列头指针*/
        struct msg *msg_last;        /*消息队列尾指针*/
        __kernel_time_t msg_stime;   /*最后一次插入消息队列消息的时间*/
        __kernel_time_t msg_rtime;   /*最后一次接收消息即删除队列中一个消息的时间*/
        __kernel_time_t msg_ctime;   /* 最后修改队列的时间*/
        unsigned long  msg_lcbytes;  /* Reuse junk fields for 32 bit */
        unsigned long  msg_lqbytes;  /* ditto */
        unsigned short msg_cbytes;   /*队列上所有消息总的字节数 */
        unsigned short msg_qnum;     /*在当前队列上消息的个数 */
        unsigned short msg_qbytes;   /* 队列最大的字节数 */
        __kernel_ipc_pid_t msg_lspid;/* 发送最后一条消息的进程的pid */
        __kernel_ipc_pid_t msg_lrpid;/* 接收最后一条消息的进程的pid */
};
消息队列结构体中的第一条内容就是IPC结构体,IPC结构体是system v系列的IPC对象所共有的数据结构。

我们首先应该知道什么是消息队列?
消息队列是消息的链接表,存放在内核中,并有消息队列标识符标识。
消息队列提供了从一个进程向另外一个进程发送一块数据的方法,每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。需要注意的是,消息队列不是严格意义上的先进先出,而是区分了数据类型,在同类型的数据中,是先进先出的
消息队列也有管道一样的不足,就是每条消息的最大长度是有上限的(MSGMAX),每个消息队列的总字节数是有上限的(MSGMNB),系统上消息队列的总数(消息条目数)也有一个上限(MSGMNI)。
消息队列相关函数接口说明
1.msgget()函数
用于创建和访问一个消息队列
int msgget(key_t, key, int msgflg);
key: 类似于端口号,也可以由ftok函数生成。
ftok函数:key_ftok(const char*pathname,int proj_id);
msgflg: 存在两个IPC标志,即IPC_CREAT和IPC_EXCL。
①IPC_CREAT: 如果IPC不存在,则创建一个IPC资源,否则打开操作。
②IPC_EXCL: 只有在共享内存不存在的时候,新的共享内存才建立,否则出错。如果单独使用IPC_EXCL,xxxget()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。
③如果IPC_CREAT和IPC_EXCL同时使用,xxxget()函数将返回一个新建的IPC标识符。如果该IPC资源已经存在,则返回-1出错。这样就保证了只有是二者同时使用,我们就可以保证所得的对象一定是新建的。
2、msgsnd()函数
该函数用来把消息添加到消息队列中。它的原型为:
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
msgid是由msgget函数返回的消息队列标识符。
msg_ptr是一个指向准备发送消息的指针,此位置用来暂时存储发送和接受的消息:是一个用户可以定义的通用结构,形态如下
struct msgstu
{
long type; //大于0
char mtext[用户指定大小];
};
msg_sz 是msg_ptr指向的消息的长度,注意是消息的长度,即char mtext[用户指定大小]的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。
msgflg 用于控制当前消息队列满或到达系统上限时将要发生的事情。
如果调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1.
3、msgrcv()函数
该函数用来从一个消息队列获取消息,它的原型为
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
msgid, msg_ptr, msg_st 的作用与msgsnd()函数同。
msgtype:它可以实现接受优先级的简单形式
msgflg 用于控制当队列中没有相应类型的消息可以接收时将发生的事情。
调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1。
4、msgctl()函数
该函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:
int msgctl(int msgid, int cmd, struct msgid_ds *buf);
msgid:由msgget函数返回的消息队列标识符。
cmd:指将要采取的动作,系统定义了3种cmd操作:
IPC_STAT:该命令用来获取消息队列对应的 msgid_ds数据结构,并将保存到buf指定的地址空间。
IPC_SET:设定消息队列的属性,要设置的属性存储在buf里面。
IPC_RMID:从内核中删除msgid表示的消息队列。
buf:指向msg_ds结构的指针,它指向消息队列模式和访问权限的结构。
它搭配第二个参数使用,此处若第二个参数为删除操作,这个参数为NULL即可。
msg_ds结构体的内容如下:
struct msgid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
成功时返回0,失败时返回-1。
我用消息队列模拟实现了回显服务器**

//common.h
#progma once
include<stdio.h>
include<string.h>
include<unistd.h>
include<sys/msg.h>
include<stdlib.h>
defiCLIENT_TYPE efine SERVER_TYPE 2
define PATHNAME"."
define PROJ_ID 0x1
typedef struct Msgbuf{
    long type;
    char text[1024];
}Msgbuf;
int CreateMQ();
int OpenMQ();
int DestroyMQ(int mqid);
int ReadMQ(int mqid, long type, char*buf, size_t size);
int WriteMQ(int mqid,long type,char*buf, size_t size);
common.c
include"common.h"
int CommonMQ(int flags){
    key_t key = ftok(PATHNAME, PROJ_ID);
    int mqid = msgget(key,flags);
    if (mqid<0){                                      
        perror("msgget");
        return -1;
    }
    return mqid;
}
int CreateMQ(){
    return CommonMQ(IPC_CREAT | IPC_EXCL | 0666);//如果不存在,就创建一个新的消息队列,如果已经存在,就调用失败
    //0666当前进程对消息队列的读写权限
    }
int OpenMQ(){
    return CommonMQ(IPC_CREAT);\\IPC_CREAT如果不存在,就创建一个新的消息队列,如果已经存在,就打开

}
int DestroyMQ(int mqid){
    int ret = msgctl(mqid, IPC_RMID, NULL);
    if (ret < 0){
        perror("msgctl");
        return -1;
    }
    return ret;
}
int ReadMQ(int mqid, long type, char*buf, size_t size)
{
    Msgbuf msgbuf;
    int ret = msgrcv(mqid, &msgbuf, sizeof(msgbuf.text), type, 0);
    if (ret < 0){
        perror("msgrcv");
        return -1;
    }
    //约定让调用者提供的缓冲区大于sizeof(msgbuf.text)
    if (size <= sizeof(msgbuf.text)){
        return -1;
    }
    strcpy(buf, msgbuf.text);
    //成功返回实际放到接收缓存区的字符个数,失败返回-1
    return ret;
}
int WriteMQ(int mqid,long type, char*buf, size_t size){
    Msgbuf msgbuf;
    msgbuf.type = type;
    if (size >= sizeof(msgbuf.text)){
        return -1;
    }
    strcpy(msgbuf.text, buf);
    int ret=msgsnd(mqid, &msgbuf, sizeof(msgbuf.text), 0);
    if (ret < 0)
    {
        perror("msgsnd");
        return -1;
    }
  return ret;
}

//client.c
include"common.h"
int main(){
    int mqid = OpenMQ();
    printf("mqid=%d\n", mqid);
    while (1){
        printf("> ");
        fflush(stdout);
        char buf[2048] = { 0 };
        ssize_t read_size = read(0, buf, sizeof(buf)-1);
        if (read_size < 0){
            perror("read");
            break;
        }
        if (read_size==0){
            printf("read done\n");
            break;
        }
        buf[read_size] = '\0'; 

        int ret = WriteMQ(mqid, CLIENT_TYPE, buf, strlen(buf));
        if (ret < 0)
        {
            printf("WriteMQ error!\n");
            break;
        }

        ret = ReadMQ(mqid, SERVER_TYPE, buf, sizeof(buf)-1);
        if (ret < 0){
            printf("ReadMQ error!\n");
            break;
        }
        printf("[server]%s\n", buf);
    }
    return 0;
}
//server.c
include"common.h"
 int main()
 {
     int mqid = CreateMQ();
     printf("mqid:%d\n", mqid);
     while (1){
         char buf[2048] = { 0 };
         int ret = ReadMQ(mqid, CLIENT_TYPE,buf, sizeof(buf)-1);

         if (ret < 0)
         {
             printf("ReadMQ error!\n");
             continue;
         }
         printf("[client]%s\n", buf);
         ret = WriteMQ(mqid, SERVER_TYPE, buf, strlen(buf));
         if (ret < 0){
             printf("WriteMQ error!\n");
             continue;
         }
     }
     DestroyMQ(mqid);
     return 0;
 }
 //Makefile
.PHONY:all
all:client server
client:client.c common.c
    gcc -o $@ $^
server:server.c common.c
    gcc -o $@ $^
.PHONY:clean
clean:
    rm -f client server

消息队列小结

双向通信
用于随意进程,不需要有亲缘关系
面向数据块 以数据块为单位进行读写,底层是链表
自带同步互斥 消息队列读完了再读,就会阻塞在reserve函数中
生命周期随内核 所有使用消息队列的进程都退出了,消息队列的资源还在,除非显式的删除或操作系统重启

猜你喜欢

转载自blog.csdn.net/jennifer1_/article/details/81350921