Linux 进程间通信基础(六)--消息队列

近正好有一些空余时间,在这里总结一下曾经使用过的Linux进程间通信的几种方法,贴出来帮助有需要的人,也有助于自己总结经验加深理解。上一次我们梳理了共享内存的相关知识,这一次梳理消息队列。

(一)概念

在介绍消息队列之前,我们先来简单说一说System V IPC通信机制。

System V IPC机制最初是由AT&T System V.2版本的UNIX引入的。这些机制是专门用于IPC(Inter-Process Communication 进程间通信)的,它们在同一个版本中被应用,又有着相似的编程接口,所以它们通常被称为System V IPC通信机制。

消息队列是最后一个System V IPC机制。消息队列的本质也是在不同的线程之间共享的一段内存。只是它与共享内存不同,程序不能够直接通过API读取或者写入这段共享内存。而它有着自己的特殊机制,就像一段队列,发送者可以向消息队列中加入一段消息,而负责接收的程序责可以从队列中读取一则消息。先被发送的消息将先被读取,也不用程序开发者去考虑同步相关的事情。系统会帮你确保它。

                                        Linux 进程间通信基础(六)--消息队列_第1张图片

(二)消息队列的创建,发送,接收和销毁

同样于共享内存,System V提供了一套极其类似的API供开发者们操作消息队列。虽然API非常类似,但是它内部的机制则差异很大,这使得消息队列和共享内存的应用场景也有很大不同。我们先来介绍一下操作消息队列的主要API。

我们可以使用msgget()函数来创建,或者是得到一个消息队列:

int msgget( key_t key, int msgflg );

其中,

--key:是消息的列的名字,也是系统内消息队列的唯一标识。访问同一个消息队列的不同进程需要使用同一个名字。

--msgflg:消息队列的标志,包含9个比特标志位,其内容与创建文件时的mode相同。有一个特殊的标志IPC_CREAT可以和权限标志以或的形式传入。

成功是返回一个整数标识消息队列的句柄,失败是返回-1。

我们可以使用msgsnd()函数来向一个消息队列发送信息:

int msgsnd( int msgid, const void* msg_ptr, size_t msg_sz, int msgflt );

其中,

--msgid:是线程内消息队列的标识,是msgget()的返回值。

--msg_ptr:是消息结构的指针,它可以是用户自定义的结构体,不过它必须以长整数开头(即结构提的第一个变量必须是长整数),这个长整数表示消息的类型,这个长整数很重要,因为消息接收函数会根据它对消息进行筛选。

--msg_zs:是消息体的长度,注意它是不包括消息类型这个长整数的,是除其意外的大小。

--msgflt:控制消息队列的标志,如果在标志中设置了IPC_NOWAIT,在出现诸如消息队列满了等情况时,函数会立刻返回-1,如果不设置的话函数会挂起一段时间。

我们使用msgrcv()函数来从消息队列中读取一个消息:

int msgrcv( int msgid, void* msg_ptr, size_t msg_sz, long int msgtype, int msgflg );

其中,

--msgid:是线程内消息队列的标识,是msgget()的返回值。

--msg_ptr:是消息结构的指针,它与msgsnd中的是一样的。

--msg_zs:是消息体的长度,注意它是不包括消息类型这个长整数的,是除其意外的大小。

--msgtype:消息类型,就是消息开头的那一段长整数,设置为0则会从消息队列中读取第一条消息,设置为>0的值会读取该类型的第一条消息。如果设置为<0的值,将会读取小于该值绝对值的所有类型的第一条消息。

--msgflt:控制消息队列的标志,如果在标志中设置了IPC_NOWAIT,在出现诸如消息队列满了等情况时,函数会立刻返回-1,如果不设置的话函数会挂起一段时间。

我们使用msgctg()函数来控制消息队列,这一点与共享内存非常相似:

int msgctl( int msgid, int command, struct msqid_ds* buf );

其中,

--msg_id:是消息队列的标示符,也即是msgget()的返回值。

--command:是要采取的动作,它有三个有效值,如下所示:

