Linux | 进程间通信之消息队列

目录

消息队列的基本原理

​编辑

消息队列数据结构

消息队列的创建

消息队列的发送

消息队列的接收

消息队列的释放

用消息队列实现client&server通信

总结

消息队列的优点:

消息队列的缺点:


消息队列的基本原理

消息队列是一种在分布式系统中用于异步通信的机制。它允许不同的应用程序、服务或组件之间通过发送和接收消息来实现解耦和异步通信

消息队列的基本原理是发送者将消息发送到队列中,而接收者从队列中获取消息并进行处理。消息队列中的消息通常按照先进先出(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_permmsg_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_CREATIPC_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相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值