一、什么是消息队列
1. 消息队列提供了⼀个从⼀个进程向另外⼀个进程发送⼀块数据的⽅法。
2.每个数据块都被认为是有⼀个类型,接收者进程接收的数据块可以有不同的类型值。(数据块:即消息队列)
3.消息队列也有管道⼀样的不⾜,就是每个消息的最⼤⻓度是有上限的,每个消息队列的总的字节数是有上限的,系统上消息队
列的总数也有⼀个上限。
二、消息队列的结构
消息队列在内核中可以认为是一个链表构成的队列,它的结构中我们主要关注队头和队尾,还有ipc对象的权限信息。
struct msqid_ds {
struct ipc_perm msg_perm;//权限信息
struct msg *msg_first; //队头
struct msg *msg_last; //队尾
__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 bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
}
内核为每个ipc对象维护一个数据结构:
重点关注_key,它是唯一的,使进程可以看到同一个消息队列。
struct ipc_perm {
key_t __key;//标识(身份证号)
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; //权限
unsigned short __seq; /* Sequence number */
};
三、消息队列函数
1.msgget函数:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
功能:⽤来创建和访问⼀个消息队列
原型:
int msgget(key_t key, int msgflg);
参数:
key: 某个消息队列的名字
msgflg:由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
(IPC_CREAT//不存在创建,存在就打开
IPC_CREAT|EXCL//不存在就创建,存在出错)
返回值:成功返回⼀个⾮负整数,即该消息队列的标识码;失败返回-1
key的生成:
系统建立IPC通信时必须指定一个ID值,即关键字key。由ftok函数得到。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
//pathname就是你指定的文件名(路径),要求文件必须存在,一般使用当前目录,如:
key_t key;
key = ftok(".", 1);//这样就是将pathname设为当前目录
//id是子序号,虽然是int类型,但是只使用8bits(1-255)
//返回值:失败返回-1,成功返回key_t值
2.msgsnd函数
功能:把⼀条消息添加到消息队列中
原型
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数
msgid: 由msgget函数返回的消息队列标识码,表示往哪个消息队列发数据
msgp:是⼀个指针,指针指向准备发送的消息
msgsz:消息大小,即mtext的大小
msgflg:0表示阻塞方式,IPC_NOWAIT表⽰队列满不等待,返回EAGAIN错误。
返回值:成功返回0;失败返回-1
消息结构参考形式如下:
struct msgbuf {
long mtype;//它必须以⼀个long int⻓整数开始,接收者函数将利⽤这个⻓整数确定消息的类型
char mtext[100];//它必须⼩于系统规定的上限值;
}
3.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
4.msgctl函数
功能:消息队列的控制函数
原型
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数
msqid: 由msgget函数返回的消息队列标识码
cmd:是将要采取的动作,(有三个可取值)
返回值:成功返回0,失败返回-1
cmd:
1. IPC_STAT
读取消息队列的数据结构msqid_ds,并将其存储在buf指定的地址中。
2. IPC_SET
设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。这个值取自buf参数。
3. IPC_RMID
删除消息队列。
四、client/server通信实例
功能:客户端向服务器段发送消息,服务器端将收到的消息返回给客户端
代码:
comm.h:
1 #ifndef _COMM__H_
2 #define _COMM__H_
3 #include<stdio.h>
4 #include<sys/types.h>
5 #include<sys/ipc.h>
6 #include<sys/msg.h>
7 #include<string.h>
8 #define PATHNAME "."//ftok参数
9 #define PROJ_ID 0x6666//ftok参数
10
11 #define SERVER_TYPE 1//通道号
12 #define CLIENT_TYPE 2
13
14 struct msgbuf
15 {
16 long mtype;
17 char mtext[1024];
18 };
19
20 int createMsgQueue();//创建消息队列
21 int getMsgQueue();//打开
22 int sendMsg(int msgid,int who,char* msg);//发送数据
23 int recvMsg(int msgid,int recvType,char out[]);//接收数据
24 int destroyMsgQueue(int msgid);//删除消息队列
25 #endif
comm.c:
1 #include"comm.h"
2
3 static int commMsgQueue(int flags)
4 {
5 key_t _key=ftok(PATHNAME,PROJ_ID);//消息队列的名字
6 if(_key<0)
7 {
8 perror("ftok");
9 return -1;
10 }
11 int msgid=msgget(_key,flags);
12 if(msgid<0)
13 {
14 perror("msgget");
15 }
16 return msgid;
17 }
18
19 int createMsgQueue()
20 {
21 return commMsgQueue(IPC_CREAT|IPC_EXCL|0666);//不存在创建,存在出错
22 }
23
24 int getMsgQueue()
25 {
26 return commMsgQueue(IPC_CREAT);//不存在创建,存在打开
27 }
28
29 int sendMsg(int msgid,int who,char* msg)
30 {
31 struct msgbuf buf;
32 buf.mtype=who;
33 strcpy(buf.mtext,msg);
34
35 if(msgsnd(msgid,(void*)&buf,sizeof(buf.mtext),0)<0)
36 {
37 perror("msgsend");
38 return -1;
39 }
40 return 0;
41 }
42
43 int recvMsg(int msgid,int recvType,char out[])//接收消息缓冲区
44 {
45 struct msgbuf buf;
46 if(msgrcv(msgid,(void*)&buf,sizeof(buf.mtext),recvType,0)<0)
47 {
48 perror("msgrecv");
49 return -1;
50 }
51 strcpy(out,buf.mtext);
52 return 0;
53 }
54 int destroyMsgQueue(int msgid)
55 {
56 if(msgctl(msgid,IPC_RMID,NULL)<0)
57 {
58 perror("msgctl");
59 return -1;
60 }
61 return 0;
62 }
client.c:
1 #include"comm.h"
2 int main()
3 {
4 int msgid=getMsgQueue();
5 char buf[1024];
6 while(1)
7 {
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 {
14 buf[s-1]=0;
15 sendMsg(msgid,CLIENT_TYPE,buf);
16 printf("send done,wait recv...\n");
17 }
18
19 recvMsg(msgid,SERVER_TYPE,buf);
20 printf("server# %s\n",buf);
21 }
22 return 0;
23 }
server.c:
1 #include"comm.h"
2 int main()
3 {
4 //创建消息队列
5 int msgid=createMsgQueue();
6 char buf[1024];
7 while(1)
8 {
9 buf[0]=0;
10 recvMsg(msgid,CLIENT_TYPE,buf);
11 printf("client# %s\n",buf);
12 printf("Please Enter# ");
13 fflush(stdout);
14 size_t s=read(0,buf,sizeof(buf));
15 if(s>0)
16 {
17 buf[s-1]=0;
18 send(msgid,SERVER_TYPE,buf);
19 printf("send done,wait recv...\n");
20 }
21 }
22 destroyMsgQueue(msgid);
23 return 0;
24 }
结果演示:
第一次运行成功,第二次运行server会出错,要手动删除ipc资源,然后再次运行。