当前位置: 首页 > news >正文

Linux/AndroidOS中进程间的通信线程间的同步 - 消息队列

本文介绍消息队列,它允许进程之间以消息的形式交换数据。数据的交换单位是整个消息。

  • POSIX 消息队列是引用计数的。只有当所有当前使用队列的进程都关闭了队列之后才会对队列进行标记以便删除。
  • POSIX 消息有一个关联的优先级,并且消息之间是严格按照优先级顺序排队的(以及接收)。
  • POSIX 消息队列提供了一个特性允许在队列中的一条消息可用时异步地通知进程。

POSIX 消息队列支持是一个通过 CONFIG_POSIX_MQUEUE 选项配置的可选内核组件。

1 概述

POSIX 消息队列 API 中的主要函数如下。

  • mq_open()函数创建一个新消息队列或打开一个既有队列,返回后续调用中会用到的消息队列描述符。
  • mq_send()函数向队列写入一条消息。
  • mq_receive()函数从队列中读取一条消息。
  • mq_close()函数关闭进程之前打开的一个消息队列。
  • mq_unlink()函数删除一个消息队列名并当所有进程关闭该队列时对队列进行标记以便删除。

上面的函数所完成的功能是相当明显的。此外,POSIX 消息队列 API 还具备一些特别的特性。

  • 每个消息队列都有一组关联的特性,其中一些特性可以在使用 mq_open()创建或打开队列时进行设置。获取和修改队列特性的工作则是由两个函数来完成的:mq_getattr()和 mq_setattr()。
  • mq_notify()函数允许一个进程向一个队列注册接收消息通知。在注册完之后,当一条消息可用时会通过发送一个信号或在一个单独的线程中调用一个函数来通知进程。

2 打开、关闭和断开链接消息队列

2.1 打开一个消息队列

mq_open()函数创建一个新消息队列或打开一个既有队列。

#include <fcntl.h>			/*Defines 0*constants */
#include<sys/stat.h>		/*Defines mode constants */
#include <mqueue.h>mqd_t mq_open(const char *name, int oflag, .../* mode t mode, struct mq_attr *attr */);/* Returns a message queue descriptor on success, or (mgd_t)-l on error */

name 参数标识出了消息队列。
oflag 参数是一个位掩码,它控制着 mq_open()操作的各个方面。下表对这个掩码中可以包含的值进行了总结。
在这里插入图片描述
oflag 参数的其中一个用途是,确定是打开一个既有队列还是创建和打开一个新队列。如果在 oflag 中不包含 O_CREAT,那么将会打开一个既有队列。如果在 oflag 中包含了 O_CREAT,并且与给定的 name 对应的队列不存在,那么就会创建一个新的空队列。如果在 oflag 中同时包含 O_CREAT 和 O_EXCL,并且与给定的 name 对应的队列已经存在,那么 mq_open()就会失败。

oflag 参数还能够通过包含 O_RDONLY、O_WRONLY 以及 O_RDWR 这三个值中的一个来表明调用进程在消息队列上的访问方式。
剩下的一个标记值 O_NONBLOCK 将会导致以非阻塞的模式打开队列。如果后续的mq_receive()或 mq_send()调用无法在不阻塞的情况下执行,那么调用就会立即返回 EAGAIN 错误。

mq_open()通常用来打开一个既有消息队列,这种调用只需要两个参数,但如果在 flags中指定了 O_CREAT,那么就还需要另外两个参数:mode 和 attr。(如果通过 name 指定的队列已经存在,那么这两个参数会被忽略。)这些参数的用法如下。

  • mode 参数是一个位掩码,它指定了施加于新消息队列之上的权限。这个参数可取的位值与文件上的掩码值是一样的,并且与 open()一样,mode 中的值会与进程的 umask取掩码。要从一个队列中读取消息(mq_receive())就必须要将读权限赋予相应的用户,要向队列写入消息(mq_send())就需要写权限。
  • attr 参数是一个 mq_attr 结构,它指定了新消息队列的特性。如果 attr 为 NULL,那么将使用实现定义的默认特性创建队列。

mq_open()在成功结束时会返回一个消息队列描述符,它是一个类型为 mqd_t 的值,在后续的调用中将会使用它来引用这个打开着的消息队列。即需要确保这个类型是一个能在赋值语句中使用或能作为函数参数传递的的类型。(如在 Linux 上,mqd_t 是一个 int)

2.2 fork()、exec()以及进程终止对消息队列描述符的影响

在 fork()中子进程会接收其父进程的消息队列描述符的副本,并且这些描述符会引用同样的打开着的消息队列描述。子进程不会继承其父进程的任何消息通知注册。
当一个进程执行了一个 exec()或终止时,所有其打开的消息队列描述符会被关闭。关闭消息队列描述符的结果是进程在相应队列上的消息通知注册会被注销。

2.3 关闭一个消息队列

mq_close()函数关闭消息队列描述符 mqdes。

#include <mqueue.h>
int mq_close(mqd_t mqdes);/* Returns 0 on success, or -l on error */

如果调用进程已经通过 mqdes 在队列上注册了消息通知,那么通知注册会自动被删除,并且另一个进程可以随后向该队列注册消息通知。
当进程终止或调用 exec()时,消息队列描述符会被自动关闭。与文件描述符一样,应用程序应该在不再使用消息队列描述符的时候显式地关闭消息队列描述符以防止出现进程耗尽消息队列描述符的情况。

与文件上的 close()一样,关闭一个消息队列并不会删除该队列。要删除队列则需要使用mq_unlink(),它是 unlink()在消息队列上的版本。

2.4 删除一个消息队列

mq_unlink()函数删除通过 name 标识的消息队列,并将队列标记为在所有进程使用完该队列之后销毁该队列(这可能意味着会立即删除,前提是所有打开该队列的进程已经关闭了该队列)。

#include <mqueue.h>
int mq_unlink(const char *name);/* Returns 0 on success, or -l on error */

示例程序显示了 mq_unlink()的用法。

/* pmsg_unlink.cUsage: pmsg_unlink mq-nameUnlink a POSIX message queue.使用 mq_unlink()断开一个 POSIX 消息队列的链接Linux supports POSIX message queues since kernel 2.6.6.
*/
#include <mqueue.h>
#include "tlpi_hdr.h"int
main(int argc, char *argv[])
{if (argc != 2 || strcmp(argv[1], "--help") == 0)usageErr("%s mq-name\n", argv[0]);if (mq_unlink(argv[1]) == -1)errExit("mq_unlink");exit(EXIT_SUCCESS);
}

3 描述符和消息队列之间的关系

消息队列描述符打开着的消息队列之间的关系 与 文件描述符打开着的文件描述之间的关系类似。消息队列描述符是一个进程级别的句柄,它引用了系统层面的打开着的消息队列描述表中的一个条目,而该条目则引用了一个消息队列对象。下图对这种关系进行了描绘。

在 Linux 上,消息队列被实现成了虚拟文件系统中的 i-node,并且消息队列描述符和打开着的消息队列描述分别被实现成了文件描述符和打开着的文件描述。
下图有助于阐明消息队列描述符的使用方面的细节问题(所有这些都与文件描述符的使用类似)。
请添加图片描述

  • 一个打开的消息队列描述拥有一组关联的标记。SUSv3 只规定了一种这样的标记,即NONBLOCK,它确定了 I/O 是否是非阻塞的。
  • 两个进程能够持有引用同一个打开的消息队列描述的消息队列描述符(图中的描述符x)。当一个进程在打开了一个消息队列之后调用 fork()时就会发生这种情况。这些描述符会共享 O_NONBLOCK 标记的状态。
  • 两个进程能够持有引用不同消息队列描述(它们引用了同一个消息队列)的打开的消息队列描述(如进程 A 中的描述符 z 和进程 B 中的描述符 y 都引用了/mq-r)。当两个进程分别使用 mq_open()打开同一个队列时就会发生这种情况。

