目录
消息队列的基本原理
消息队列是一种在分布式系统中用于异步通信的机制。它允许不同的应用程序、服务或组件之间通过发送和接收消息来实现解耦和异步通信。
消息队列的基本原理是发送者将消息发送到队列中,而接收者从队列中获取消息并进行处理。消息队列中的消息通常按照先进先出(FIFO)的顺序进行处理,确保消息的顺序性。
通常情况下多个进程可同时向一个消息队列发送消息,也可以同时从一个消息队列中接收消息。发送进程把消息发送到队列尾部,接受进程从消息队列头部读取消息,消息一旦被读出就从队列中删除。
消息队列数据结构
系统当中可能会存在大量的消息队列,为了维护相关的消息队列,操作系统应该有对应的内核数据结构进行管理,消息队列的数据结构如下:
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 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 */
};
可以看到上面消息队列数据结构的第一个成员是msg_perm,msg_perm是一个ipc_perm类型的结构体变量,每个消息队列的key值存储在msg_perm这个结构体变量当中,其中ipc_perm结构体的定义如下:
struct ipc_perm{
__kernel_key_t key; //消息队列的唯一标识符
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode; //权限
unsigned short seq;
};
消息队列的创建
创建消息队列我们需要用msgget函数,msgget函数的函数原型如下:
int msgget(key_t key, int msgflg);
//参数:
key:表示待创建的消息队列在内存中的唯一标识符
msgflg:表示创建消息队列的创建方式
//返回值:
创建成功返回消息队列的标识号,失败返回 -1
传入msgget函数的第一个参数key,需要我们使用ftok函数进行获取
ftok函数的函数原型如下:
key_t ftok(const char *pathname, int proj_id);
//参数:
pathname:代表一个存在的文件路径名
proj_id:用于区分不同的共享内存、消息队列或信号量的一个整数,通常设置为非0值
//返回值:
ftok 函数根据 pathname 和 proj_id 生成一个唯一的 key,并返回这个 key
这个返回的key值可以传给消息队列参数,作为struct ipc_perm中唯一标识消息队列的key
值得注意的是:
- 需要进行通信的各个进程,在使用ftok函数获取key值时,都需要采用同样的路径名和和整数标识符,进而生成同一种key值,然后才能找到同一个共享资源。
传入msgget函数的第二个参数msgflg
常用两个IPC_CREAT和IPC_EXCL,一般后面还加一个权限,相当于文件的权限
- IPC_CREAT:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列并返回该消息队列的句柄;如果存在这样的消息队列,则直接返回该消息队列的句柄
- IPC_CREAT | IPC_EXCL:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列并返回该消息队列的句柄;如果存在这样的消息队列,则出错返回
使用:IPC_CREAT | IPC_EXCL | 0666
例如:
int msgid;
//创建关键字为0x1234,访问权限为0666的消息队列,如队列已存在则报错
msgid = msgget(0x1234, IPC_CREAT|IPC_EXCL|0666);
消息队列的发送
释放消息队列我们需要用msgsnd函数,msgsnd函数的函数原型如下:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//参数:
msgid:指定发送消息队列的标识号
msgp:指向存储待发送消息内容的内存地址,用户可设计自己的消息结构
msgsz:指定长度,仅记载数据的长度
msgflg:控制消息发送的方式
传入msgsnd函数的第一个参数msqid,需要我们使用msget函数进行获取
int msgid;
msgid = msgget(KEY, IPC_CREAT|0666);
传入msgsnd函数的第二个参数msgp,需要我们自定义消息结构
消息队列中消息本身由 消息类型 和 消息数据 组成,通常使用如下结构:
struct msgbuf
{
long mtype;
char mtext[1];
}
- mtype:消息类型,为正整数。
- mtext:消息的数据,我们可以定义任意的数据类型甚至包括结构来描述消息数据。
例如定义一个消息结构,它的消息数据是由一个字符数组和一个整型数据组成:
struct msgtext
{
char ctext[24];
int ntext;
}
struct msgbuf
{
long mtype;
struct msgtext stext;
};
传入msgsnd函数的第三个参数msgsz,一般使用strlen函数来计算
msgsz是指仅记载数据的长度,不包括消息类型部分,且必须大于0
传入msgsnd函数的第四个参数msgflg,一般采用 0
常见的参数有:
- IPC_NOWAIT:如果消息队列已满,则不等待,立即返回错误。
- IPC_EXCEPT:如果消息队列中已经存在与发送消息类型相同的消息,则将其删除并发送新的消息。
- MSG_NOERROR:如果消息长度超过了消息队列的最大限制,将截断消息而不返回错误。
- MSG_COPY:将消息复制到内核空间,而不是直接引用用户空间的消息缓冲区。
至此,我们就可以完成消息队列的发送,具体的样例代码如下:
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/errno.h>
#include <sys/types.h>
struct my_msgbuf
{
long mtype;
char ctext[100];
};
int main()
{
//创建消息队列
int msgid;
msgid = msgget(0x1234, IPC_CREAT|0666);
if(msgid < 0)
{
return -1; //创建失败
}
//创建消息
struct msgbuf buf;
buf.mtype = 100;
strcpy(buf.ctext, “Hello Linux!”);
//发送消息到消息队列
int ret;
ret = msgsnd(msgid, &buf, strlen(buf.ctext), 0);
//发送判断
if(ret == -1)
{
return -1; //发送失败
}
return 0;
}
消息队列的接收
从消息队列获取数据我们需要用msgrcv函数,msgrcv函数的函数原型如下:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
//参数:
msqid:消息队列的标识符
msgp:指向接收消息的缓冲区的指针
msgsz:接收消息缓冲区的大小
msgtyp:指定要接收的消息类型
msgflg:控制消息发送的方式
//返回值:
调用成功,返回实际获取到mtext数组中的字节数,失败返回 -1
msgrcv函数的前三个参数与msgsnd相同
msgrcv函数的第四个参数msgtyp
常见的参数有:
- 0:接收队列中的第一条消息。
- 正整数:接收队列中类型等于指定值的第一条消息。
- 负整数:接收队列中类型小于或等于指定值的绝对值的第一条消息。
msgrcv函数的第五个参数msgflg有些许差别:
常见的参数有:
- IPC_NOWAIT:如果消息队列中没有与指定类型相匹配的消息,则立即返回错误,而不等待。
- MSG_EXCEPT:接收消息队列中与指定类型不匹配的第一个消息,并将其从队列中删除。
- MSG_NOERROR:如果接收到的消息长度超过了接收缓冲区的大小,则将其截断而不返回错误。
- MSG_COPY:将消息从内核空间复制到用户空间的消息缓冲区,而不是直接引用内核空间的消息。
至此,我们就可以完成消息的接收,具体的样例代码如下:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/errno.h>
#include <sys/types.h>
struct my_msgbuf
{
long mtype;
char ctext[100];
};
int main()
{
//创建(匹配)消息队列
int msgid;
msgid = msgget(0x1234, IPC_CREAT|0666);
if(msgid < 0)
{
return -1; //创建(匹配)失败
}
//接收消息队列
int ret;
ret = msgrcv(msgid, &buf, sizeof(buf.ctext), buf.mtype, 0);
if(ret < 0)
{
return -1; //接收失败
}
//打印消息
fprintf(stdout,"Msg:Type=%d,Len=%d,Text:%s",buf.mtype,ret, buf.ctext);
return 0;
}
消息队列的释放
消息队列是保存在内核中的消息链表,所以消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在
我们可以通过 ipcs -q 命令来查看当前存在的消息队列
释放消息队列我们需要用msgctl函数,msgctl函数的函数原型如下:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//参数:
msqid:消息队列的标识符
cmd:控制命令,用于指定要执行的操作
buf:指向msqid_ds结构体的指针,用于存储消息队列的状态信息。
msgctl函数数根据cmd参数执行不同的操作。
常见的命令:
IPC_STAT
:获取消息队列的状态信息,并将其存储在buf
指向的结构体中。IPC_SET
:设置消息队列的状态信息,使用buf
指向的结构体中的值进行设置。IPC_RMID
:删除消息队列。
我们使用 IPC_RMID 命令来删除(释放)消息队列,如下:
msgctl(msqid, IPC_RMID, NULL);
至此,我们就可以完成对消息队列的删除。
用消息队列实现client&server通信
在知道了消息队列的创建、发送、接收以及释放后,现在可以尝试让两个进程通过消息队列进行通信了。
客户端将消息放入消息队列中,服务端从消息队列中读取消息,读取完成后释放消息队列。
客户端:
//client.c
#include "comm.h"
int main()
{
//创建消息队列
int msgid;
msgid = msgget(0x1234, IPC_CREAT|0666);
if(msgid < 0)
{
return -1; //创建失败
}
//创建消息
struct my_msgbuf buf;
buf.mtype = 100;
strcpy(buf.ctext, "Hello server,I am client!");
//发送消息到消息队列
int ret;
ret = msgsnd(msgid, &buf, strlen(buf.ctext), 0);
//发送判断
if(ret == -1)
{
return -1; //发送失败
}
return 0;
}
服务端:
//server.c
#include "comm.h"
int main()
{
//创建(匹配)消息队列
int msgid;
msgid = msgget(0x1234, IPC_CREAT|0666);
if(msgid < 0)
{
return -1; //创建(匹配)失败
}
//匹配消息
struct my_msgbuf buf;
//接收消息队列
int ret;
ret = msgrcv(msgid, &buf, sizeof(buf.ctext), 100, 0);
if(ret < 0)
{
return -1; //接收失败
}
//打印消息
fprintf(stdout,"Msg:Type=%d,Len=%d,Text:%s \n",buf.mtype,ret, buf.ctext);
//释放消息队列
if (msgctl(msqid, IPC_RMID, NULL) == -1)
{
return -1; //释放失败
}
return 0;
}
共用头文件:
//comm.h
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/errno.h>
#include <sys/types.h>
struct my_msgbuf
{
long mtype;
char ctext[100];
};
运行结果:
总结
消息队列的优点:
- 异步通信:发送和接收操作可以在不同的时间进行,不需要双方同时在线,提供了更大的灵活性。
- 可靠性:消息队列提供了可靠的消息传递机制,确保消息不会丢失。
- 容量灵活:消息队列可以存储不同大小的消息,适用于传递大量数据的场景。
- 优先级支持:消息队列可以按照优先级进行消息的读写操作,确保重要消息能够及时处理。
消息队列的缺点:
- 有限的容量:消息队列的容量是有限的,当消息队列已满时,发送操作可能会阻塞或失败。
- 实时性差:由于消息队列是基于内核的机制,消息的传递可能会受到内核调度的影响,导致实时性较差。
- 复杂性:相对于其他进程间通信方式(如管道、共享内存),使用消息队列需要更多的代码和操作,增加了开发和维护的复杂性。
综上所述,Linux消息队列适用于需要在不同进程之间传递大量数据的场景,提供了可靠的异步通信机制。然而,对于实时性要求较高的场景或者需要简单易用的通信方式,可能需要考虑其他进程间通信方式。以上就是本文的全部内容了,本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!