6.消息队列的通信原理
消息队列的工作原理如下图解释,消息队列存在于linux内核,每个队列有Id号,是由链表组成的,但我们关心的是1、如何创建新的消息队列(链表) ;2、进程B(或A)怎么加消息到队列;3、进程A(或B)怎么从队列拿到消息 。
下面看看别人的博文如何描述消息队列的:
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识
1、特点
1、消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
2、消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
3、消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
7.消息队列相关api
AB进程使用消息队列方式通讯有这几个步骤:
A1、获取队列; B1、获取/创建队列
A2、读数据; B2、写数据到队列
原型
1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);//key是索引值是无类型的整形数,通过其从内核中找到队列,flag是打开队列的方式。返回值是队列的ID。
4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);//msgid是队列的ID;第二个参数是消息内容,第三个是消息的大小,flag=0是非阻塞方式写到消息队列结构体里面
6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);//于上面的发送消息一样的参数,type是进程找相关类型的队列消息,flag如果是0的话就是以默认方式读数据,读不到type(就是结构体的类型)是888类型的数据会阻塞。
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
在以下两种情况下,msgget将创建一个新的消息队列:
1、如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。//一般用第一种情况。
2、key参数为IPC_PRIVATE。
队列里面的单个信息是个结构体是这样的(是读写队列函数的第二个参数模板)
struct msgbuf {
long mtype; /* message type, must be > 0 *//类型
char mtext[1]; /* message data */ //内容
};
8.消息队列编程收发数据
直接上代码(msgGet.c):接收端
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
//1.huoqu
struct msgbuf readBuf;
int msgId = msgget(0x1234,IPC_CREAT|0777);//打开或创建队列
if(msgId ==-1){
printf("get que failuer\n");
}
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);//读取消息
printf("read from que:%s\n",readBuf.mtext);
struct msgbuf sendBuf = {988,"thank you for reach"};
msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//发送消息
return 0;
}
直接上代码(msgSend.c):接收端
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
//1.send
struct msgbuf sendBuf = {888,"this is message from quen"};//队列的类型和消息内容都在这个结构体里。
struct msgbuf readBuf;
int msgId = msgget(0x1234,IPC_CREAT|0777);//创建或打开队列
if(msgId ==-1){
printf("get que failuer\n");
}
msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//发送队列消息
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);//接收队列消息
printf("return from get:%s\n",readBuf.mtext);
return 0;
}
运行结果如下:(两个进程间可以通过消息队列发送和接收信息)
9.键值生成及消息队列移除
上面的带代是key(是索引,去内核找到相关队列的ID)被定死的,比较恼火,这里我们用更高大上的方式来做:
首先需要用到ftok函数:
系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
ftok原型
头文件
#include <sys/types.h>
#include <sys/ipc.h>
函数原型:
key_t ftok( const char * fname, int id )
fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:
key_t key;
key = ftok(".", 1); 这样就是将fname设为当前目录。
id是子序号。虽然是int类型,但是只使用8bits(1-255)(可以是字符,因为字符是一个字节的,)。
在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
查询文件索引节点号的方法是: ls -i
下面是键值生消息队列接收端(msgGet.c)的代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
//1.huoqu
struct msgbuf readBuf;
key_t key;
key = ftok(".",'z');
printf("key=%x\n",key);
int msgId = msgget(key,IPC_CREAT|0777);
if(msgId ==-1){
printf("get que failuer\n");
}
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
printf("read from que:%s\n",readBuf.mtext);
struct msgbuf sendBuf = {988,"thank you for reach"};
msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
下面是键值生消息队列接收端(msgSend.c)的代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
//1.send
struct msgbuf sendBuf = {888,"this is message from quen"};
struct msgbuf readBuf;
key_t key;
key = ftok(".",'z'); //键值生成队列,需要先生成key。
printf("key=%x\n",key);
int msgId = msgget(key,IPC_CREAT|0777);
if(msgId ==-1){
printf("get que failuer\n");
}
msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);
printf("return from get:%s\n",readBuf.mtext);
msgctl(msgId,IPC_RMID,NULL); //消除队列,第一个参数是队列的Id,第二个参数是命令,这里用消除的命令,第三个参数基本是写NULL。
return 0;
}
运行结果图如下:
10.共享内存概述
下图是用场景来解释进程间通讯:1、管道的话就是男(或女)的写纸条塞进管道,女(或男)的只能读,不能写,只能确定一方是读还是写;2、消息队列男的放入箱子中纸条,女的去看,但是不拿走;女的也可以放纸条,男的看,不拿走;3、共享内存是纸条就放在桌上,男的直接写,女的直接就可以看到了,这是目前为止比较先进的一种通讯IPC方式。
11.共享内存编程实现
1、特点:
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
因为多个进程可以同时操作,所以需要进行同步。
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
2、原型
1 #include <sys/shm.h>
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);//第二个参数是共享内存大小,其必须于兆(M)对齐的。
4 // 连接(映射)共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);//第二个参数写0,让Linux内核自动帮我们安排共享内存。第三个参数一般我们也写0,代表我们映射进来的共享内存可读可写。
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr);
8 // 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);//一般用来干掉共享内存,第二个是命令,这里用消除的命令,消息队列有碰到;第三个参数一般写0.
下面是共享内存写端的代码(shmw.c):
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/*函数原型
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
*/
int main()
{
int shmid;
char *shmaddr;
key_t key;
key = ftok(".",1);
printf("key=%d\n",key);//创建key
shmid = shmget(key,1024*4,IPC_CREAT|0666);//shmid与key值不相等,这里是创建共享内存
printf("shmid=%d\n",shmid);
if(shmid == -1){
printf("shmget noOk\n");
exit(-1);
}
shmaddr = shmat(shmid,NULL,0);//映射到这个进程,用指针指向这个共享内存空间。
printf("shmat ok\n");
strcpy(shmaddr,"zhanglong");//对内存空间写数据
sleep(5);
shmdt(shmaddr);//进程断开与内存空间的连接
shmctl(shmid,IPC_RMID,0);//消除内存空间
printf("quit\n");
return 0;
}
下面是共享内存读端的代码(shmw.c):
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/*函数原型
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
*/
int main()
{
int shmid;
char *shmaddr;
key_t key;
key = ftok(".",1);
printf("key=%d\n",key);//创建key(索引)
shmid = shmget(key,1024*4,0);//这里只对共享内存读,所以第三个参数写0就好了。
printf("shmid=%d\n",shmid);
if(shmid == -1){
printf("shmget noOk\n");
exit(-1);
}
shmaddr = shmat(shmid,NULL,0);//映射共享内存,连接当前进程。
printf("shmat ok\n");
printf("data: %s",shmaddr);
shmdt(shmaddr);//断开连接,因为写端已经消除共享内存了,所以这里不需要调用shmctl函数消除。
printf("quit\n");
return 0;
}
运行结果如下图,此处只是简单的对共享内存应用,只是进行一端写,不然会把共享内存写的东西杂在一起的,所以需要后面信号量的学习;