什么是消息队列
1.消息队列,是消息的链接表,存放在内核中。
2.一个消息队列由一个标识符(即队列ID)来标识。
3.因为消息队列其实就是一个链表,所以往消息队列里面发送数据就等价于向链表里面插入一个消息结点,从消息队列里面取数据就等价于访问该结点,访问完后再删除这个结点。
消息队列的特点
1.消息队列可用于任意两个进程进行进程间通信(只要这两个进程能够看到同一个消息队列就可以);
2.消息队列可以用于双向通信(全双工);
3.消息队列面向数据报,不再面向字节流,因为消息队列里面接收的数据都是有特定类型的;
4.消息队列生命周期随内核,只有当终端重启或者手动删除消息队列后,该消息队列才会不存在。
5.消息队列也自带同步与互斥。
创建消息队列
1.通过msgget函数。
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
2.参数解释
key:用来标识IPC对象(IPC就是进程间通信)的唯一标识符,通过ftok函数获得key;
msgflag:是一个标志位,如果为IPC_CREAT表示不存在就创建消息队列,存在就打开;如果当参数为IPC_CREAT | IPC_EXCL时表示不存在就创建,存在就会出错,单独使用IPC_EXCL没有任何作用。
3.ftok函数(用于自定义生成一个key值)
key_t ftok(const char *pathname, int proj_id);
//pathname为路径名 proj_id可以理解为项目编号
//成功返回有效key值,失败返回-1
只要两个进程定义的pathname和proj_id相同,那么通过ftok函数这两个进程就能获得同一个key。
4.msgget函数的使用
#include <stdio.h>
#include <sys/msg.h>
int main()
{
key_t key = ftok(".",1); //.表示当前目录,proj_id为1表示项目编号为1
if(key < 0)
{
perror("ftok");
return -1;
}
int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0666);
if(msgid < 0)
{
perror("msgget");
return -1;
}
printf("msgid=%lu\n",msgid);
return 0;
}
运行结果如下:
通过命令查看消息队列
1.ipcs -q用于查看所有的消息队列相关的信息
2.ipcrm -q 后面带上msqid,用于删除指定的消息队列
3.使用:
消息队列的相关API
1.创建消息队列
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
2.删除消息队列
(1)函数原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
(2)参数解析:
msgid:表示消息队列的id
cmd:表示对该消息队列执行相关的命令,当为IPC_RMID时表示按照id删除该消息队列
buf:可以为NULL
3.向消息队列里面发送数据
(1)函数原型:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
(2)参数解析:
msgp:表示一个指针,该指针指向往消息队列发送的消息
消息的结构参考如下:
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; //可以自定义大小
};
msgz:表示消息的大小(消息结构msgbuf里面mtext的大小)
msgflag:当前可以填0
4.从消息队列里面接收数据
(1)函数原型:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
(2)参数解析:
msgp:仍然是指向消息的指针
msgz:表示消息的大小(与msgsnd的参数意义及用法一致)
msgtyp:表示消息的类型(必须是大于0的整数)
利用消息队列模拟简单的客户端和服务器的通信
1.功能:客户端从标准输入读取数据发送至服务器,服务器将接收到的消息回显,(服务器回相同的数据)
2.代码:
将客户端和服务器公共的代码提取出来封装至comm.c里面,再在客户端和服务器中实现各自独立的部分,就可以使代码得到复用。
comm.h
#ifndef __COMM_H__
#define __COMM_H__
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/msg.h>
#define PATHNAME "." //.表示当前目录
#define PROJ_ID 1 //当前项目编号为1
#define ClientType 1
#define ServerType 2 //服务器的类型
typedef struct msgbuf
{
long mtype; /* message type, must be > 0*/
char mtext[1024]; /* message data */
}msgbuf;
int CommMsgQueue(int flags);
int CreatMsgQueue(); //创建消息队列
int GetMsgQueue(); //拿到消息队列
int DestroyMsgQueue(int msgid);
int RecvMsg(); //从消息队列中接收数据
int SndMsg(); //往消息队列中发送数据
#endif
comm.c
#include"comm.h"
int CommMsgQueue(int flags)
{
//创建一个key
key_t key;
key = ftok(PATHNAME,PROJ_ID);
if(key < 0)
{
perror("ftok");
return -1;
}
//通过msgget创建或获取消息队列
int msgid = msgget(key,flags);
if(msgid < 0)
{
perror("msgget");
return -1;
}
return msgid;
}
//创建消息队列,如果存在就出错,如果不存在就创建
int CreatMsgQueue()
{
return CommMsgQueue(IPC_CREAT | IPC_EXCL | 0666);
}
//获得消息队列
int GetMsgQueue()
{
return CommMsgQueue(IPC_CREAT);
}
//删除指定的msgid的消息队列
int DestroyMsgQueue(int msgid)
{
int ret = msgctl(msgid,IPC_RMID,NULL);
if(ret < 0)
{
perror("msgctl");
return -1;
}
}
return 0; //销毁成功返回0
}
//往指定的消息队列中发送数据,type为消息的类型,size为消息的大小
int SndMsg(int msgid,int type, const char* msg)
{
msgbuf mbuf;
mbuf.mtype = type; //消息的类型
//将msg的内容拷贝至mbuf
strcpy(mbuf.mtext,msg);
int ret = msgsnd(msgid, &mbuf,sizeof(mbuf.mtext), 0);
if(ret < 0)
{
perror("msgsnd");
return -1;
}
return 0;
}
//从消息队列中接收数据,将接收到的数据放至output数组里面
int RecvMsg(int msgid,int recvtype,char* output)
{
msgbuf mbuf;
ssize_t ret = msgrcv(msgid, &mbuf, sizeof(mbuf.mtext), recvtype, 0);
int output_size = 0;
if(ret < 0)
{
perror("msgrcv");
return -1;
}
else if(ret == 0)
{
printf("read down!!!\n");
return 0;
}
else
{
//将mbuf里面的数据拷贝至output数组里面
strcpy(output, mbuf.mtext);
}
return 0;
}
server.c
#include"comm.h"
int main()
{
//服务器创建消息队列
int msgid = CreatMsgQueue();
if(msgid < 0)
{
perror("CreatMsgQueue");
return -1;
}
while(1)
{
//从消息队列里面接收客户端发来的消息
char buf[1024]={0};
//服务器接收客户端发来数据的类型
RecvMsg(msgid,ClientType,buf);
printf("client say#%s\n",buf);
//再将从客户端接收到的数据发送至服务器
SndMsg(msgid,ServerType,buf);
}
DestroyMsgQueue(msgid);
return 0;
}
client.c
#include"comm.h"
int main()
{
//客户端拿到消息队列
int msgid = GetMsgQueue();
if(msgid < 0)
{
perror("GetMsgQueue");
return -1;
}
while(1)
{
//buf用于存放从标准输入读到的内容
char buf[1024] = {0};
printf(">");
fflush(stdout);
scanf("%s",buf);
//客户端通过消息队列向服务器发送消息
SndMsg(msgid,ClientType,buf);
//从消息队列里面接收服务器回显的数据(回显就是我发什么就显示什么)
char buf2[1024] = {0}; //buf2用于存放从消息队列里面接收到的数据
RecvMsg(msgid,ServerType,buf2);
printf("%s\n",buf);
fflush(stdout);
SndMsg(msgid,ServerType,buf);
}
return 0;
}