4 消息队列特性

mq_open()、mq_getattr()以及 mq_setattr()函数都会接收一个参数,它是一个指向 mq_attr结构的指针。这个结构是在<mqueue.h>中进行定义的,其形式如下。

struct mq_attr {long mq_flags;			/* Message queue description flags:0 or O_NONBLOCK [mq_getattr(),mq_setattr()] */long mq_maxmsg;			/* Maximum number of messages on queue [mq_open(),mq_getattr()] */long mq_msgsize;		/* Maximum message size(in bytes) [mq_open(),mq_getattr()] */long mq_curmsgs;		/* Number of messages currently in queue [mq_getattr()] */
};

在开始深入介绍 mq_attr 的细节之前有必要指出以下几点。

  • 这三个函数中的每个函数都只用到了其中几个字段。上面给出的结构定义中的注释指出了各个函数所用到的字段。
  • 这个结构包含了与一个消息描述符相关联的打开的消息队列描述(mq_flags)的相关信息以及该描述符所引用的队列的相关信息(mq_maxmsg、mq_msgsize、mq_curmsgs)。
  • 其中一些字段中包含的信息在使用 mq_open()创建队列时就已经确定下来了(mq_maxmsg 和 mq_msgsize);其他字段则会返回消息队列描述(mq_flags)或消息队列(mq_curmsgs)的当前状态的相关信息。

4.1 在创建队列时设置消息队列特性

在使用 mq_open()创建消息队列时可以通过下列 mq_attr 字段来确定队列的特性。

  • mq_maxmsg 字段定义了使用mq_send()向消息队列添加消息的数量上限,其取值必须大于0。
  • mq_msgsize 字段定义了加入消息队列的每条消息的大小的上限,其取值必须大于 0。

内核根据这两个值来确定消息队列所需的最大内存量。

mq_maxmsg 和 mq_msgsize 特性是在消息队列被创建时就确定下来的,并且之后也无法修改这两个特性。

示例程序为 mq_open()函数提供了一个命令行界面并展示了在 mq_open()中如何使用 mq_attr 结构。

消息队列特性可以通过两个命令行参数来指定:–m 用于指定 mq_maxmsg,–s 用于指定mq_msgsize。

  • 只要指定了其中一个选项,那么一个非 NULL 的 attrp 参数就会被传递给mq_open()。
  • 如果在命令行中只指定了–m 和–s 选项中的一个,那么 attrp 指向的 mq_attr 结构中的一些字段就会取默认值。
  • 如果两个选项都被没有被指定,那么在调用 mq_open()时会将attrp 指定为 NULL,这将会导致使用由实现定义的队列特性的默认值来创建队列。
/* pmsg_create.cCreate a POSIX message queue.创建一个 POSIX 消息队列Usage as shown in usageError().Linux supports POSIX message queues since kernel 2.6.6.
*/
#include <mqueue.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "tlpi_hdr.h"static void
usageError(const char *progName)
{fprintf(stderr, "Usage: %s [-cx] [-m maxmsg] [-s msgsize] mq-name ""[octal-perms]\n", progName);fprintf(stderr, "    -c          Create queue (O_CREAT)\n");fprintf(stderr, "    -m maxmsg   Set maximum # of messages\n");fprintf(stderr, "    -s msgsize  Set maximum message size\n");fprintf(stderr, "    -x          Create exclusively (O_EXCL)\n");exit(EXIT_FAILURE);
}int
main(int argc, char *argv[])
{int flags, opt;mode_t perms;mqd_t mqd;struct mq_attr attr, *attrp;/* If 'attrp' is NULL, mq_open() uses default attributes. If anoption specifying a message queue attribute is supplied on thecommand line, we save the attribute in 'attr' and set 'attrp'pointing to 'attr'. We assign some (arbitrary) default valuesto the fields of 'attr' in case the user specifies the valuefor one of the queue attributes, but not the other. */attrp = NULL;attr.mq_maxmsg = 10;attr.mq_msgsize = 2048;flags = O_RDWR;/* Parse command-line options */while ((opt = getopt(argc, argv, "cm:s:x")) != -1) {switch (opt) {case 'c':flags |= O_CREAT;break;case 'm':attr.mq_maxmsg = atoi(optarg);attrp = &attr;break;case 's':attr.mq_msgsize = atoi(optarg);attrp = &attr;break;case 'x':flags |= O_EXCL;break;default:usageError(argv[0]);}}if (optind >= argc)usageError(argv[0]);perms = (argc <= optind + 1) ? (S_IRUSR | S_IWUSR) :getInt(argv[optind + 1], GN_BASE_8, "octal-perms");mqd = mq_open(argv[optind], flags, perms, attrp);if (mqd == (mqd_t) -1)errExit("mq_open");exit(EXIT_SUCCESS);
}

4.2 获取消息队列特性

mq_getattr()函数返回一个包含与描述符 mqdes 相关联的消息队列描述和消息队列的相关信息的 mq_attr 结构。

#include <mqueue.h>
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);/* Returs 0 on success, or -l on error */

除了上面已经介绍的 mq_maxmsg 和 mq_msgsize 字段之外,attr 指向的返回结构中还包含下列字段。

  • mq_flags
    这些是与描述符 mqdes 相关联的打开的消息队列描述的标记,其取值只有一个:O_NONBLOCK。这个标记是根据 mq_open()的 oflag 参数来初始化的,并且使用 mq_setattr()可以修改这个标记。
  • mq_curmsgs
    当前位于队列中的消息数。这个信息在 mq_getattr()返回时可能已经发生了改变,前提是存在其他进程从队列中读取消息或向队列写入消息。

示例程序使用了 mq_getattr()来获取通过命令行参数指定的消息队列的特性,然后在标准输出中显示这些特性。

/* pmsg_getattr.cDisplay attributes of a POSIX message queue.获取 POSIX 消息队列特性Linux supports POSIX message queues since kernel 2.6.6.
*/
#include <mqueue.h>
#include "tlpi_hdr.h"int
main(int argc, char *argv[])
{mqd_t mqd;struct mq_attr attr;if (argc != 2 || strcmp(argv[1], "--help") == 0)usageErr("%s mq-name\n", argv[0]);mqd = mq_open(argv[1], O_RDONLY);if (mqd == (mqd_t) -1)errExit("mq_open");if (mq_getattr(mqd, &attr) == -1)errExit("mq_getattr");printf("Maximum # of messages on queue:   %ld\n", attr.mq_maxmsg);printf("Maximum message size:             %ld\n", attr.mq_msgsize);printf("# of messages currently on queue: %ld\n", attr.mq_curmsgs);exit(EXIT_SUCCESS);
}

下面的 shell会话使用了示例程序来创建一个消息队列并使用实现定义的默认值来初始化其特性(即传入 mq_open()的最后一个参数为 NULL),然后显示队列特性,这样就能够看到 Linux 上的默认设置了。

$ ./pmsg_create -cx /mq
$ ./pmsg_getattr /mq
Maximum # of messages on queue:   10
Maximum message size:             8192
# of messages currently on queue: 0
$ ./pmsg_unlink /mq

从上面的输出中可以看出 Linux 上 mq_maxmsg 和 mq_msgsize 的默认取值分别为 10 和8192。

4.3 修改消息队列特性

mq_setattr()函数设置与消息队列描述符 mqdes 相关联的消息队列描述的特性,并可选地返回与消息队列有关的信息。

#include <mqueue.h>
int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr);/* Returns 0 on success, or-l on error */

mq_setattr()函数执行下列任务。

  • 它使用 newattr 指向的 mq_attr 结构中的 mq_flags 字段来修改与描述符 mqdes 相关联的消息队列描述的标记(POSIX 规定使用 mq_setattr()能够修改的唯一特性是 O_NONBLOCK 标记的状态)。
  • 如果 oldattr 不为 NULL,那么就返回一个包含之前的消息队列描述标记和消息队列特性的 mq_attr 结构(即与 mq_getattr()执行的任务一样)。

