Linux 进程间通信(IPC)之一 --- 消息队列

Linux 的进程间通信有很多种方法,诸如管道,共享内存,消息队列,信号,信号量,套接字等;其中,消息队列的机制具有传送大消息内容的优势,今天这篇文章详细论述消息队列的原理和实现。

 

简单的说消息队列的通信机制是通过下列三个系统调用来实现的。

1. msgget() ---创建一个消息队列。

2. msgrcv() --- 从消息队列中取出一个消息。

3. msgsnd(), ----像消息队列中发送一个消息。

 

首先,看看这三个系统调用的说明:

msgget

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

函数原型:int    msgget ( key_t  key , int  msgflg );

函数描述:建立消息队列

参数:

msgget()函数的第一个参数是消息队列对象的关键字(key),函数将它与已有的消息队
列对象的关键字进行比较来判断消息队列对象是否已经创建。而函数进行的具体操作是第二个参数,msgflg控制的。它可以取下面的几个值:
IPC_CREAT:如果消息队列对象不存在,则创建之,否则则进行打开操作;
IPC_EXCL:IPC_CREAT一起使用(用”|”连接),如果消息对象不存在则创建之,否     则产生一个错误并返回。

返回值:

成功时返回队列ID,失败返回-1,错误原因存于error

EEXIST (Queue exists, cannot create)
EIDRM (Queue is marked for deletion)
ENOENT (Queue does not exist)
ENOMEM (Not enough memory to create queue)
ENOSPC (Maximum queue limit exceeded)




msgsnd函数:将消息送入消息队列

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h

函数原型:int  msgsnd ( int msgid ,  struct msgbuf*msgp , int msgsz, int msgflg );

参数说明:

传给msgsnd()函数的第一个参数msqid是消息队列对象的标识符(由msgget()函数得

到),第二个参数msgp指向要发送的消息所在的内存,第三个参数msgsz是要发送信息     的长度(字节数),可以用以下的公式计算:
msgsz = sizeof(struct mymsgbuf) - sizeof(long);
第四个参数是控制函数行为的标志,可以取以下的值:
0,忽略标志位;
IPC_NOWAIT,如果消息队列已满,消息将不被写入队列,控制权返回调用函数的线
程。如果不指定这个参数,线程将被阻塞直到消息被可以被写入。

smgbuf结构体定义如下:

struct smgbuf

{

                     long   mtype;

                    char   mtext [x] ;  //长度由msgsz决定

}


msgflg可设置为IPC_NOWAIT。如果消息队列已满或其他情况无法送入消息,则立即返回EAGIN

返回:0 on success

