linux进程间通信--POSIX消息队列

Linux下进程间通信方式介绍

管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
有名管道 (named pipe): 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

信号 ( sinal ): 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

共享内存( shared memory ):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

套接字( socket ) :socket既可以用于同设备进程间通信,也可用于不同设备进程间通信。

消息队列与管道、FIFO的区别:

Ø 一个进程向消息队列写入消息之前,并不需要某个进程在该队列上等待该消息的到达,而管道和FIFO是相反的,进程向其中写消息时,管道和FIFO必须已经打开来读,否则写进程就会阻塞(默认情况下)。

Ø IPC的持续性不同。管道和FIFO是随进程的持续性,当管道和FIFO最后一次关闭发生时,仍在管道和FIFO中的数据会被丢弃。消息队列是随内核的持续性,即一个进程向消息队列写入消息后,然后终止,另外一个进程可以在以后某个时刻打开该队列读取消息。只要内核没有重新自举,消息队列没有被删除。

消息队列中的每条消息通常具有以下属性
Ø 一个表示优先级的整数;
Ø 消息的数据部分的长度;
Ø 消息数据本身;
linux进程间通信--POSIX消息队列_第1张图片

Posix消息队列和System V 消息队列的区别:

对Posix消息队列的读取总是返回最高优先级的最早消息; 对System V消息队列的读则可以返回任意指定优先级的消息。
当往一个空队列放置一个消息时, Posix消息队列允许产生一个信号或者启动一个线程; System V则不提供类似机制。

posix消息队列接口介绍

1、mq_open来创建非默认个数大小消息队列:

函数原型

mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);

     struct mq_attr{ 
       long mq_flags;
       long mq_maxmsg;
       long mq_msgsize;
       long mq_curmsgs;
    };

当第四个参数为空指针时,就使用默认属性。
当指向mq_attr结构的指针作为参数时,允许我们在该函数的实际操作时创建一个新队列时,给它指定mq_maxmsg和mq_msgsize属性. mq_open忽略该结构的另外两个成员.
(1)attr.mq_maxmsg 不能超过文件 /proc/sys/fs/mqueue/msg_max 中的数值;
(2)attr.mq_msgsize不能超过 /proc/sys/fs/mqueue/msgsize_max 的数值;
(3)消息队列名称前面必须加上斜杆。
在POSIX消息队列中 msg_max 默认为 10 ,msgsize_max 默认为8192 ,否则会报错!!!
查看当前配置:

/proc/sys/fs/mqueue# cat msg_max   
/proc/sys/fs/mqueue# cat msgsize_max

修改的话,要使用:echo 1000 > /proc/sys/fs/mqueue/msg_max

2、获取消息队列的属性

一个进程在发送和接收消息之前,需要了解消息对象的属性,如消息的最大长度。以便
设定接收和发送的buffer大小。

mqd_t mq_getattr(mqd_t mqdes, struct mq_attr *attr);

参数:
Mqdes:打开消息队列时获取的描述符。
Attr:指向结构struct mq_attr的指针,用来获取消息队列的四个属性

    struct mq_attr { 
        long mq_flags;    // 0或者O_NONBLOCK 
        long mq_maxmsg; //队列中包含的消息数的最大限制数  
        long mq_msgsize; //每个消息大小的最大限制数
        long mq_curmsgs;  //当前队列中的消息数 
    } 

3、设置消息队列属性

我们可以设置消息队列的属性,实际只能设置flag标志,说明队列中没有消息时,接收消息的进程是否在队列上继续等待。

mqd_t mq_setattr(mqd_t mqdes, struct mq_attr *newattr, struct mq_attr *oldattr); 

参数:
Mqdes:打开消息队列时获取的描述符。
Attr:指向结构struct mq_attr的指针,用来获取消息队列的最大消息个数和最大消息长
度。放到数据结构的mq_maxmsg和mq_msgsize中。

     struct mq_attr { 
        long mq_flags;    // 0或者O_NONBLOCK,只能设置这个
        long mq_maxmsg; //队列中包含的消息数的最大限制数 
        long mq_msgsize; //每个消息大小的最大限制数   
        long mq_curmsgs;  //当前队列中的消息数
     } 

oldattr:用来保存设置之前的attr值,可以为NULL.
mq_setattr只能更改mq_flags,剩余三项参数被忽略,只能在mq_open时指定.

4、发送消息

进程在打开消息队列后,可以使用下面的函数发送消息

int mq_send(mqd_t mqdes, const char *ptr, size_t len, unsigned int prio); 

参数:
mqdes: 打开消息队列时获得的描述符。
ptr: 指向发送缓冲区的指针,发送缓冲区存放了要发送的数据。
Len: 要发送的数据的长度,必须小于open时指定的mq_msgsize。
prio :消息的优先级;它是一个小于MQ_PRIO_MAX 的数,数值越大,优先级越高。
POSIX 消息队列在调用 mq_receive 时总是返回队列中 最高优先级的最早消息 。如果消息不需要设定优先级,那么可以在 mq_send 是置 prio 为 0 , mq_receive 的 prio 置为 NULL 。 返回值:发送成功,返回0,失败,返回-1.

5、接收消息

进程在打开消息队列后,可以使用下面的函数接收消息。

ssize_t mq_receive(mqd_t mqdes, char *ptr, size_t len, unsigned int *prio); 