应用程序应该通过使用 mq_getattr() 来 获 取 mq_flags 值并修改O_NONBLOCK 位来修改 O_NONBLOCK 标记的状态以及调用 mq_setattr()来修改 mq_flags 设置。如为启用 O_NONBLOCK 需要编写下列代码:

if(mg_getattr(mqd,&attr)==-1)errExit("mq getattr");
attr.mq_flagS=O_NONBLOCK;
if(mq_setattr(mqd,&attr,NULL)==-1)errExit("mq_getattr");

5 交换消息

5.1 发送消息

mq_send()函数将位于 msg_ptr 指向的缓冲区中的消息添加到描述符 mqdes 所引用的消息队列中。

#include <mqueue.h>
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);/* Returns 0 on success, or -l on error */

msg_len 参数指定了 msg_ptr 指向的消息的长度,其值必须小于或等于队列的 mq_msgsize特性,否则 mq_send()就会返回 EMSGSIZE 错误。长度为零的消息是允许的。
每条消息都拥有一个用非负整数表示的优先级,它通过 msg_prio 参数指定。消息在队列中是按照优先级倒序排列的(即 0 表示优先级最低)。当一条消息被添加到队列中时,它会被放置在队列中具有相同的优先级的所有消息之后。如果一个应用程序无需使用消息优先级,那么只需要将 msg_prio 指定为 0 即可。

Linux上 这个上限至少是 32768(_POSIX_MQ_PRIO_MAX),即优先级的取值范围至少为 0 到 32767。

如果消息队列已经满了(即已经达到了队列的 mq_maxmsg 限制),那么后续的 mq_send()调用会阻塞直到队列中存在可用空间为止或者在 O_NONBLOCK 标记起作用时立即失败并返回 EAGAIN 错误。

示例程序为 mq_send()函数提供了一个命令行界面。

/* pmsg_send.cUsage as shown in usageError().Send a message (specified as a command line argument) to a POSIX message queue.向 POSIX 消息队列写入一条消息See also pmsg_receive.c.Linux supports POSIX message queues since kernel 2.6.6.
*/
#include <mqueue.h>
#include <fcntl.h>              /* For definition of O_NONBLOCK */
#include "tlpi_hdr.h"static void
usageError(const char *progName)
{fprintf(stderr, "Usage: %s [-n] mq-name msg [prio]\n", progName);fprintf(stderr, "    -n           Use O_NONBLOCK flag\n");exit(EXIT_FAILURE);
}int
main(int argc, char *argv[])
{int flags, opt;mqd_t mqd;unsigned int prio;flags = O_WRONLY;while ((opt = getopt(argc, argv, "n")) != -1) {switch (opt) {case 'n':   flags |= O_NONBLOCK;        break;default:    usageError(argv[0]);}}if (optind + 1 >= argc)usageError(argv[0]);mqd = mq_open(argv[optind], flags);if (mqd == (mqd_t) -1)errExit("mq_open");prio = (argc > optind + 2) ? atoi(argv[optind + 2]) : 0;if (mq_send(mqd, argv[optind + 1], strlen(argv[optind + 1]), prio) == -1)errExit("mq_send");exit(EXIT_SUCCESS);
}

5.2 接收消息

mq_receive()函数从 mqdes 引用的消息队列中删除一条优先级最高、存在时间最长的消息并将删除的消息放置在 msg_ptr 指向的缓冲区。

#include <mqueue.h>
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);/* Returns number of bytes in received message on success, or -l on error */

调用者使用 msg_len 参数来指定 msg_ptr 指向的缓冲区中的可用字节数。
不管消息的实际大小是什么,msg_len(即 msg_ptr 指向的缓冲区的大小)必须要大于或等于队列的 mq_msgsize 特性,否则 mq_receive()就会失败并返回 EMSGSIZE 错误。如果不清楚一个队列的 mq_msgsize 特性的值,那么可以使用 mq_getattr()来获取这个值。(在一个包含多个协作进程的应用程序中一般无需使用 mq_getattr(),因为应用程序通常能够提前确定队列的 mq_msgsize 设置。)

如果 msg_prio 不为 NULL,那么接收到的消息的优先级会被复制到 msg_prio 指向的位置处。如果消息队列当前为空,那么 mq_receive() 会阻塞直到存在可用的消息或在O_NONBLOCK 标记起作用时会立即失败并返回 EAGAIN 错误。

示例程序为 mq_receive()函数提供了一个命令行界面,在 usageError()函数中给出了这个程序的命令格式。下面的 shell 会话演示了示例程序的用法。首先创建了一个消息队列并向其发送了一些具备不同优先级的消息。

$ ./pmsg_create -cx /mq
$ ./pmsg_send /mq msg-a 5
$ ./pmsg_send /mq msg-b 0
$ ./pmsg_send /mq msg-c 10

然后执行一系列命令来从队列中接收消息。

$ ./pmsg_receive /mq
Read 5 bytes; priority = 10
msg-c
$ ./pmsg_receive /mq
Read 5 bytes; priority = 5
msg-a
$ ./pmsg_receive /mq
Read 5 bytes; priority = 0
msg-b

从上面的输出中可以看出,消息的读取是按照优先级来进行的。
此刻,这个队列是空的。当再次执行阻塞式接收时,操作就会阻塞。

$ ./pmsg_receive /mq

另一方面,如果执行了一个非阻塞接收,那么调用就会立即返回一个失败状态。

$ ./pmsg_receive -n /mq
ERROR [EAGAIN/EWOULDBLOCK Resource temporarily unavailable] mq_receive
/* pmsg_receive.cUsage as shown in usageError().Receive a message from a POSIX message queue, and write it onstandard output.从 POSIX 消息队列中读取一条消息See also pmsg_send.c.Linux supports POSIX message queues since kernel 2.6.6.
*/
#include <mqueue.h>
#include <fcntl.h>              /* For definition of O_NONBLOCK */
#include "tlpi_hdr.h"static void
usageError(const char *progName)
{fprintf(stderr, "Usage: %s [-n] mq-name\n", progName);fprintf(stderr, "    -n           Use O_NONBLOCK flag\n");exit(EXIT_FAILURE);
}int
main(int argc, char *argv[])
{int flags, opt;mqd_t mqd;unsigned int prio;void *buffer;struct mq_attr attr;ssize_t numRead;flags = O_RDONLY;while ((opt = getopt(argc, argv, "n")) != -1) {switch (opt) {case 'n':   flags |= O_NONBLOCK;        break;default:    usageError(argv[0]);}}if (optind >= argc)usageError(argv[0]);mqd = mq_open(argv[optind], flags);if (mqd == (mqd_t) -1)errExit("mq_open");/* We need to know the 'mq_msgsize' attribute of the queue inorder to determine the size of the buffer for mq_receive() */if (mq_getattr(mqd, &attr) == -1)errExit("mq_getattr");buffer = malloc(attr.mq_msgsize);if (buffer == NULL)errExit("malloc");numRead = mq_receive(mqd, buffer, attr.mq_msgsize, &prio);if (numRead == -1)errExit("mq_receive");printf("Read %ld bytes; priority = %u\n", (long) numRead, prio);if (write(STDOUT_FILENO, buffer, numRead) == -1)errExit("write");write(STDOUT_FILENO, "\n", 1);exit(EXIT_SUCCESS);
}

5.3 在发送和接收消息时设置超时时间

mq_timedsend()和 mq_timedreceive()函数与 mq_send()和 mq_receive()几乎是完全一样的,它们之间唯一的差别在于如果操作无法立即被执行,并且该消息队列描述上的O_NONBLOCK 标记不起作用,那么 abs_timeout 参数就会为调用阻塞的时间指定一个上限。