-1 on error: errno = EAGAIN (queue is full, and IPC_NOWAIT was asserted)
EACCES (permission denied, no write permission)
EFAULT (msgp address isn't accessable – invalid)
EIDRM (The message queue has been removed)
EINTR (Received a signal while waiting to write)
EINVAL (Invalid message queue identifier, nonpositive
message type, or invalid message size)
ENOMEM (Not enough memory to copy message buffer)


msgrcv函数:从消息队列中读取消息

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

函数定义:int  msgrcv( int  msgid , struct   msgbuf*  msgp ,  int msgsz ,  long msgtyp, int msgflg);

参数:

函数的前三个参数和msgsnd()函数中对应的参数的含义是相同的。第四个参数mtype

指定了函数从队列中所取的消息的类型。函数将从队列中搜索类型与之匹配的消息并将返回。不过这里有一个例外。如果mtype的值是零的话,函数将不做类型检查而自动返     回队列中的最旧的消息。第五个参数依然是是控制函数行为的标志,取值可以是:
0,表示忽略;
IPC_NOWAIT,如果消息队列为空,则返回一个ENOMSG,并将控制权交回调用函数
的进程。如果不指定这个参数,那么进程将被阻塞直到函数可以从队列中得到符合条件消息为止。如果一个client正在等待消息的时候队列被删除,EIDRM就会被返回。如果     进程在阻塞等待过程中收到了系统的中断信号,EINTR就会被返回。
MSG_NOERROR,如果函数取得的消息长度大于msgsz,将只返回msgsz长度的信息,
剩下的部分被丢弃了。如果不指定这个参数,E2BIG将被返回,而消息则留在队列中不     被取出。
当消息从队列内取出后,相应的消息就从队列中删除了。

msgbuf:结构体,定义如下:

struct msgbuf

{

                      long  mtype ;  //信息种类

                       char   mtest[x];//信息内容   ,长度由msgsz指定

}


msgtyp: 信息类型。 取值如下:

msgtyp = 0,不分类型,直接返回消息队列中的第一项

msgtyp > 0 ,返回第一项msgtypmsgbuf结构体中的mtype相同的信息

msgtyp <0 ,返回第一项mtype小于等于msgtyp绝对值的信息


msgflg:取值如下:

IPC_NOWAIT ,不阻塞

IPC_NOERROR,若信息长度超过参数msgsz,则截断信息而不报错。



返回值:

成功时返回所获取信息的长度,失败返回-1,错误信息存于error

Number of bytes copied into message buffer
-1 on error: errno = E2BIG (Message length is greater than
msgsz,no MSG_NOERROR)
EACCES (No read permission)
EFAULT (Address pointed to by msgp isinvalid)
EIDRM (Queue was removed duringretrieval)
EINTR (Interrupted by arriving signal)
EINVAL (msgqid invalid, or msgsz less than 0)
ENOMSG (IPC_NOWAIT asserted, and nomessageexists in the queue to satisfy therequest)

 

 

实例代码:

 

场景:

    进程A向消息队列写消息,而进程B则从消息队列读消息。

消息队列和之前的管道不同,消息队列是相对独立于进程的,它不需要进程自己来提供同步方法。消息队列里面有消息就可以读。

读取消息实例:msg_rcv.c

01.#include <unistd.h>  
02.#include <stdlib.h>  
03.#include <stdio.h>  
04.#include <sys/types.h>  
05.#include <sys/ipc.h>  
06.#include <sys/msg.h>  
07.  
08.struct  my_msg_st {  
09.    long int my_msg_type;  
10.    char some_text[BUFSIZ];  
11.};  
12.  
13.int main(int argc,char **argv)  
14.{  
15.    int running = 1;  
16.    int msgid;  
17.    struct  my_msg_st some_data;  
18.    long int msg_to_receive = 0;  
19.  
20.    msgid = msgget((key_t)1234,0666 | IPC_CREAT);  
21.    if(msgid == -1){  
22.        fprintf(stderr,"Msgget failed!\n");  
23.        exit(-1);  
24.    }  
25.  
26.    while(running){  
27.        if(msgrcv(msgid,(void *)&some_data,BUFSIZ,msg_to_receive,0) == -1){  
28.            fprintf(stderr,"Msgrcv failed!\n");  
29.            exit(-1);  
30.        }  
31.        printf("You wrote: %s",some_data.some_text);  
32.        if(strncmp(some_data.some_text,"end",3) == 0){  
33.            running = 0;  
34.        }  
35.    }  
36.  
37.    if(msgctl(msgid,IPC_RMID,0) == -1){  
38.        fprintf(stderr,"Msgctl failed!\n");  
39.        exit(-1);  
40.    }  
41.  
42.    exit(0);  
43.} 

写入消息队列应用:msg_snd.c
  
01.#include <unistd.h>  
02.#include <stdlib.h>  
03.#include <stdio.h>  
04.#include <sys/types.h>  
05.#include <sys/ipc.h>  
06.#include <sys/msg.h>  
07.  
08.#define MAX_TEXT    256  
09.  
10.struct  my_msg_st {  
11.    long int my_msg_type;  
12.    char some_text[BUFSIZ];  
13.};  
14.  
15.int main(int argc,char **argv)  
16.{  
17.    int running = 1;  
18.    int msgid;  
19.    struct  my_msg_st some_data;  
20.    char    buffer[BUFSIZ];  
21.  
22.    msgid = msgget((key_t)1234,0666 | IPC_CREAT);  
23.    if(msgid == -1){  
24.        fprintf(stderr,"Msgget failed!\n");  
25.        exit(-1);  
26.    }  
27.  
28.    while(running){  
29.        printf("Enter Some Text: ");  
30.        fgets(buffer,BUFSIZ,stdin);  
31.        some_data.my_msg_type = 1;  
32.        strcpy(some_data.some_text,buffer);  
33.        if(msgsnd(msgid,(void *)&some_data,MAX_TEXT,0) == -1){  
34.            fprintf(stderr,"Msgsnd failed!\n");  
35.            exit(-1);  
36.        }  
37.        if(strncmp(some_data.some_text,"end",3) == 0){  
38.            running = 0;  
39.        }  
40.    }  
41.    exit(0);  
42.}  

 

注意,这个程序的验证并不需要两个进程同时运行。

首先运行snd进程,写入几条消息。

然后启动rcv进程,读取消息并显示,最后删除消息队列文件。

备注:查看系统进程的IPC资源命令

# ipcs //查看ipc资源

# ipcrm //删除ipc资源


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值