消息队列的基本原理
1.1什么是消息队列
和共享内存、管道一样都是数据的中转站,只是中转的方式不同。比较起来消息队列和有名管道很相似,文末会介绍消息队列与有名管道的区别。
不同的是消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
1.2从快递派送的角度理解消息队列的作用
1.2.1 解耦(这个词听起来很高大上,其实都是唬人的)
快递小哥手上有很多快递需要送,他每次都需要先电话一一确认收货人是否有空、哪个时间段有空,然后再确定好送货的方案。这样完全依赖收货人了!如果快递一多,快递小哥估计的忙疯了……如果有了便利店,快递小哥只需要将同一个小区的快递放在同一个便利店,然后通知收货人来取货就可以了,这时候快递小哥和收货人就实现了解耦!
1.2.2 异步(这个词也是唬人的,与同步相对,就是进程或线程不按顺序执行程序,各忙各的)
快递小哥打电话给我后需要一直在你楼下等着,直到我拿走你的快递他才能去送其他人的。快递小哥将快递放在小芳便利店后,又可以干其他的活儿去了,不需要等待你到来而一直处于等待状态。提高了工作的效率。
1.2.3 削峰(这个词也是唬人的)
假设双十一我买了不同店里的各种商品,而恰巧这些店发货的快递都不一样,有中通、圆通、申通、各种通等……更巧的是他们都同时到货了!中通的小哥打来电话叫我去北门取快递、圆通小哥叫我去南门、申通小哥叫我去东门。我一时手忙脚乱……我们能看到在系统需要交互的场景中,使用消息队列中间件真的是好处多多,基于这种思路,就有了丰巢、菜鸟驿站等比便利店更专业的“中间件”了。
详细介绍可以看这篇文章:带你去流浪的博客 (cnblogs.com)
消息队列的使用
用法和共享内存和信号量很相似,一般只需要理解四个函数就够了。
2.1消息队列创建函数 int msgget(key_t key,int msgflg)
与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。所以它成功申请后会返回以key命名的消息队列的标识符,失败时返回-1。
msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。
2.2消息发送函数 int msgsnd(int msgid,const void* psg_ptr,size_t msg_sz, int msgflg)
msgid是由msgget函数返回的消息队列标识符。
msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样:
struct my_message {
long int message_type;
/*这里要要注意,我们通过设定这个变量,之后我们接受消息的时候可以用这个变量来辨识那个消息是
我们想要的,这样消息队列就可以实现不同数据存到消息队列中,但是每个线程或者进程都能准确获取自己
想要的数据(就像我们去菜鸟驿站时的取件码)*/
char text[SIZE];
/* The data you wish to transfer */
};
msg_sz 是msg_ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员long int message_type变量的长度。所以这里填size的时候要sizeof(my_message)-sizeof(long int)。
msgflg 用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。
如果调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1。
2.3消息获取函数 int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg)
参数msgid, msg_ptr, msg_st 的作用也函数msgsnd()函数的一样。
重点理解:
msgtype 可以实现一种简单的接收优先级。
如果msgtype为0,就获取队列中的第一个消息。
如果它的值大于零或是其他值(菜鸟驿站的取件码),将获取具有相同消息类型的第一个信息。
如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。
2.4消息队列控制函数 int msgctl(int msgid, int command, struct msgid_ds *buf)
command是将要采取的动作,它可以取3个值,
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列(这个函数主要就是用这个参数)
buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:
struct msgid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
成功时返回0,失败时返回-1.
详细介绍可以看这篇文章:Linux进程间通信(七):消息队列 msgget()、msgsend()、msgrcv()、msgctl() - 52php - 博客园 (cnblogs.com)
3.消息队列相对于有名管道的优势
3.1简单介绍一下有名管道
管道通信方式的中间介质是文件,通常称这种文件为管道文件。两个进程利用管道文件进行通信时,一个进程为写进程,另一个进程为读进程。写进程通过写端(发送端)往管道文件中写入信息;读进程通过读端(接收端)从管道文件中读取信息。两个进程协调不断地进行写、读,便会构成双方通过管道传递信息的流水线。
3.1.1管道分为无名管道(又称为匿名管道,PIPE)和有名管道(又称为命名管道,FIFO)。
(1)匿名管道:管道是半双工的,数据只能单向通信;需要双方通信时,需要建立起两个管道;只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。
(2)命名管道:可在同一台计算机的不同进程之间或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。
3.1.2二者区别
FIFO不同于PIPE之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。
值得注意的是,FIFO严格遵循先进先出,对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。
PIPE是一种非永久性的管道通信机构,当它访问的进程全部终止时,它也将随之被撤消;
而FIFO是一种永久的管道通信机构,它可以弥补PIPE的不足。
3.2有名管道的弊端
虽然有名管道相较于无名管道,已经实现不同进程和线程之间的通信,但是还是存在的缺陷。
3.2.1有名管道读写需要同步和阻塞问题。
1.写进程阻塞,读进程阻塞。
先运行写进程(被阻塞),再运行读进程,一切正常。
先运行读进程(被阻塞),再运行写进程,一切正常。
2.
写进程阻塞,读进程非阻塞。
就改一句代码 fd=open(FIFO_NAME,O_RDONLY | O_NONBLOCK),下面类似。
先运行写进程(被阻塞),再运行读进程,一切正常。
先运行读进程,程序直接崩掉(Segmentation fault (core dumped)),想想也挺自然的,没东西你还要读,而且不愿等。。。
3.
写进程非阻塞,读进程阻塞。
先运行写进程,open调用将返回-1,打开失败。
先运行读进程(被阻塞),再运行写进程,一切正常。
4.
写进程非阻塞,读进程非阻塞。
先运行读进程,程序直接崩掉(Segmentation fault (core dumped)),想想也挺自然的,没东西你还要读,而且不愿等。。。
先运行写进程,open调用将返回-1,打开失败。
其实就是上面2,3类各取一半不正常的情况。。
所以进程要实现通信必须要同步,那样消息队列就起不到菜鸟驿站或者丰巢的功能了,快递员又要回归到等我们来取完快递后才能派发下一个快递件。效率就会变低。
3.2.2命名管道只能接收一种消息类型,只能对接一个消费者。
这里就是因为由于在消息队列发送函数中有long int message_type这个成员的存在 :
struct my_message {
long int message_type;
/*这里要要注意,我们通过设定这个变量,之后我们接受消息的时候可以用这个变量来辨识那个消息是
我们想要的,这样消息队列就可以实现不同数据存到消息队列中,但是每个线程或者进程都能准确获取自己
想要的数据(就像我们去菜鸟驿站时的取件码)*/
char text[SIZE];
/* The data you wish to transfer */
};
我们就可以对接不同的消费者,各个消费者各取所需,同时也可以对接多个生产者。
详细介绍可以看这篇文章:Linux有名管道(FIFO)的阻塞和非阻塞读写_xtrb的博客-CSDN博客