#define _XOPEN_SOURCE 600
#include <mqueue.h>
#include <time.h>
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout);/* Returns 0 on success, or -l on error */
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len,unsigned int *msg_prio, const struct timespec *abs_timeout);/* Returns number of bytes in received message on success, or -l on error */

abs_timeout 参数是一个 timespec 结构,它将超时时间描述为自新纪元到现在的一个绝对值,其单位为秒数和纳秒数。要指定一个相对超时则可以使用 clock_gettime()来获取 CLOCK_REALTIME 时钟的当前值并在该值上加上所需的时间量来生成一个恰当初始化过的 timespec 结构。

如果 mq_timedsend()或 mq_timedreceive()调用因超时而无法完成操作,那么调用就会失败并返回 ETIMEDOUT 错误。

6 消息通知

POSIX 消息队列能够接收之前为空的队列上有可用消息的异步通知即队列从空变成了非空)。这个特性意味着已经无需执行一个阻塞的调用或将消息队列描述符标记为非阻塞并在队列上定期执行 mq_receive()调用了,因为一个进程能够请求消息到达通知,然后继续执行其他任务直到收到通知为止。进程可以选择通过信号的形式或通过在一个单独的线程中调用一个函数的形式来接收通知。

mq_notify()函数注册调用进程在一条消息进入描述符 mqdes 引用的空队列时接收通知。

#include <mqueue.h>
int mq_notify(mqd_t mqdes, const struct sigevent *notification);/* Returns 0 on success, or -l on error */

notification 参数指定了进程接收通知的机制。在深入介绍 notification 参数的细节之前,有关消息通知需要注意以下几点。

  • 在任何一个时刻都只有一个进程(“注册进程”)能够向一个特定的消息队列注册接收通知。如果一个消息队列上已经存在注册进程了,那么后续在该队列上的注册请求将会失败(mq_notify()返回 EBUSY 错误)。
  • 只有当一条新消息进入之前为空的队列时注册进程才会收到通知。如果在注册的时候队列中已经包含消息,那么只有当队列被清空之后有一条新消息达到之时才会发出通知。
  • 当向注册进程发送了一个通知之后就会删除注册信息,之后任何进程就能够向队列注册接收通知了。换句话说,只要一个进程想要持续地接收通知,那么它就必须要在每次接收到通知之后再次调用 mq_notify()来注册自己。
  • 注册进程只有在当前不存在其他在该队列上调用 mq_receive()而发生阻塞的进程时才会收到通知。如果其他进程在 mq_receive()调用中被阻塞了,那么该进程会读取消息,注册进程会保持注册状态。
  • 一个进程可以通过在调用 mq_notify()时传入一个值为 NULL 的 notification 参数来撤销自己在消息通知上的注册信息。

示例程序中给出的是该结构的一个简化版本,它只列出了与 mq_notify()相关的字段。

union sigval {int 	sival_int;		/*Integer value for accompanying data */void 	*sival_ptr;		/* Pointer value for accompanying data */
};struct sigevent {int		sigev_notify;	/* Notification method */int		sigev_signo;	/* Notification signal for SIGEV SIGNAL */union	sigval sigev_value;		/*Value passed to signal handler or thread function */void (*sigev_notify_function) (union sigval);	/* Thread notification function */void *sigev_notify_attributes;	/*Really 'pthread attr t'*/

这个结构的 sigev_notify 字段将会被设置成下列值中的一个。

  • SIGEV_NONE
    注册这个进程接收通知,但当一条消息进入之前为空的队列时不通知该进程。与往常一样,当新消息进入空队列之后注册信息会被删除。
  • SIGEV_SIGNAL
    通过生成一个在 sigev_signo 字段中指定的信号来通知进程。如果 sigev_signo 是一个实时信号,那么 sigev_value 字段将会指定信号都带的数据。通过传入信号处理器的siginfo_t 结构中的 si_value 字段或通过调用 sigwaitinfo()或 sigtimedwait()返回值能够取得这部分数据。siginfo_t 结构中的下列字段也会被填充:si_code,其值为 SI_MESGQ;si_signo,其值是信号编号;si_pid,其值是发送消息的进程的进程 ID;以及 si_uid,其值是发送消息的进程的真实用户 ID。(si_pid 和 si_uid 字段在其他大多数实现上不会被设置。)
  • SIGEV_THREAD
    通过调用在 sigev_notify_function 中指定的函数来通知进程,就像是在一个新线程中启动该函数一样。sigev_notify_attributes 字段可以为 NULL 或是一个指向定义了线程的特性的 pthread_ attr_t 结构的指针。sigev_value 中指定的联合 sigval 值将会作为参数传入这个函数。

6.1 通过信号接收通知

示例程序提供了一个使用信号来进行消息通知的例子。这个程序执行了下列任务。

  1. 以非阻塞模式打开了一个通过命令行指定名称的消息队列①,确定了该队列的mq_msgsize 特性的值②,并分配了一个大小为该值的缓冲区来接收消息③。
  2. 阻塞通知信号(SIGUSR1)并为其建立一个处理器④。
  3. 首次调用 mq_notify()来注册进程接收消息通知⑤。
  4. 执行一个无限循环,在循环中执行下列任务。
    (1) 调用 sigsuspend(),该函数会解除通知信号的阻塞状态并等待直到信号被捕获⑥。从这个系统调用中返回表示已经发生了一个消息通知。此刻,进程会撤销消息通知的注册信息。
    (2) 调用 mq_notify()重新注册进程接收消息通知⑦。
    (3) 执行一个 while 循环从队列中尽可能多地读取消息以便清空队列⑧。
/* mq_notify_sig.cUsage: mq_notify_sig mq-nameDemonstrate message notification via signals (catching the signals witha signal handler) on a POSIX message queue.通过信号接收消息通知
*/
#include <signal.h>
#include <mqueue.h>
#include <fcntl.h>              /* For definition of O_NONBLOCK */
#include "tlpi_hdr.h"#define NOTIFY_SIG SIGUSR1static void
handler(int sig)
{/* Just interrupt sigsuspend() */
}/* This program does not handle the case where a message already exists onthe queue by the time the first attempt is made to register for messagenotification. In that case, the program would never receive a notification.See mq_notify_via_signal.c for an example of how to deal with that case. */int
main(int argc, char *argv[])
{struct sigevent sev;mqd_t mqd;struct mq_attr attr;void *buffer;ssize_t numRead;sigset_t blockMask, emptyMask;struct sigaction sa;if (argc != 2 || strcmp(argv[1], "--help") == 0)usageErr("%s mq-name\n", argv[0]);mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);	//以非阻塞模式打开了一个通过命令行指定名称的消息队列if (mqd == (mqd_t) -1)errExit("mq_open");/* Determine mq_msgsize for message queue, and allocate an input bufferof that size */if (mq_getattr(mqd, &attr) == -1)		//确定了该队列的mq_msgsize 特性的值errExit("mq_getattr");buffer = malloc(attr.mq_msgsize);		//并分配了一个大小为该值的缓冲区来接收消息if (buffer == NULL)errExit("malloc");/* Block the notification signal and establish a handler for it */sigemptyset(&blockMask);				//阻塞通知信号(SIGUSR1)并为其建立一个处理器sigaddset(&blockMask, NOTIFY_SIG);if (sigprocmask(SIG_BLOCK, &blockMask, NULL) == -1)errExit("sigprocmask");sigemptyset(&sa.sa_mask);sa.sa_flags = 0;sa.sa_handler = handler;if (sigaction(NOTIFY_SIG, &sa, NULL) == -1)errExit("sigaction");/* Register for message notification via a signal */sev.sigev_notify = SIGEV_SIGNAL;		//首次调用 mq_notify()来注册进程接收消息通知sev.sigev_signo = NOTIFY_SIG;if (mq_notify(mqd, &sev) == -1)errExit("mq_notify");sigemptyset(&emptyMask);for (;;) {sigsuspend(&emptyMask);         /* 调用 sigsuspend(),该函数会解除通知信号的阻塞状态并等待直到信号被捕获 *//* Reregister for message notification */if (mq_notify(mqd, &sev) == -1)	//调用 mq_notify()重新注册进程接收消息通知errExit("mq_notify");while ((numRead = mq_receive(mqd, buffer, attr.mq_msgsize, NULL)) >= 0)	//执行一个 while 循环从队列中尽可能多地读取消息以便清空队列printf("Read %ld bytes\n", (long) numRead);if (errno != EAGAIN)            /* Unexpected error */errExit("mq_receive");}
}

