Linux下进程间通信--消息队列

消息队列的定义遍地都是,不想移驾,请看下文:

一、定义:

   消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。  

   每个数据块都被认 为是有一个类型,接收者进程接收的数据块可以有不同的类型值。我们可以通过发送消息来避免命名管道的同步和阻塞问题。

消息队列与管道不同的是,消息队列是基于消息的, 而管道是基于字节流的,且消息队列的读取不一定是先入先出。

消息队列与命名管道有一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)

关于上下限:


二、IPC对象数据结构 

   内核为每个IPC对象维护一个数据结构(/usr/include/linux/ipc.h)     

struct ipc_perm
 {
    key_t   __key;       /* Key supplied to xxxget(2) */ 
    uid_t   uid;         /* Effective UID of owner */ 
    gid_t   gid;         /* Effective GID of owner */ 
    uid_t   cuid;        /* Effective UID of creator */ 
    gid_t   cgid;        /* Effective GID of creator */ 
    unsigned short mode; /* Permissions */ 
    unsigned short __seq;/* Sequence number */ 
};

消息队列,共享内存和信号量都有这样一个共同的数据结构。

三、消息队列结构:


可以看到第一个条目就是IPC结构体,即是共有的,后面的都是消息队列所私有的成员,消息队列是用链表实现的

四、相关函数解释:

1、msgget函数
该函数用来创建和访问一个消息队列。它的原型为:
int msgget(key_t, key, int msgflg); 
与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。
1.msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。

2.msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。

3.它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1.

2、msgsnd函数
该函数用来把消息添加到消息队列中。它的原型为:
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg); 
1.msgid是由msgget函数返回的消息队列标识符。

2.msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体;

    接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样:
struct my_message{  
    long int message_type;  
    /* The data you wish to transfer*/  
};
3.msg_sz是msg_ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。

4.msgflg用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。

   如果调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1.

3、msgrcv函数
该函数用来从一个消息队列获取消息,它的原型为
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
1.msgid, msg_ptr, msg_st的作用也函数msgsnd函数的一样。

2.msgtype可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。

     如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。

3.msgflg用于控制当队列中没有相应类型的消息可以接收时将发生的事情。

调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1.

4、msgctl函数
该函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:
int msgctl(int msgid, int command, struct msgid_ds *buf);  
command是将要采取的动作,它可以取3个值,

    IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。

    IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值

    IPC_RMID:删除消息队列

buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:
struct msgid_ds  
{  
    uid_t shm_perm.uid;  
    uid_t shm_perm.gid;  
    mode_t shm_perm.mode;  
};  
成功时返回0,失败时返回-1.

五、用消息队列实现进程间通信实例

让两个没有任何联系的进程间通信,以下是Linux下的代码:
1.server.c

2.client.c

3.comm.h


4.comm.c



代码主要就贴comm.c里的:

#include"comm.h"

static int com_msg_queue(int flags)
{
    key_t _key = ftok(_PROJ_NAME_,_PROJ_ID_);
    if(_key < 0)
    {
	perror("ftok");
	return -1;
    }

    int msg_id = msgget(_key,flags); //
    if(msg_id < 0)
    {
	perror("msgget");
    }

    return msg_id;
}

int create_msg_queue()
{
    int flags = IPC_CREAT | IPC_EXCL | 0644;
    return com_msg_queue(flags);
}

int get_msg_queue()
{
    int flags = IPC_CREAT;
    return com_msg_queue(flags);
}

int destroy_msg_queue(int msg_id)
{
    if(msgctl(msg_id,IPC_RMID,NULL) < 0)
    {
	perror("msgctl");
	return -1;
    }
    return 0;
}

int send_msg(int msg_id,int t,const char* msg)
{
    struct msgbuf _msg;
    _msg.mtype = t;
    strncpy(_msg.mtext,msg,strlen(msg)+1);

    if(msgsnd(msg_id,&_msg,sizeof(_msg.mtext),0) < 0)
    {
	perror("msgsnd");
	return -1;
    }
    return 0;
}

int recv_msg(int msg_id,int t,char* out)
{
    struct msgbuf _msg;
    _msg.mtype = t;
    memset(_msg.mtext,'\0',sizeof(_msg.mtext));
    if(msgrcv(msg_id,&_msg,sizeof(_msg.mtext),t,0) < 0)
    {
	perror("msgrcv");
	return -1;
    }
    else
    {
	strcpy(out,_msg.mtext);
    }
    return 0;
}
分析:主要是两个.C文件,两个main函数,两个进程,一个进程发送,一个进程接受,模拟实现连个进程间的通信,代码中最后的两个函数,一个是发送,一个是接受;

六、消息队列与命名管道的比较

   消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。

在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制。
与命名管道相比,消息队列的优势在于:

1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。

2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。

3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

赐教!

posted @ 2016-08-08 21:14  Li_Ning  阅读(140)  评论(0编辑  收藏  举报