参数:
mqdes: 打开消息队列时获得的描述符。
ptr: 指向接收缓冲区的指针。接收缓冲区用来存放收到的消息。
Len: 接收缓冲区的长度。 len不能小于mq_msgsize,否则会返回EMSGSIZE
prio :消息的优先级;它是一个小于 MQ_PRIO_MAX 的数,数值越大,优先级越高。 POSIX 消息队列在调用 mq_receive 时总是返回队列中 最高优先级的最早消息 。如果消息不需要设定优先级,那么可以在 mq_send 是置 prio 为 0 , mq_receive 的 prio 置为 NULL 。 返回值: 接收成功,返回0,失败,返回-1.

6、消息队列的关闭

mqd_t mq_close(mqd_t mqdes);

关闭消息队列,但并不删除它 成功返回0,失败返回-1

7、删除消息队列

   mqd_t mq_unlink(const char *name); 

每个消息队列有一个保存其当前打开着描述符数的引用计数器:

当一个消息队列引用计数大于0时, 其name就能删除, 但是该队列的析构要到最后一个mq_close发生时才进行,又因为Posix消息队列具备随内核的持续性, 即使当前没有打开着的消息队列, 该队列以及其上的各个消息也将一直存在, 直到调用mq_unlink并让它的引用计数达到0以删除该队列为止

8、mq_notify函数

int mq_notify(mqd_t mqdes, const struct sigevent *sevp);
On success mq_notify() returns 0; on error, -1 is returned, with errno set to indicate the error.

Posix消息队列允许异步事件通知,以告知何时有一个消息放置到了某个消息队列中,以下两种方式可选:
1. 产生一个信号
2. 创建一个线程来执行指定函数

参考https://blog.csdn.net/u012062760/article/details/47043865

*sevp定义如下:

       union sigval {          /* Data passed with notification */
           int     sival_int;         /* Integer value */
           void   *sival_ptr;         /* Pointer value */
       };

       struct sigevent {
           int          sigev_notify; /* Notification method */
           int          sigev_signo;  /* Notification signal */
           union sigval sigev_value;  /* Data passed with
                                         notification */
           void       (*sigev_notify_function) (union sigval);
                            /* Function used for thread
                               notification (SIGEV_THREAD) */
           void        *sigev_notify_attributes;
                            /* Attributes for notification thread
                               (SIGEV_THREAD) */
           pid_t        sigev_notify_thread_id;
                            /* ID of thread to signal (SIGEV_THREAD_ID) */
       };

sigev_notify可以为SIGEV_NONE、SIGEV_SIGNAL、SIGEV_THREAD 。
如果设为SIGEV_NONE,则表示仅注册了当前队列的消息通知,当空消息队列收到数据时,并不给注册进程发送信号;
如果设为SIGEV_SIGNAL, 则将发送sigev_signo所指定的信号;
如果设为SIGEV_THREAD,则将启动新线程执行sigev_notify_function函数。
只允许有一个进程注册一个消息队列的通知。

如果sevp为NULL,则表示当前进程注销此mq的消息通知,允许其他进程注册此mq的通知。

如果同时有进程使用mq_receive在等待消息队列数据,则mq_receive优先于mq_notify注册的进程。

mq_notify注册的消息通知只生效一次,当收到mq消息通知时,如果还想继续收到mq消息通知,则必须重新调用mq_notify注册。

man例子:

EXAMPLE
       The following program registers a notification request for the message queue named in its command-line argument.
       Notification is performed by creating a thread.  The thread executes a function which reads one message from the
       queue and then terminates the process.

   Program source
       #include 
       #include 
       #include 
       #include 
       #include 

       #define handle_error(msg) \
           do { perror(msg); exit(EXIT_FAILURE); } while (0)

       static void                     /* Thread start function */
       tfunc(union sigval sv)
       {
           struct mq_attr attr;
           ssize_t nr;
           void *buf;
           mqd_t mqdes = *((mqd_t *) sv.sival_ptr);

           /* Determine max. msg size; allocate buffer to receive msg */

           if (mq_getattr(mqdes, &attr) == -1)
               handle_error("mq_getattr");
           buf = malloc(attr.mq_msgsize);
           if (buf == NULL)
               handle_error("malloc");

           nr = mq_receive(mqdes, buf, attr.mq_msgsize, NULL);
           if (nr == -1)
               handle_error("mq_receive");

           printf("Read %zd bytes from MQ\n", nr);
           free(buf);
           exit(EXIT_SUCCESS);         /* Terminate the process */
       }

       int
       main(int argc, char *argv[])
       {
           mqd_t mqdes;
           struct sigevent sev;

           if (argc != 2) {
               fprintf(stderr, "Usage: %s \n", argv[0]);
               exit(EXIT_FAILURE);
           }

           mqdes = mq_open(argv[1], O_RDONLY);
           if (mqdes == (mqd_t) -1)
               handle_error("mq_open");

           sev.sigev_notify = SIGEV_THREAD;
           sev.sigev_notify_function = tfunc;
           sev.sigev_notify_attributes = NULL;
           sev.sigev_value.sival_ptr = &mqdes;   /* Arg. to thread func. */
           if (mq_notify(mqdes, &sev) == -1)
               handle_error("mq_notify");

           pause();    /* Process will be terminated by thread function */
       }


参考:
https://www.cnblogs.com/linuxbug/p/4872496.html
https://blog.csdn.net/sty23122555/article/details/51132158
管道介绍:
http://www.cnblogs.com/linuxbug/p/4863724.html

你可能感兴趣的:(Linux)