示例程序中存在很多方面值得详细解释。

  • 程序阻塞了通知信号并使用 sigsuspend()来等待该信号,而没有使用 pause(),这是为了防止出现程序在执行 for 循环中的其他代码(即没有因等待信号而阻塞)时错过信号的情况。如果发生了这种情况,并且使用了 pause()来等待信号,那么下次调用 pause()时会阻塞,即使系统已经发出了一个信号。
  • 程序以非阻塞模式打开了队列,并且当一个通知发生之后使用一个 while 循环来读取队列中的所有消息。通过这种方式来清空队列能够确保当一条新消息到达之后会产生一个新通知。使用非阻塞模式意味着 while 循环在队列被清空之后就会终止(mq_receive()会失败并返回 EAGAIN 错误)。
  • 在 for 循环中比较重要的一点是在读取队列中的所有消息之前重新注册接收消息通知。如果颠倒了顺序,如按照下面的顺序:队列中的所有消息都被读取了,while 循环终止;另一个消息被添加到了队列中;mq_notify()被调用以重新注册接收消息通知。此刻,系统将不会产生新的通知信号,因为队列已经非空了,其结果是程序在下次调用 sigsuspend()时会永远阻塞。

6.2 通过线程接收通知

示例程序提供了一个使用线程来发布消息通知的例子。

  • 当消息通知发生时,程序会在清空队列之前重新启用通知②。
  • 采用了非阻塞模式使得在接收到一个通知之后可以在无需阻塞的情况下完全清空队列⑤。
/* mq_notify_thread.cDemonstrate message notification via threads on a POSIX message queue.
*/
#include <pthread.h>
#include <mqueue.h>
#include <signal.h>
#include <fcntl.h>              /* For definition of O_NONBLOCK */
#include "tlpi_hdr.h"/* This program does not handle the case where a message already exists onthe queue by the time the first attempt is made to register for messagenotification. In that case, the program would never receive a notification.See mq_notify_via_thread.c for an example of how to deal with that case. */static void notifySetup(mqd_t *mqdp);static void                     /* Thread notification function */
threadFunc(union sigval sv)
{ssize_t numRead;mqd_t *mqdp;void *buffer;struct mq_attr attr;mqdp = sv.sival_ptr;/* Determine mq_msgsize for message queue, and allocate an input bufferof that size */if (mq_getattr(*mqdp, &attr) == -1)errExit("mq_getattr");buffer = malloc(attr.mq_msgsize);if (buffer == NULL)errExit("malloc");/* Reregister for message notification */notifySetup(mqdp);while ((numRead = mq_receive(*mqdp, buffer, attr.mq_msgsize, NULL)) >= 0)printf("Read %ld bytes\n", (long) numRead);if (errno != EAGAIN)                        /* Unexpected error */errExit("mq_receive");free(buffer);
}static void
notifySetup(mqd_t *mqdp)
{struct sigevent sev;sev.sigev_notify = SIGEV_THREAD;            /* Notify via thread */sev.sigev_notify_function = threadFunc;sev.sigev_notify_attributes = NULL;/* Could be pointer to pthread_attr_t structure */sev.sigev_value.sival_ptr = mqdp;           /* Argument to threadFunc() */if (mq_notify(*mqdp, &sev) == -1)errExit("mq_notify");
}int
main(int argc, char *argv[])
{mqd_t mqd;if (argc != 2 || strcmp(argv[1], "--help") == 0)usageErr("%s mq-name\n", argv[0]);mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);if (mqd == (mqd_t) -1)errExit("mq_open");notifySetup(&mqd);pause();                    /* Wait for notifications via thread function */
}

示例程序的设计还需要注意以下几点。

  • 程序通过一个线程来请求通知需要将传入 mq_notify()的 sigevent 结构的 sigev_notify字段的值指定为 SIGEV_THREAD 。线程的启动函数 threadFunc() 是通过sigev_notify_function 字段来指定的③。
  • 在启用消息通知之后,主程序会永远中止⑥;定时器通知是通过在一个单独的线程中调用 threadFunc()来分发的①。
  • 本来可以通过将消息队列描述符 mqd 变成一个全局变量使之对 threadFunc()可见,但这里采用了一种不同的做法:将消息队列描述符的地址放在了传给 mq_notify()的sigev_value.sival_ptr 字段中④。当后面调用 threadFunc()时,这个参数会作为其参数被传入到该函数中。

7 Linux 特有的特性

POSIX 消息队列在 Linux 上的实现提供了一些非标准的却相当有用的特性。

7.1 通过命令行显示和删除消息队列对象

POSIX IPC 对象被实现成了虚拟文件系统中的文件,并且可以使用 ls和 rm 来列出和删除这些文件。为列出和删除 POSIX 消息队列就必须要使用形如下面的命令来将消息队列挂载到文件系统中。

# mount -t mqueue source target

source 可以是任意一个名字(通常将其指定为字符串 none),其唯一的意义是它将出现在/proc/mounts 中并且 mount 和 df 命令会显示出这个名字。target 是消息队列文件系统的挂载点。

下面的 shell 会话显示了如何挂载消息队列文件系统和显示其内容。首先为文件系统创建一个挂载点并挂载它。

$ sudo mkdir /dev/mqueue
$ sudo mount -t mqueue none /dev/mqueue

接着显示新挂载在/proc/mounts 中的记录,然后显示挂载目录上的权限。

$ cat /proc/mounts | grep mqueue
none /dev/mqueue mqueue rw 0 0
$ ls -ld /dev/mqueue
drwxrwxrwt 2 root root 60 May  7 16:54 /dev/mqueue/

在 ls 命令的输出中需要注意的一点是消息队列文件系统在挂载时会自动为挂载目录设置粘滞位。(从 ls 的输出中的 other-execute 权限字段中有一个 t 就可以看出这一点。)这意味着非特权进程只能在它所拥有的消息队列上执行断开链接的操作。

接着创建一个消息队列,使用 ls 来表明它在文件系统中是可见的,然后删除该消息队列。

$ ./pmsg_create -c /newq
$ ls /dev/mqueue
newq
$ rm /dev/mqueue/newq

7.2 获取消息队列的相关信息

可以显示消息队列文件系统中的文件的内容,每个虚拟文件都包含了其关联的消息队列的相关信息。

$ ./pmsg_create -c /mq
$ ./pmsg_send /mq abcdefg
$ cat /dev/mqueue/mq
QSIZE:7          NOTIFY:0     SIGNO:0     NOTIFY_PID:0

QSIZE 字段的值为队列中所有数据的总字节数,剩下的字段则与消息通知相关。如果NOTIFY_PID 为非零,那么进程 ID 为该值的进程已经向该队列注册接收消息通知了,剩下的字段则提供了与这种通知相关的信息。

  • NOTIFY 是一个与其中一个 sigev_notify 常量对应的值:0 表示 SIGEV_SIGNAL,1表示 SIGEV_NONE,2 表示 SIGEV_THREAD。
  • 如果通知方式是 SIGEV_SIGNAL,那么 SIGNO 字段指出了哪个信号会用来分发消息通知。下面的 shell 会话对这些字段中包含的信息进行了说明。
