进程间通信之消息队列

来源:互联网 发布:淘宝网店赚钱吗 编辑:程序博客网 时间:2024/06/07 04:02

概述

进程间的通信,除过管道和命名管道之外,还有一种我认为是比上述两种方式更优秀的通信方式,那就是消息队列。


消息队列的基本概念

消息队列是一个存放在内核中的消息链表,每个消息队列是由消息读列标识符标识。它与之前两种进程间通信方式不同的就是,它是存在于内核之中的,只有在内核重启的时候或是显式的删除一个消息队列的时候,该消息队列才会真正的删除。
要操作消息队列,需要用到一些数据结构,熟悉并掌握这些数据结构对消息队列的理解是很重要的。


消息缓冲结构

向消息队列发送消息时,必须组成合理的数据结构。Linux定义了一个模板数据结构msgbuf。

#include<linux/msg.h>struct msgbuf{    long mtype;    char mtext[1];};//mtype表示的是消息类型,给消息指定类型,可以使消息在一个队列之中重复的使用。mtext指消息的内容。

注意: mtext虽然定义为char类型,但并不代表消息内容只能是一个字符,它是任意类型,它可以由用户定义。如:

struct mymsgbuf{    long mtype;    struct student stu;};

消息队列中的消息大小是受限制的,由linux/msg.h中的宏MSGMAX给出消息的最大长度。


msqid_ds内核数据结构

Linux之中,每个消息队列都维护一个结构体msqid_ds,此结构体保存着消息队列当前的状态信息,它被定义在linux/msg.h之中。

struct msqid_ds{      struct_ipc_perm      msg_perm;      struct_msg              *msg_first;      struct_msg              *msg_last;      __kernel_t time_t      msg_stime;      __kernel_t time_t      msg_rtime;      __kernel_t time_t      msg_ctime;      unsigned long          msg_lcbytes;      unsigned long          msg_lqbytes;      unsigned short        msg_cbytes;      unsigned short        msg_qnum;      unsigned short        msg_qbytes;      __kernel_ipc_pid_t    msg_lspid;      __kernel_ipc_pid_t    msg_lrpid;  };  

各字段的含义如下:

msg_perm:是一个ipc_perm(定义在头文件linux/ipc.h)的结构,保存了消息队列的存取权限,以及队列的用户ID、组ID等信息
msg_first:指向队列中的第一条消息
msg_last:指向队列中的最后一条消息
msg_stime:向消息队列发送最后一条信息的时间
msg_rtime:从消息队列取最后一条信息的时间
msg_ctime:最后一次变更消息队列的时间
msg_cbytes:消息队列中所有消息占的字节数
msg_qnum:消息队列中消息的数目
msg_qbytes:消息队列的最大字节数
msg_lspid:向消息队列发送最后一条消息的进程ID
msg_lrpid:从消息队列读取最后一条信息的进程ID


ipc_perm内核数据结构

ipc_perm保存着消息队列一些重要的信息 ,包括消息队列所关联的键值,消息队列的用户ID,组ID等等,它定义在linux_ipc.h中。

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     seg;  };  

几个主要字段的含义如下:

key:创建消息队列用到的键值key
uid:消息队列的用户ID
gid:消息队列的组ID
cuid:创建消息队列的进程用户ID
cgid:创建消息队列的进程组ID


消息队列的创建和读写

创建消息队列

获取键值:

#include<sys/types.h>#include<sys/ipc.h>key_t ftok(const char *pathname, int proj_id);//ftok函数主要根据后面两个参数生成键值,执行成功返回键值,失败返回-1。

注意: 参数pathname必须是系统中一定存在并且进程有权访问的。参数proj_id的范围是1~255。

创建队列:
返回的键值提供给函数msgget函数,可以创建一个新的消息队列或访问一个已经存在的消息队列。它被定义在头文件sys/msg.h之中。

int msgget(key_t key, int msgflg);//key是返回的键值,msgflg是一个标志参数。调用成功返回标识符,失败返回-1。

msgflg的取值:

IPC_CREATE:如果内核之中不存在键值与key相等的消息队列,则创建一个,如果存在就返回该消息队列的描述符。
IPC_EXCL:和上述取值一起使用,对应键值的key值已经存在,出错,返回-1。单独使用没有意义。


消息队列的读写

写消息队列

#include<sys/msg.h>int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int magflg)//函数调用成功返回0,失败返回-1。

其中各个参数的含义如下:

  • msqid:函数向msqid标志的消息队列发送一个消息
  • msgp:msgp指向发送的消息
  • msgsz:要发送消息的大小,不包括消息类型所占的4个字节
  • magflg:操作标志位。可以设置为0或IPC_NOWAIT。如果设置为0,则当消息队列满的时候,msgsnd会进行阻塞,知道消息可以写进消息队列。如果操作符设置为IPC_NOWAIT则当消息队列已经满的时》候,msgsnd不会进行阻塞而直接返回

常见的错误码:

  • EAGAIN:消息队列已满
  • EIDRM:消息队列已经被删除
  • EACCESS:无权访问消息队列

读消息队列

