【技术篇】linux进程间通讯--消息队列

(一)什么是消息队列

       消息队列提供了一个进程给另外一个进程发送数据块的方法,每个数据块都可以被认为是由自己的类型的,接受者接受的数据块可以有不同的类型;不像管道和命名管道那样,必须以先进先出的方式接收数据。

       同管道类似的是,他有一个不足的地方就是每个消息的最大长度是有上限的(MSGMAX),每个仙子队列的总的字节数(MSGMNB),系统上消息队列的总数上限(MSGMNI),可以用cat/proc/sys/kernel/msgmax查看具体的数据。

(二)消息队列的使用

        linux消息队列的API都定义在sys/msg.h头文件中,其中包括四个系统调用:msgget、msgsnd、msgrcv、msgctl;

    2.1msgget系统调用

         用来创建一个消息队列,或者获取一个已存在的消息队列,定义如下:

#include <sys/msg.h>
int msgget(key_t, key, int msgflg);

【参数 key】:键值,用来标识一个全局唯一的消息队列。

【参数 msgflg】:一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存时,IPC_CREAT标志会被忽略,而只返回一个标识符。

【函数返回值】成功返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1.

        在shmget用于创建内存,与之关联的内核数据结构shmid_ds将被创建并初始化,shmid_ds结构体的定义和初始化参照《linux高性能服务器编程》263页了解。

    2.2msgsnd系统调用

        用来将一条信息添加到消息队列中,定义如下:

#include <sys/msg.h>
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);

【参数 msgid】:是由msgget系统调用返回的消息队列标识符。

【参数 msg_ptr】:指向一个准备发送消息的指针,消息必须被定义为如下:

    struct my_message
    {
        long int message_type;  /* 消息类型,必须是一个正整数 */
        char mtext[512]; /* 消息数据 */       
    };

【参数 msg_sz】:是指消息的数据部分(mtext)的长度,这个长度可以为0,代表没有数据。

【参数 msgflg】:控制msgsnd的行为。它通常仅支持IPC_NOWAIT标志,即以非阻塞的方式发送消息。默认情况下,发送消息时如果消息队列满了,则msgsnd将会阻塞。若IPC_NOWAIT标志被指定,则msgsnd将会立即返回并设置werrno为EAGAIN.具体情况参照《linux高性能服务器编程》264页。

【函数返回值】:如果调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1并设置errno.成功后回修改内核数据结构msqid_ds的部分字段。

    2.3msgrcv系统调用

      用来从消息队列中获取消息,定义如下:

#include <sys/msg.h>
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);

【参数 msgtype】:指定接受哪一种类型的消息。可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。

【参数 msgflg】:用于控制当队列中没有相应类型的消息可以接收时将发生的事情。

【函数返回值】:调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1.

 参数msgid, msg_ptr, msg_st的作用和函数msgsnd的参数意义相同一。

   2.4msgctl系统调用

     用来控制消息队列的某些属性,定义如下:

#include <sys/msg.h>
int msgctl(int msgid, int command, struct msgid_ds *buf);

【参数 id】:同上。

【参数 command】:command是将要采取的动作,它可以取6个值,这里列举常用的3个:
    (1)IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
    (2)IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
    (3)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.

(三)代码演示

      消息队列可以让不相关的进程进行行通信,所以我们在这里将会编写两个程序,msgreceive和msgsned来表示接收和发送信息。根据正常的情况,我们允许两个程序都可以创建消息,但只有接收者在接收完最后一个消息之后,它才把它删除。

接受数据源代码:recv.c

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
 
struct msg_st
{
	long int msg_type;
	char text[BUFSIZ];
};
 
int main()
{
	int running = 1;
	int msgid = -1;
	struct msg_st data;
	long int msgtype = 0; //注意1
 
	//建立消息队列
	msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
	if(msgid == -1)
	{
		fprintf(stderr, "msgget failed with error: %d\n", errno);
		exit(EXIT_FAILURE);
	}
	//从队列中获取消息,直到遇到end消息为止
	while(running)
	{
		if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)
		{
			fprintf(stderr, "msgrcv failed with errno: %d\n", errno);
			exit(EXIT_FAILURE);
		}
		printf("You wrote: %s\n",data.text);
		//遇到end结束
		if(strncmp(data.text, "end", 3) == 0)
			running = 0;
	}
	//删除消息队列
	if(msgctl(msgid, IPC_RMID, 0) == -1)
	{
		fprintf(stderr, "msgctl(IPC_RMID) failed\n");
		exit(EXIT_FAILURE);
	}
	exit(EXIT_SUCCESS);
}

发送数据源代码:write.c

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>
 
#define MAX_TEXT 512
struct msg_st
{
	long int msg_type;
	char text[MAX_TEXT];
};
 
int main()
{
	int running = 1;
	struct msg_st data;
	char buffer[BUFSIZ];
	int msgid = -1;
 
	//建立消息队列
	msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
	if(msgid == -1)
	{
		fprintf(stderr, "msgget failed with error: %d\n", errno);
		exit(EXIT_FAILURE);
	}
 
	//向消息队列中写消息,直到写入end
	while(running)
	{
		//输入数据
		printf("Enter some text: ");
		fgets(buffer, BUFSIZ, stdin);
		data.msg_type = 1;    //注意2
		strcpy(data.text, buffer);
		//向队列发送数据
		if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)
		{
			fprintf(stderr, "msgsnd failed\n");
			exit(EXIT_FAILURE);
		}
		//输入end结束输入
		if(strncmp(buffer, "end", 3) == 0)
			running = 0;
		sleep(1);
	}
	exit(EXIT_SUCCESS);
}

(四)消息队列与命名管道的比较

       消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制。

      与命名管道相比,消息队列的优势在于:

     1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。

     2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。

     3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

参考文章:

https://blog.csdn.net/wei_cheng18/article/details/79661495

https://blog.csdn.net/ljianhui/article/details/10287879

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章