$ ./mq_notify_sig /mq &
[1] 1920782
$ cat /dev/mqueue/mq
QSIZE:7          NOTIFY:0     SIGNO:10    NOTIFY_PID:1920782
$ kill %1
$ ./mq_notify_thread /mq &
[2] 1921732
[1]   Terminated              ./mq_notify_sig /mq
$ cat /dev/mqueue/mq
QSIZE:7          NOTIFY:2     SIGNO:0     NOTIFY_PID:1921732

7.3 使用另一种 I/O 模型操作消息队列

在 Linux 实现上,消息队列描述符实际上是一个文件描述符,因此可以使用 I/O 多路复用系统调用(select()和 poll())或 epoll API 来监控这个文件描述符。

8 消息队列限制

SUSv3 为 POSIX 消息队列定义了两个限制。

  • MQ_PRIO_MAX
    它定义了一条消息的最大优先级。
  • MQ_OPEN_MAX
    一个实现可以定义这个限制来指明一个进程最多能打开的消息队列数量。由于 Linux将消息队列描述符实现成了文件描述符,因此适用于文件描述符的限制将适用于消息队列描述符。(换句话说,在 Linux 上,每个进程以及系统所能打开的文件描述符的数量限制实际上会应用于文件描述符数量和消息队列描述符数量之和。)
    Linux 还提供了一些/proc 文件来查看和修改(需具备特权)控制 POSIX 消息队列的使用的限制。下面这三个文件位于/proc/sys/fs/ mqueue 目录中。
    msg_max
    这个限制为新消息队列的 mq_maxmsg 特性的取值规定了一个上限(即使用 mq_open()创建队列时 attr.mq_maxmsg 字段的上限值)。这个限制的默认值是 10,最小值是 1(在早于 2.6.28 的内核中是10),最大值由内核常量HARD_MSGMAX 定义,该常量的值是通过公式(131072 / sizeof(void *))计算得来的,在 Linux/x86-32 上其值为 32768。当一个特权进程(CAP_SYS_RESOURCE)调用 mq_open()时 msg_max 限制会被忽略,但 HARD_MSGMAX 仍然担当着 attr.mq_maxmsg 的上限值的角色。
    msgsize_max
    这个限制为非特权进程创建的新消息队列的 mq_msgsize 特性的取值规定了一个上限(即使用 mq_open()创建队列时 attr.mq_msgsize 字段的上限值)。这个限制的默认值是 8192,最小值是 128(在早于 2.6.28 的内核中是 8192),最大值是 1048576(在早于 2.6.28 的内核中是INT_MAX)。当一个非特权进程(CAP_SYS_RESOURCE)调用 mq_open()时会忽略这个限制。
    queues_max
    这是一个系统级别的限制,它规定了系统上最多能够创建的消息队列的数量。一旦达到这个限制,就只有特权进程(CAP_SYS_RESOURCE)才能够创建新队列。这个限制的默认值是 256,其取值可以为范围从 0 到 INT_MAX 之间的任意一个值。
    Linux 还提供了 RLIMIT_MSGQUEUE 资源限制,它可以用来为属于调用进程的真实用户ID 的所有消息队列所消耗的空间规定一个上限。

相关文章:

Linux/AndroidOS中进程间的通信线程间的同步 - 消息队列

本文介绍消息队列&#xff0c;它允许进程之间以消息的形式交换数据。数据的交换单位是整个消息。 POSIX 消息队列是引用计数的。只有当所有当前使用队列的进程都关闭了队列之后才会对队列进行标记以便删除。POSIX 消息有一个关联的优先级&#xff0c;并且消息之间是严格按照优…...

DNA Launcher:打造个性化安卓桌面,开启全新视觉体验

DNA Launcher是一款专为安卓手机设计的桌面美化软件&#xff0c;旨在为用户提供丰富多样的桌面美化选项和全新的操作逻辑。通过这款软件&#xff0c;用户可以轻松调整桌面布局、更换主题、添加个性化元素&#xff0c;打造出独一无二的手机桌面。它支持多分辨率重新布局&#xf…...

Flink SQL DataStream 融合开发模式与动态配置热加载机制实战

一、为什么需要 SQL 与 DataStream 融合开发? 在实时数仓构建中,Flink SQL 的易用性和声明式优势广受欢迎;但遇到业务逻辑复杂、需要灵活控制时,DataStream API 提供了不可替代的灵活性。 而现实中,我们常常遇到如下痛点: 场景问题解决方式多业务线、多个 Kafka Topic,…...

4.2java包装类

在 Java 里&#xff0c;基本数据类型不具备对象的特性&#xff0c;像不能调用方法、参与面向对象的操作等。为了让基本数据类型也能有对象的行为&#xff0c;Java 提供了对应的包装类。同时&#xff0c;自动拆箱和自动装箱机制让基本数据类型和包装类之间的转换更加便捷。 包装…...

在一台服务器上通过 Nginx 配置实现不同子域名访问静态文件和后端服务

一、域名解析配置 要实现通过不同子域名访问静态文件和后端服务&#xff0c;首先需要进行域名解析。在域名注册商或 DNS 服务商处&#xff0c;为你的两个子域名 blog.xxx.com 和 api.xxx.com 配置 A 记录或 CNAME 记录。将它们的 A 记录都指向你服务器的 IP 地址。例如&#x…...

C++23 views::as_rvalue (P2446R2) 深入解析

文章目录 引言C20 Ranges库回顾什么是Rangesstd::views的作用 views::as_rvalue 概述基本概念原型定义工作原理 应用场景容器元素的移动与其他视图适配器结合使用 总结 引言 在C的发展历程中&#xff0c;每一个新版本都会带来一系列令人期待的新特性&#xff0c;这些特性不仅提…...

Mockoon 使用教程

文章目录 一、简介二、模拟接口1、Get2、Post 一、简介 1、Mockoon 可以快速模拟API&#xff0c;无需远程部署&#xff0c;无需帐户&#xff0c;免费&#xff0c;跨平台且开源&#xff0c;适合离线环境。 2、支持get、post、put、delete等所有格式。 二、模拟接口 1、Get 左…...

15.thinkphp的上传功能

一&#xff0e;上传功能 1. 如果要实现上传功能&#xff0c;首先需要建立一个上传表单&#xff0c;具体如下&#xff1a; <form action"http://localhost/tp6/public/upload"enctype"multipart/form-data" method"post"><input type&…...

G口大带宽服务器线路怎么选

G口大带宽服务器线路选择指南 ​​一、线路类型与特点​​ ​​单线&#xff08;电信/联通/移动&#xff09;​​ ​​优势​​&#xff1a;带宽独享、价格低、延迟稳定&#xff0c;适合单一运营商用户集中场景。​​劣势​​&#xff1a;跨运营商访问延迟高&#xff08;如电信…...

低秩适应(LoRA)与量化LoRA(QLoRA)技术解析

LoRA&#xff1a;从线性代数到模型微调 从矩阵分解理解Lora 假设我们有一个大模型中的权重矩阵&#xff0c;形状为1024512&#xff08;包含约52万个参数&#xff09;。传统微调方法会直接更新这52万个参数&#xff0c;这不仅计算量大&#xff0c;而且存在过拟合风险。 LoRA的…...

Webug4.0靶场通关笔记22- 第27关文件包含

目录 一、文件包含 1、原理分析 2、文件包含函数 &#xff08;1&#xff09;include( ) &#xff08;2&#xff09;include_once( ) &#xff08;3&#xff09;require( ) &#xff08;4&#xff09;require_once( ) 二、第27关渗透实战 1、打开靶场 2、源码分析 3、…...

OpenCV CPU性能优化

OpenCV 在 CPU 上的性能优化涉及多个层次&#xff0c;从算法选择到指令级优化。以下是系统的优化方法和实践技巧&#xff1a; 一、基础优化策略 1. 内存访问优化 连续内存布局&#xff1a;优先使用 cv::Mat::isContinuous() 检查 cpp if(mat.isContinuous()) {// 可优化为单循…...