说明
IPC_STAT 把buf结构中的值设置为共享内存的关联值
IPC_SET                                 如果拥有足够的权限,将共享内存的值设置为buf中的值
IPC_RMID 删除共享内存段

--buf:是一个msgid_ds结构的指针它可以设置消息队列的关联值。msgid_ds的结构如下所示

struct msgid_ds {
    uid_t msg_perm.uid;
    uid_t msg_perm.gid;
    mode_t msg_perm.mode;
}

(三)使用消息队列通信的例子

现在我们就来尝试写两个简单例子来使用以下消息队列,我们首先要写一个头文件,定义我们的消息格式。因为这是个非常简单的例子,所以我们的消息结构里只用一个长整型的消息类型(必须),两外再加上一段字符串,它表示具体消息内容。我们把它保存为msgdef.h:

#ifndef _MSGDEF_H_
#define _MSGDEF_H_

#define MSG_FD          1111    /* message id*/
#define MSG_TEXT_SIZE   24      /* message text max size definition */

/* Structure of general message */
typedef struct message_body
{
    long int    msg_type;                   /* message type */
    char        msg_text[MSG_TEXT_SIZE];    /* message text */
}MSG_BODY;

#endif /* _MSGDEF_H_ */

现在我们来完成消息接收者,它将从消息队列读取一个消息并且打印到屏幕上,创建一个新文件msgrecv.c:

#include 
#include 
#include 
#include 

#include "msgdef.h"

int main()
{
    int result;
    int msgfd;
    MSG_BODY message;

    /* Create a message list. */
    msgfd = msgget( MSG_FD, IPC_CREAT | 0666 );
    if( msgfd < 0 )
    {
        printf( "error:can't apply a message list.\n" );
        return 1;
    }

    do
    {
        /* Get the first message from the message list and print it's text. */
        msgrcv( msgfd, &message, MSG_TEXT_SIZE, 0, 0 );
        printf( "receive: %s\n", message.msg_text );
    }
    while( memcmp( message.msg_text, "close", 5 ) != 0 );

    /* Close the message list. */
    msgctl( msgfd, IPC_RMID, 0 );

    return 0;
}

然后我们来完成消息的发送者,它会读取用户的输入并发送到消息队列。我们把它命名为msgsend.c:

#include 
#include 
#include 
#include 

#include "msgdef.h"

int main()
{
    int result;
    int msgfd;
    MSG_BODY message;

    /* Create a message list. */
    msgfd = msgget( MSG_FD, IPC_CREAT | 0666 );
    if( msgfd < 0 )
    {
        printf( "error:can't apply a message list.\n" );
        return 1;
    }

    
    do
    {
        /* Send the input of users to receiver. */
        printf( "Please input something:" );
        scanf( "%s", message.msg_text );
        msgsnd( msgfd, &message, MSG_TEXT_SIZE, 0 );
    }
    while( memcmp( message.msg_text, "close", 5 ) != 0 );
    
    return 0;
}

现在我们来编译一下这两个程序:

root@workspace-server:/home/root/workspace/msgq/system-v# gcc msgrecv.c -o msgrecv
root@workspace-server:/home/root/workspace/msgq/system-v# gcc msgsend.c -o msgsend

尝试启动接收者:

root@workspace-server:/home/root/workspace/msgq/system-v# ./msgrecv 

现在我们另外启动一个终端,尝试启动发送者,并输入一下东西:

root@workspace-server:/home/root/workspace/msgq/linux# ./msgsend 
Please input something:abc
Please input something:hellow
Please input something:fuck
Please input something:ono
Please input something:close
root@workspace-server:/home/root/workspace/msgq/linux# 

然后我们看一下接收者那边的情况:

root@workspace-server:/home/root/workspace/msgq/system-v# ./msgrecv 
receive: abc
receive: hellow
receive: fuck
receive: ono
receive: close
root@workspace-server:/home/root/workspace/msgq/system-v# 

可以看到输入的内容被通过消息队列成功的传递了。

你可能感兴趣的:(Linux程序设计)