一、消息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法.
每个数据块都被认为是一种类型,接受者进程接收的数据块可以有不同的类型值.
我们可以通过发送消息来避免命名管道的同步与阻塞问题.消息队列与管道不同的是,消息队列是基于消息的,而管道是基于字节流的.且消息队列的读取不一定是先入先出.
缺陷是:每个消息的最大长度是有限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统的消息队列的总数也有一个上限(MSGMNI).
二、IPC数据对象结构
内核为每个IPC对象维护⼀一个数据结构
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 */
};
消息队列结构 /usr/include/linux/msg.h
struct msqid_ds
{
struct ipc_perm msg_perm;
struct msg* msg_first; /* first message on queue, unused */
struct msg* msg_last; /* last message in queue, unused */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of butes on queue */
unsigned short msg_qnum; /* number of messages in queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
}
三、有关的函数介绍
1、msgget 函数:用来创建和访问⼀一个消息队列
可以看出:
原型 int msgget(key_t key, int msgflg);
参数介绍:
key: 某个消息队列的名字
msgflg:由九个权限标志构成,它们的⽤用法和创建⽂文件时使⽤用的mode模式标志是⼀一样的
返回值:成功返回⼀一个⾮非负整数,即该消息队列的标识码;失败返回-1
2、msgctl 函数:消息队列的控制函数
可以看出:
原型 :int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数 :msqid: 由msgget函数返回的消息队列标识码 cmd:是将要采取的动作,有三个可取值,如下
IPC_STAT:用来获取消息队列对应的msqid_ds数据结构,并将其保存到buf指定的地址空间
IPC_SET:用来设置消息队列的属性,要设置在存储在Buf中
IPC_RMID:从内核中删除msqid标识的消息队列
返回值:成功返回0,失败返回-1
3、msgsnd 函数:把一条消息添加到消息队列中
可以看出:
原型 :int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数 :
msgid: 由msgget函数返回的消息队列标识码
msgp:是⼀一个指针,指针指向准备发送的消息,
msgsz:是msgp指向的消息⻓长度,这个⻓长度不含保存消息类型的那个long int⻓长整型
msgflg:控制着当前消息队列满或到达系统上限时将要发⽣生的事情 (msgflg=IPC_NOWAIT表⽰示队列满不等待,返回EAGAIN错误)
返回值:成功返回0;失败返回-1
4、msgrcv 函数:是从⼀一个消息队列接收消息
由上图可知,
原型 : ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数 :
msgid: 由msgget函数返回的消息队列标识码
msgp:是⼀一个指针,指针指向准备接收的消息,
msgsz:是msgp指向的消息⻓长度,这个⻓长度不含保存消息类型的那个long int⻓长整型
msgtype:它可以实现接收优先级的简单形式
msgflg:控制着队列中没有相应类型的消息可供接收时将要发⽣生的事
返回值:成功返回实际放到接收缓冲区⾥里去的字符个数,失败返回-1
5、ftok()函数:在一个已存在的路径名pathname提取文件信息,根据这些文件信息及proj_id,转换成一个key_t 值,称为IPC键
四、消息队列的实现
1、Makefile文件的实现
1
2 .PHONY:all
3 all:client server
4
5 client:client.c comm.c
6 gcc -o $@ $^
7 server:server.c comm.c
8 gcc -o $@ $^
9
10 .PHONY:clean
11 clean:
12 rm -f client server
2、comm.h文件
1 #ifndef _COMM_H_
2 #define _COMM_H_
3
4 #include<stdio.h>
5 #include<sys/types.h>
6 #include<sys/ipc.h>
7 #include<sys/msg.h>
8 #include<string.h>
9
10 #define PATHNAME "."
11 #define PROJ_ID 0x6666
12
13 #define SERVER_TYPE 1
14 #define CLIENT_TYPE 2
15
16 struct msgbuf{
17 long mtype;
18 char mtext[1024];
19 };
20
21 int createMsgQueue();
22 int getMsgQueue();
23 int destoryMsgQueue(int magid);
24 int sendMsg(int msgid,int who,char *msg);
25 int recvMsg(int msgid,int recvType,char out[]);
26
27 #endif
3、comm.c文件
1 #include"comm.h"
2
3 //success>0 failed==-1
4
5 static int commMsgQueue(int flags){
6 key_t _key=ftok(PATHNAME,PROJ_ID);
7 if(_key<0){
8 perror("ftok");
9 return -1;
10 }
11 //int msgid=msgget(_key,IPC_CREAT|IPC_EXCL);
12 int msgid=msgget(_key,flags);
13 if(msgid<0){
14 perror("msgget");
15 }
16 return msgid;
17 }
18
19 int createMsgQueue(){
20 return commMsgQueue(IPC_CREAT|IPC_EXCL|0666);
21 }
22
23 int getMsgQueue(){
24 return commMsgQueue(IPC_CREAT);
25 }
26
27 int destoryMsgQueue(int msgid){
28 if(msgctl(msgid,IPC_RMID,NULL)<0){
29 perror("msgctl");
30 return -1;
31 }
32 return 0;
33 }
34
35 int sendMsg(int msgid,int who,char *msg){
36 struct msgbuf buf;
37 buf.mtype=who;
38 strcpy(buf.mtext,msg);
39
40 if(msgsnd(msgid,(void*)&buf,sizeof(buf.mtext),0)<0){
41 perror("msgsnd");
42 return -1;
43 }
44 return 0;
45 }
4、server.c文件
1 #include"comm.h"
2
3 int main(){
4 int msgid=createMsgQueue();
5
6 char buf[1024];
7 while(1){
8 buf[0]=0;
9 recvMsg(msgid,CLIENT_TYPE,buf);
10 printf("client# %s\n",buf);
11
12 printf("Please Enter# ");
13 fflush(stdout);
14 ssize_t s=read(0,buf,sizeof(buf));
15 if(s>0){
16 buf[s-1]=0;
17 sendMsg(msgid,SERVER_TYPE,buf);
18 printf("send done,wait recv...\n");
19 }
20 }
21 destoryMsgQueue(msgid);
22 return 0;
23 }
5、client.c文件
1 #include"comm.h"
2
3 int main(){
4 int msgid=getMsgQueue();
5
6 char buf[1024];
7 while(1){
8 buf[0]=0;
9 printf("Please Enter# ");
10 fflush(stdout);
11 ssize_t s=read(0,buf,sizeof(buf));
12 if(s>0){
13 buf[s-1]=0;
14 sendMsg(msgid,CLIENT_TYPE,buf);
15 printf("send done,wait recv...\n");
16 }
17 recvMsg(msgid,SERVER_TYPE,buf);
18 printf("server# %s\n",buf);
19 }
20 return 0;
21 }
看运行结果:
五、ipcs与ipcrm命令
ipcs: 显⽰示IPC资源
ipcrm: ⼿手动删除IPC资源
异常终⽌止server和client,再次运⾏server会报错。
如下,终止后在运行报错:
这时就要用到ipcs与ipcrm命令:
再运行就可以成功了。