OpenCV进阶操作:图像的透视变换

文章目录 前言一、什么是透视变换&#xff1f;二、透视变换的过程三、OpenCV透视变换核心函数四、文档扫描校正&#xff08;代码&#xff09;1、预处理2、定义轮廓点的排序函数3、定义透视变换函数4、读取原图并缩放5、轮廓检测6、绘制最大轮廓7、对最大轮廓进行透视变换8、旋转…...

MySQL事务隔离机制与并发控制策略

MySQL事务隔离机制与并发控制策略 MySQL事务隔离机制与并发控制策略一、数据库并发问题全景解析二、事务隔离级别深度解析三、MySQL并发控制核心技术1. 多版本并发控制&#xff08;MVCC&#xff09;2. 锁机制 四、隔离级别实现差异对比五、生产环境最佳实践六、高级优化技巧七、…...

【算法学习】递归、搜索与回溯算法(二)

算法学习&#xff1a; https://blog.csdn.net/2301_80220607/category_12922080.html?spm1001.2014.3001.5482 前言&#xff1a; 在&#xff08;一&#xff09;中我们挑了几个经典例题&#xff0c;已经对递归、搜索与回溯算法进行了初步讲解&#xff0c;今天我们来进一步讲解…...

SpringBoot整合PDF导出功能

在实际开发中&#xff0c;我们经常需要将数据导出为PDF格式&#xff0c;以便于打印、分享或存档。SpringBoot提供了多种方式来实现PDF导出功能&#xff0c;下面我们将介绍其中的一些。 HTML 模板转 PDF&#xff08;推荐&#xff09; 通过模板引擎&#xff08;如 Thymeleaf 或…...

关于MySQL 数据库故障排查指南

&#x1f6e0; MySQL 数据库故障排查指南 目标&#xff1a;解决常见数据库问题&#xff0c;保障数据安全与系统稳定运行。 一、常见故障类型概览 故障类型可能原因排查/解决步骤无法连接服务未启动、端口未监听、用户权限不足 查看服务状态&#xff1a; systemctl status my…...

ubuntu yolov5(c++)算法部署

1.安装onnx 1.15.0 首先使用如下命令关闭 anaconda 对后续源码编译的影响&#xff1b; # 禁用当前 conda 环境 conda deactivate# 确保 conda 初始化脚本不会自动激活 base 环境 conda config --set auto_activate_base false# 然后重新打开终端或执行 source ~/.bashrc 1.安…...

基于Centos7的DHCP服务器搭建

一、准备实验环境&#xff1a; 克隆两台虚拟机 一台作服务器&#xff1a;DHCP Server 一台作客户端&#xff1a;DHCP Clinet 二、部署服务器 在网络模式为NAT下使用yum下载DHCP 需要管理员用户权限才能下载&#xff0c;下载好后关闭客户端&#xff0c;改NAT模式为仅主机模式…...

《开源先锋Apache软件基金会:历史沿革、顶级项目与行业影响》

1. Apache软件基金会概述 Apache软件基金会&#xff08;Apache Software Foundation, ASF&#xff09; 是全球最大的开源软件组织之一&#xff0c;成立于1999年&#xff0c;是一个非营利性机构&#xff0c;致力于为公共利益提供开源软件。ASF以“社区主导、共识决策”为核心原…...

Java数据结构——Queue

Queue 队列的概念队列的使用offer和poll方法add和remove方法 设计循环队列队列实现栈栈实现队列 前面所说的Stack是 先入后出的原则&#xff0c;那有没有 先入先出的原则的结构呢&#xff1f;这就是本篇博客所讲的Queue序列就是这个原则 队列的概念 只允许在一段进行插入数据…...

仓储车间安全革命:AI叉车防撞装置系统如何化解操作风险

在现代物流体系中&#xff0c;仓储承担着货物储存、保管、分拣和配送等重要任务。但现代仓储行业的安全现状却不容乐观&#xff0c;诸多痛点严重制约着其发展&#xff0c;其中叉车作业的安全问题尤为突出。相关数据显示&#xff0c;全球范围内&#xff0c;每年因叉车事故导致的…...

深入 FaaS 核心:函数是如何“活”起来的?

深入 FaaS 核心:函数是如何“活”起来的? 在上一篇《你好,Serverless!告别服务器运维的烦恼》中,我们认识了 Serverless 的基本概念,并知道了 FaaS (Function as a Service) 是其核心计算单元,就像一个个“随叫随到”的专业工具人。 那么,这些“工具人”到底是如何被“…...

vue2 两种路由跳转方式

第一种方式&#xff1a;path跳转 第二中写法&#xff1a;用name跳转 路由传参 动态路由传参 案例 通过${} 动态路由传参 动态路由使用params来进行接收 name 传参 总结 传的什么用什么接受...

手机上使用的记录笔记的软件推荐哪一款

在快节奏的生活中&#xff0c;一款好用的手机笔记软件就像随身携带的“外挂大脑”&#xff0c;能帮我们高效记录生活点滴、工作计划和灵感创意。今天&#xff0c;就来给大家详细对比一下Pendo、敬业签、MIGi日历记事本这三款热门笔记软件。 一、Pendo笔记&#xff1a;智能日程…...

SpringBoot 讯飞星火AI WebFlux流式接口返回 异步返回 对接AI大模型 人工智能接口返回

介绍 用于构建基于 WebFlux 的响应式 Web 应用程序。集成了 Spring WebFlux 模块&#xff0c;支持响应式编程模型&#xff0c;构建非阻塞、异步的 Web 应用。WebFlux 使用了非阻塞的异步模型&#xff0c;能够更好地处理高并发请求。适合需要实时数据推送的应用场景。 WebClie…...

Python学习笔记--Django的安装和简单使用(一)

一.简介 Django 是一个用于构建 Web 应用程序的高级 Python Web 框架。Django 提供了一套强大的工具和约定&#xff0c;使得开发者能够快速构建功能齐全且易于维护的网站。Django 遵守 BSD 版权&#xff0c;初次发布于 2005 年 7 月, 并于 2008 年 9 月发布了第一个正式版本 1…...

Java 17配置Jenkins

找到 Java 17 的安装路径 which java ls -l /usr/lib/jvm/ 修改 Jenkins 服务配置 sudo nano /etc/systemd/system/jenkins.service 修改为 [Unit] DescriptionJenkins Automation Server Afternetwork.target[Service] Typesimple Userjenkins Groupjenkins Environment&…...

前端面试每日三题 - Day 28

这是我为准备前端/全栈开发工程师面试整理的第28天每日三题练习&#xff1a; ✅ 题目1&#xff1a;HTTP缓存策略全景解析 核心缓存类型对比表 缓存类型验证方式响应头网络请求消耗强缓存无Cache-Control/Expires无协商缓存If-Modified-Since等ETag/Last-Modified304响应 1.强…...

B站pwn教程笔记-8

接着上次的习题刷&#xff0c;然后补充新的知识。这开始就接触花式栈溢出了 pwn3&#xff08;ret2libc较难&#xff09; 上次已经知道大致思路&#xff0c;现在看看怎么实现。 使用命令 ldd 可看出连接的LIBC是哪个&#xff0c;如下图所示。&#xff08;第一行&#xff09; …...

uniapp项目打包的微信小程序,设置uni-popup type=“bottom“时,底部有空隙

问题&#xff1a; uniapp项目打包的微信小程序&#xff0c;设置uni-popup type"bottom"时&#xff0c;底部有空隙 解决思路&#xff1a; 1、检查代码是否存在样式问题 2、使用微信小程序自带的调试器元素 3、查看源码定位底部是如何出现该空隙的 1、检查代码 检…...

《Zabbix Proxy分布式监控实战:从安装到配置全解析》

