linux进程间通信之消息队列

http://www.cnblogs.com/magicbox/archive/2012/02/01/2363880.html
 
=========================================================

一、消息队列的基本概念
消息队列 (也叫做报文队列)是Unix系统V版本中3种进程间通信机制之一。另外两种是信号量共享存储。这些IPC机制使用共同的授权方法。只有通过系统调用将标志符传递给核心之后,进程才能存取这些资源。这种系统IPC对象使用的控制方法和文件系统非常类似。使用对象的引用标志符作为资源表中的索引。
消息队列就是一个消息的链表。就是把消息看作一个记录,并且这个记录具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读出消息。
Linux采用消息队列的方式来实现消息传递。这种消息的发送方式是:发送方不必等待接收方检查它所收到的消息就可以继续工作下去,而接收方如果没有收到消息也不需等待。这种通信机制相对简单,但是应用程序使用起来就需要使用相对复杂的方式来应付了。新的消息总是放在队列的末尾,接收的时候并不总是从头来接收,可以从中间来接收。
消息队列是随内核持续的并和进程相关,只有在内核重起或者显示删除一个消息队列时,该消息队列才会真正被删除。因此系统中记录消息队列的数据结构 (struct ipc_ids msg_ids)位于内核中,系统中的所有消息队列都可以在结构msg_ids中中找到访问入口。


IPC标识符:每一个I P C目标都有一个唯一的I PC标识符。这里所指的I P C目标是指一个单独的消息队列、一个信号量集或者一个共享的内存段。系统内核使用此标识符在系统内核中指明 I P C目标。
IPC 关键字:想要获得唯一的标识符,则必须使用一个 I P C关键字。客户端进程和服务器端进程必须双方都同意此关键字。这是建立一个客户机/服务器框架的第一步。在System V IPC机制中,建立两端联系的路由方法是和I P C关键字直接相关的。通过在应用程序中设置关键字值,每一次使用的关键字都可以是相同的。一般情况下,可以使用f t o k ( )函数为客户端和服务器端产生关键字值。

二、ipcs 命令
命令ipcs用于读取System V IPC目标的状态。
ipcs -q: 只显示消息队列。
ipcs -s: 只显示信号量。
ipcs -m: 只显示共享内存。
ipcs –help: 其他的参数。    
 
三、消息队列的主要调用
 
内核中实现消息传递机制的代码基本上都在文件ipc/msg.c中,消息队列的主要调用有下面4个
(1)msgget:调用者提供一个消息队列的键标 (用于表示个消息队列的唯一名字),当这个消息队列存在的时候, 这个消息调用负责返回这个队列的标识号;如果这个队列不存在,就创建一个消息队列,然后返回这个消息队列的标识号 ,主要由sys_msgget执行。
(2)msgsnd:向一个消息队列发送一个消息,主要由sys_msgsnd执行。
(3)msgrcv:从一个消息队列中收到一个消息,主要由sys_msgrcv执行。
(4)msgctl:在消息队列上执行指定的操作。根据参数的不同和权限的不同,可以执行检索、删除等的操作,主要由sys_msgctl执行。
 


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


int     msgget( key_t msgkey , int flag );
取得一个消息队列的ID,如不存在则建立。
返回值:        成功时:消息队列的ID
                        失败时:-1
 


int     msgsnd( int msqid , struct msgbuf *msgp , size_t msgsiz , int msgflag );
向消息队列送消息
返回值:        成功时:0
                        失败时:-1
msqid是消息队列的ID,size_t msgsiz是结构体成员mdata的大小,msgflag与上一章所讲的共享内存的flag起一样的作用,不过,当这个参数为IPC_NOWAIT的时候,如果消息队列已满,则返回错误值。如果不为IPC_NOWAIT,在消息队列已满 的情况下,会一直等到消息队列有空地方的时候再发送。
注意这里的这个 struct msgbuf *msgp 。要求的格式如下:
struct  msgbuf
{
        long    mtype;
        char    mdata[256];
};
long mtype在这里我们用来保存本进程的PID。mdata则是保存要发送的数据。由于mdata的大小不一定(根据实际需要定义),所以这个结构体并没有事先定义好。但是我们定义这个结构体的时候一定要遵循这个规定。你可以改的,只有mdata的大小,和结构体的名称。尽量不要修改结构体成员的名称和类型。实际上,根据mtype,我们还可以有所选择地接受消息。这在下面将会谈到。
 
 