#include<sys/msg.h>int msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long int msgtyp, int msgflg)

各参数含义如下:

  • msqid:消息队列描述符
  • msgp:读取消息队列的消息存入msgp中
  • msgsz:消息缓冲区的大小
  • msgtyp:请求读取的消息类型
  • msgflg:操作标志位

msgflg的取值:

  • IPC_NOWAIT:没有满足条件的消息则立即返回
  • IPC_EXCEPT:与msgtyp配合使用,返回队列中第一个类型不为msgtyp的消息
  • IPC_NOERROR:如果满足条件的消息内容大于所请求的msgsz字节,则把该消息截断

常见的错误码:

  • E2BIG:消息长度大于msgsz
  • EIDRM:消息队列已经被删除
  • EINVAL:说明msqid无效或msgsz小于0

获取和设置消息队列的属性

msgctl函数可以获取或设置消息队列的属性。

#include<sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf)

该函数对msqid标识的消息队列执行了cmd操作,系统定义了3中cmd操作。
cmd的三种取值:

  • IPC_STAT:该命令用来获取消息队列对应的msqid_ds数据结构,并将其保存在buf所指向的地址空间中
  • IPC_SET:该命令用来设置消息队列的属性,要设置的属性包括在buf之中,可设置的属性包括msg_perm.uid,msg_perm.gid,msg_perm.mode,msg_qbytes
  • IPC_RMID:从内核之中删除msqid标识的消息队列

消息队列的应用实例

下面我将给出基于消息队列的进程间通信的程序,对上面的几个知识点进行总结。
Server:

#include<stdio.h>#include<fcntl.h>#include<stdlib.h>#include<string.h>#include<sys/types.h>#include<sys/ipc.h>#include<sys/msg.h>#include<sys/stat.h>#define BUF_SIZE 256#define PROJ_ID 32#define PATH_NAME "/tmp"#define SERVER_MSG 1#define CLIENT_MSG 2struct mymsgbuf{    long msgtype;    char ctrlstring[BUF_SIZE];}msgbuffer;int main(){    int qid;    int msglen;    key_t msgkey;    if((msgkey = ftok(PATH_NAME, PROJ_ID)) == -1)                        //先获取键值    {        perror("ftok error!\n");        exit(1);    }    if((qid = msgget(msgkey, IPC_CREAT | 0660)) == -1)                   //获取消息队列标识符    {        perror("msgget error!\n");        exit(1);    }    while(1)    {        printf("server: ");        fgets(msgbuffer.ctrlstring, BUF_SIZE, stdin);                           //从键盘获取服务端的输入        if(strncmp("exit", msgbuffer.ctrlstring, 4) == 0)        {            msgctl(qid, IPC_RMID, NULL);            break;        }        msgbuffer.ctrlstring[strlen(msgbuffer.ctrlstring) -1] = '\0';        msgbuffer.msgtype = SERVER_MSG;        if(msgsnd(qid, &msgbuffer, strlen(msgbuffer.ctrlstring) +1, 0) == -1)   //发送消息        {            perror("Server msgsnd error!\n");            exit(1);        }        if(msgrcv(qid, &msgbuffer, BUF_SIZE, CLIENT_MSG, 0) == -1)   //接受客户端的消息        {            perror("Client msgsnd error!\n");            exit(1);        }        printf("Client: %s\n", msgbuffer.ctrlstring);    }    return 0;}

Client:

#include<stdio.h>#include<fcntl.h>#include<stdlib.h>#include<string.h>#include<sys/types.h>#include<sys/ipc.h>#include<sys/msg.h>#include<sys/stat.h>#define BUF_SIZE 256#define PROJ_ID 32#define PATH_NAME "/tmp"#define SERVER_MSG 1#define CLIENT_MSG 2struct mymsgbuf{    long msgtype;    char ctrlstring[BUF_SIZE];}msgbuffer;int main(){    int qid;    int msglen;    key_t msgkey;    if(msgkey = ftok(PATH_NAME, PROJ_ID) == -1)                        //获取键值    {        perror("msgget error!\n");        exit(1);    }    if(qid = msgget(msgkey, IPC_CREAT | 0660) == -1)                   //获取消息队列标识符    {        perror("msgget error!\n");        exit(1);    }    while(1)    {        if(msgrcv(qid, &msgbuffer, BUF_SIZE, SERVER_MSG, 0) == -1)        {            perror("Server msgrcv error!\n");            exit(1);        }        printf("server: %s\n", msgbuffer.ctrlstring);        printf("Client: ");        fgets(msgbuffer.ctrlstring, BUF_SIZE, stdin);        if(strncmp("exit", msgbuffer.ctrlstring, 4) == 0)        {            break;        }        msgbuffer.ctrlstring[strlen(msgbuffer.ctrlstring) -1] = '\0';        msgbuffer.msgtype = CLIENT_MSG;        if(msgsnd(qid, &msgbuffer, strlen(msgbuffer.ctrlstring) +1, 0) == -1)        {            perror("client msgsnd error!\n");            exit(1);        }    }    return 0;}

运行结果:
服务器端

客户端

0 0
原创粉丝点击