注意&#xff1a;实验所需的zabbix服务器的搭建可参考博客 zabbix 的docker安装_docker安装zabbix-CSDN博客 1.1 实验介绍 1.1.1 实验目的 本实验旨在搭建一个基于Zabbix的监控系统&#xff0c;通过安装和配置Zabbix Proxy、MySQL数据库以及Zabbix Agent&#xff0c;实现分…...

zookeeper实现分布式获取全局唯一自增ID的案例。

项目结构 所有配置写在 application.yml 文件中&#xff0c;代码进行了拆分&#xff0c;加入了相关依赖。 1. pom.xml 依赖 <dependencies><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><…...

微信小程序上传视频,解决ios上传完video组件无法播放

1.碰到问题 工单里面上传完视频video组件ios无法播放视频,安卓可以 2.原因 使用了后台接口返回的url拼域名 &#xff0c; 正确做法&#xff1a;使用wx.chooseMedia()里面的tempFilePath&#xff08;本地临时文件路径 (本地路径)&#xff09;&#xff0c;上传好了详情可以使用后…...

硕博士学位论文题目需要注意的几个问题

摘要: 论文题目既要高大上, 又要与别人的区别开. 本贴描述一些基本的思路. 研究生们应该从图书馆找 100 篇博士论文的题目参考&#xff0c;以跳出思维定式. 1. 题目要足够具体 需要把自己的几篇小论文覆盖&#xff0c;且最小的一个帽子 帽子大了就变成书籍的名字&#xff0c;…...

图像匹配导航定位技术 第 8 章

第 8 章 SAR 图像匹配定位技术 目前 &#xff0c;光学传感器已经能获取高分辨率&#xff0c;即与视觉效果相近的目标图像&#xff0c;但是光学传感器容易受到天气变化的影响&#xff0c;从而影响效率。而径雷达 ( synthetic aperture radar&#xff0c;SAR)传感器不仅能获得与…...

四、Hadoop 2.X vs 3.X:特性、架构与性能全解析

Hadoop 2.X 与 Hadoop 3.X 深度对比&#xff1a;版本特性、架构与性能剖析 在大数据处理的浪潮中&#xff0c;Hadoop 凭借其分布式存储与计算的强大能力&#xff0c;成为了业界的核心框架之一。随着技术的不断演进&#xff0c;Hadoop 也经历了多个重要版本的迭代。其中&#x…...

【Linux】FreeRTOS与Linux:实时与通用的终极对比

文章目录 FreeRTOS & Linux1 本质区别2 应用场景3 架构差异4 为什么容易混淆&#xff1f;5 合作与共存总结 FreeRTOS & Linux FreeRTOS 和Linux是两种完全不同的操作系统&#xff0c;设计目标和应用场景有显著区别。 1 本质区别 特性FreeRTOSLinux类型实时操作系统&…...

关于vue-office在vue3工程中的引用报错问题

在vue3项目工程中&#xff0c;根据vue-office文档在vue2中的引用&#xff1a; //引入VueOfficeDocx组件 相关样式import VueOfficeDocx from vue-office/docx;import vue-office/docx/lib/index.css; 报错信息&#xff1a; [plugin:vite:import-analysis] Failed to resolve …...

【NLP 71、常见大模型的模型结构对比】

三到五年的深耕&#xff0c;足够让你成为一个你想成为的人 —— 25.5.8 模型名称位置编码Transformer结构多头机制Feed Forward层设计归一化层设计线性层偏置项激活函数训练数据规模及来源参数量应用场景侧重GPT-5 (OpenAI)RoPE动态相对编码混合专家架构&#xff08;MoE&#…...

Java详解LeetCode 热题 100(13):LeetCode 53:最大子数组和(Maximum Subarray)详解

文章目录 1. 题目描述2. 理解题目3. 解题思路3.1 暴力法3.1.1 O(n) 暴力解法3.1.2 O(n) 优化的暴力解法3.2 分治法3.3 动态规划(Kadane算法)3.3.1 动态规划基本思路3.3.2 Kadane算法(空间优化版本)3.4 前缀和方法4. 具体实例解析5. 代码优化与技巧5.1 处理空数组和边界情况…...

数字化驱动下的智慧物流与零售创新:全流程无人仓与定制开发开源AI智能名片S2B2C商城小程序的协同实践

摘要&#xff1a;本文以京东"全球首个全流程无人仓"为技术载体&#xff0c;结合"定制开发开源AI智能名片S2B2C商城小程序"的零售创新实践&#xff0c;探讨数字化技术如何重构物流与零售场景。研究揭示&#xff0c;京东通过全流程无人仓实现仓储效率提升4倍…...

从“工地砌砖”到“工厂造房”:模块化集成建筑(MiC建筑)如何重塑建筑业

在城市化进程加速与资源环境约束加剧的双重挑战下&#xff0c;建筑业正经历着一场深刻变革。模块化集成建筑&#xff08;Modular Integrated Construction&#xff0c;简称MiC&#xff09;以“工厂造楼”为核心理念&#xff0c;通过将建筑拆解为标准化模块并在工厂完成全流程预…...

idea出现tomcat不能正确部署的问题--解决方案

启动tomcat 报如下错误&#xff1a;&#xff08;是因为已经在其他tomcat的中使用了这两个端口&#xff09; 改成新端口 注意&#xff1a;不管是新增了页面&#xff0c;还是修改了页面&#xff0c;都需要重新部署项目&#xff0c;方法就是点击下面的绿色图标。否则新的页面操作不…...

编专利或委托他人编专利属于学术不端行为吗?

原文链接&#xff1a;编专利或委托他人编专利属于学术不端行为吗&#xff1f; 自己编专利或委托他人编专利属于学术不端吗&#xff1f; 5月4日&#xff0c;一篇题为《针对性护理干预在子宫肌瘤围手术期的情绪和生活质量临床应用效果》的论文&#xff0c;受到网友的广泛议论。…...

IEEE PRMVAI Workshop 17 | 智能医疗数据分析与应用

科研小伙伴们看过来&#xff01;2025 年 IEEE 第三届模式识别、机器视觉和人工智能国际会议旗下的 Workshop 17——“Intelligent Health Monitoring and Inspection of Infrastructure&#xff08;智能医疗数据分析与应用&#xff09;” 超值得关注&#xff01; &#x1f4c5…...

网工实验——OSPF配置

网络拓扑图 配置 1.为每个路由器配置接口&#xff08;略&#xff09;&#xff08;详细见RIP实验&#xff09; 2.配置OSPF AR1 [AR1]ospf [AR1-ospf-1]area 1 [AR1-ospf-1-area-0.0.0.1]network 172.16.1.1 0.0.0.0 #精确配置网络&#xff0c;也可以像下面那条命令那样配置 …...

图灵码上爬第5题:屠龙刀--爬虫逆向

别问我&#xff0c;问我就是小菜鸡记录下学习成果&#xff0c;哈哈哈哈 第五题其实跟第四题的逻辑差不多&#xff0c;只不过加了点简单混淆的代码 题目&#xff1a; 请求参数经过特殊处理&#xff0c;适合JavaScript逆向入门练习 思路&#xff1a; 首先打开开发者工具&…...

Linux网络编程day7 线程池

线程池 typedef struct{void*(*function)(void*); //函数指针&#xff0c;回调函数void*arg; //上面函数的参数 }threadpool_task_t; //各子线程任务的结构体/*描述线程池相关信息*/struct threadpool_t{pthread_mutex_t lock; …...

【SpringMVC】详解cookie,session及实战

目录 1.前言 2.正文 2.1cookie与session概念 2.2返回cookie参数 2.3设置session 3.小结 1.前言 哈喽大家好吖&#xff0c;今天继续来给大家来分享SpringMVC的学习&#xff0c;今天主要带来的是cookie与session的讲解以及通过postman和fiddler来实战&#xff0c;废话不多…...