int     msgrcv( int msqid , struct msgbuf *msgp , size_t msgsiz , long msgtyp , int msgflag );
从消息队列取得一个消息
返回值:        成功时:0
                        失败时:-1
msqid , *msgp , msgsiz不用说了。long msgtyp是结构体msgbuf的mtype成员。msgflag与上述一样。只不过为IPC_NOWAIT的时候,如果消息队列是空的,则等到有消息可读的时候再读。当不为IPC_NOWAIT的时候,如果消息队列是空的,则返回错误值(与字面上理解的有些相反)
 
下面这个链接帮助了我更好地理解了msgrcv  中的 long msgtyp这个参数和
struct  msgbuf
{
        long    mtype;
        char    mdata[256];
};
里的long mtype这个结构体成员。
http://topic.csdn.net/u/20120131/15/235be4a4-3901-41ef-a577-55a5650efeeb.html?14521
 


同样地,为了控制管理消息队列,一样有一个函数msgctl()如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>


int     msgctl( int msqid , int cmd , struct msqid_ds *buf );
返回值:        成功时:0
                        失败时:-1
cmd所指定的值与共享内存部分相同。
 
最后自己写了一个利用消息队列实现进程通信
基本思路:
我每次将A端发送消息时的消息类型标记为奇数,将B端发送的消息类型为偶数。
A端读取消息类型为奇数的消息(也就是说是从B端发送过来的)
B端读取消息类型为偶数的消息(也就是说是从A端发送过来的)
 

//A.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>

int     main()
{  
	int 	count = 0;                                                 /*打印奇数消息类型,发送偶数消息类型*/
	int     msqid;
	key_t   msgkey;
	struct  msgbuf
	{
		long    mtype;
		char    mdata[256];
	};
	struct  msgbuf  msgdata , *p ;
	p = &msgdata ;
	msgkey = ftok( "." , 'a' );     				   /*计算标识符*/
	msqid = msgget( msgkey , IPC_CREAT | 0666 ) ;  		   	   /*取得消息队列的ID*/

	while(1)
	{	
		printf("MiiBotree : ");
	   	fflush( stdin );       					  /*刷新标准输入缓冲区*/
    	gets( p->mdata );      						  /*输入字符串*/
    	p->mtype = count;
   	
    	if (!msgsnd( msqid , p , sizeof(p->mdata) , 0 ))    	          /*送消息*/
		{
			count = count + 2;
		}

		msgrcv( msqid , p , sizeof(p->mdata) , count+1 , 0 ) ;    /*读消息*/
		if (p->mtype % 2 == 1){
			printf("Firefoxbug:%s\n", p->mdata);
			count += 2;
		}
		
	}
	return 0;
}

//B.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>

int  main()
{
	int count = 1;          /*打印偶数消息类型,发送奇数消息类型*/
	int     msqid;
    key_t   msgkey;
	struct  msgbuf
	{
		long    mtype;
 		char    mdata[256];
 	};
	struct  msgbuf  msgdata , *p ;									        
	p = &msgdata ;
	msgkey = ftok ( "." , 'a' );   				  
   	msqid = msgget( msgkey , IPC_CREAT | 0666 ) ;  			 
    	
	while(1)
	{
		printf("Firefoxbug : ");
    	fflush( stdin );       						
    	gets( p->mdata );      						
    	p->mtype = count;
    	if (!msgsnd( msqid , p , sizeof(p->mdata) , 0 ))                 
		{
			count = count + 2;
		}

		msgrcv( msqid , p , sizeof(p->mdata) , count-1 , 0 ) ;   
		if (p->mtype % 2 == 0){
			printf("MiiBotree:%s\n", p->mdata);
			count += 2;
		}
	
	}
	return 0;
}


 
 
这个程序还有一些不完善的地方:
1.每个端口不能连续输入,必须等对方输入以后才会显示结果。这里我也不清楚怎么回事。gdb的调试还不太会,错误不太会找。希望高手指教,呵呵
2.没有删除消息队列,下次打开时候还会残留以前的消息
 
最后列出参考资料:
http://linux.chinaunix.net/techdoc/develop/2009/04/27/1109110.shtml
http://blog.sina.com.cn/s/blog_48c9576b0100joqg.html
http://blog.sina.com.cn/s/blog_48c9576b0100joqg.html
《Linux软件工程师(c语言)实用版》
 
===================END====================================
 

posted @ 2013-04-15 16:43  夏至冬末  阅读(237)  评论(0编辑  收藏  举报