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

系统编程_进程间通信机制_消息队列与共享内存

消息队列概述

  1. 消息有类型:每条消息都有一个类型,就像每封信都有一个标签,方便分类和查找。
  2. 消息有格式:消息的内容有固定的格式,就像每封信都有固定的信纸格式。
  3. 随机查询:你可以按类型读取消息,不一定要按顺序读取,就像你可以按标签找信,而不是按收到的顺序。
  4. 多进程读写:多个进程可以同时向消息队列写入或读取消息,就像多个邮递员可以同时投递或取走信件。
  5. 读出即删除:从消息队列中读出消息后,消息就会被删除,就像你从信箱里取出信后,信箱里就没有这封信了。
  6. 唯一标识符:每个消息队列都有一个唯一的标识符,就像每个信箱都有一个唯一的编号。
  7. 持久存在:消息队列会一直存在,直到内核重启或你手动删除它,就像信箱会一直在,除非你把它拆掉。

Ubuntu 12.04 中的消息队列限制

  • 每条消息最多可以有 8K 字节,就像每封信最多可以写 8K 字节的内容。
  • 每个消息队列最多可以存储 16K 字节的消息,就像每个信箱最多可以放 16K 字节的信件。
  • 系统中最多可以有 1609 个消息队列,就像一个城市最多可以有 1609 个信箱。
  • 系统中最多可以有 16384 条消息,就像一个城市最多可以有 16384 封信。

 消息队列的使用方法一般是:

发送者(邮递员)的步骤

  1. 获取消息队列的键值:

    • 就像邮递员要投递,首先需要知道信箱的编号(消息队列的键值)。
    • 通过 msgget 函数获取消息队列的消息队列的键值。
  2. 将数据放入结构体并发送

    • 你要发送一个信件(消息),需要把信件装进一个贴有邮票(类型)的盒子(结构体)。
    • 使用 msgsnd 函数将这个带有标识的消息发送到消息队列。

接收者(拿信的人)的步骤

  1. 获取消息队列的 ID

    • 就像你要从信箱取信件,首先需要知道信箱的编号(消息队列的键值)。
    • 通过 msgget 函数获取消息队列的键值。
  2. 读取指定标识的消息

    • 你要取出特定邮票(类型)的包裹(消息)。
    • 使用 msgrcv 函数从消息队列中读取带有指定类型的消息。

查看系统中已创建的消息队列的指令

使用 ipcs 命令可以查看系统中已经创建的消息队列,以及其他IPC资源(共享内存和信号量)。

ipcs 命令参数

  • -m:查看系统共享内存信息。
  • -q:查看系统消息队列信息。
  • -s:查看系统信号量信息。
  • -a:显示系统内所有的IPC信息。
# 查看系统中所有的消息队列 
ipcs -q

移除IPC资源

使用 ipcrm 命令可以移除指定的IPC资源,包括共享内存段、信号量和消息队列。

ipcrm 命令参数

  • -m shmid:移除用 shmid 标识的共享内存段。
  • -s semid:移除用 semid 标识的信号量。
  • -q msgid:移除用 msgid 标识的消息队列。
ipcrm -q 12345  # 删除消息队列 12345 12345 是共享内存段的标识符(msgid)

让我们来区分一下消息队列中的“标识”(msgid)和“类型”(mtype)。

标识(msgid)

  • 定义msgid 是消息队列的标识符,用于唯一标识一个消息队列。
  • 作用:当你创建或获取一个消息队列时,系统会返回一个 msgid,你可以通过这个 msgid 来进行后续的消息发送、接收和删除操作。
  • 示例:就像一个快递柜的编号,你需要知道这个编号才能往里面放包裹或取包裹。

类型(mtype)

  • 定义mtype 是消息的类型,用于标识消息的类别。
  • 作用:在发送和接收消息时,可以根据 mtype 来区分不同类型的消息。接收消息时,可以指定接收某一类型的消息。
  • 示例:就像包裹上的标签,用于区分不同的包裹类型,比如“普通包裹”、“快递包裹”等。

区别

  • msgid 是消息队列的唯一标识符,用于操作整个消息队列。
  • mtype 是消息的类型,用于区分消息队列中的不同消息。

 消息队列函数

ftok 函数

ftok 函数用于生成一个唯一的键值(key),这个键值用于标识IPC(进程间通信)资源,比如消息队列、共享内存和信号量。这个键值就像一个独特的“身份证号”,确保每个IPC资源都有一个唯一的标识。

函数原型

#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);

参数解释

  • pathname:这是一个文件路径名。ftok 函数会使用这个路径名来生成键值。
  • proj_id:这是一个项目ID,是一个非0的整数(只有低8位有效)。(随便填)

返回值

  • 成功:返回一个唯一的键值(key),就像生成了一个独特的身份证号。
  • 失败:返回 -1,表示生成键值失败。

msgget 函数的作用

msgget 函数用于创建一个新的消息队列或打开一个已经存在的消息队列。消息队列就像一个共享的邮箱,不同的进程可以通过这个邮箱来发送和接收消息。

函数原型

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

参数解释

  • key:这是消息队列的“钥匙”。只要不同的进程使用相同的钥匙(key),它们就能访问同一个消息队列。这个钥匙可以通过 ftok 函数生成。
  • msgflg:这是一个标志,决定了函数的行为和消息队列的权限。它可以是以下几种值的组合:
    • IPC_CREAT:如果消息队列不存在,就创建一个新的。
    • IPC_EXCL:如果消息队列已经存在,并且同时指定了 IPC_CREAT,那么函数会返回错误。
    • 权限位:设置消息队列的访问权限,比如读写权限,类似于文件的权限设置。

返回值

  • 成功:返回消息队列的标识符(ID),这是一个非负整数。
  • 失败:返回 -1,并设置 errno 以指示错误类型。

msgsnd 函数的作用

msgsnd 函数用于将新消息添加到消息队列中。你可以把它想象成把一封信放进一个共享的邮箱里,其他进程可以从这个邮箱里取出这封信。

函数原型

#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数解释

  • msqid:消息队列的标识符。就像邮箱的编号,你需要知道这个编号才能往里面放信。
  • msgp:待发送消息结构体的地址。就像你要发送的信的地址。
  • msgsz:消息正文的字节数。就像信的内容有多长。
  • msgflg:函数的控制属性,决定了消息发送的行为。可以是以下值之一:
    • 0:如果消息队列已满,msgsnd 会阻塞(等待),直到有空间可以放入消息。就像你要等到邮箱有空位才能放信。
    • IPC_NOWAIT:如果消息队列已满,msgsnd 会立即返回,而不是等待。就像你看到邮箱满了,直接走开,不等空位。

返回值

  • 成功:返回 0,表示消息成功发送。
  • 失败:返回 -1,并设置 errno 以指示错误类型。

msgrcv 函数的作用

msgrcv 函数用于从消息队列中接收一个消息。一旦成功接收消息,这条消息就会从消息队列中删除。你可以把它想象成从共享的邮箱里取出一封信,取出后这封信就不在邮箱里了。

函数原型

#include <sys/types.h>
#inclde <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数解释

  • msqid:消息队列的标识符。就像邮箱的编号,你需要知道这个编号才能从里面取信。
  • msgp:存放消息结构体的地址。就像你要把取出的信放到哪里。
  • msgsz:消息正文的字节数。就像信的内容有多长。
  • msgtyp:消息的类型,决定你要取哪种类型的消息。可以有以下几种情况:
    • msgtyp = 0:取队列中的第一个消息。
    • msgtyp > 0:取队列中类型为 msgtyp 的消息。
    • msgtyp < 0:取队列中类型值小于或等于 msgtyp 绝对值的消息,如果有多个这样的消息,则取类型值最小的消息。

msgflg:函数的控制属性

  • 0msgrcv 调用会阻塞,直到成功接收消息为止。就像你一直等到邮箱里有信为止。
  • MSG_NOERROR:如果返回的消息字节数比 msgsz 多,则消息会被截短到 msgsz 字节,不通知发送进程。
  • IPC_NOWAIT:调用进程会立即返回。如果没有收到消息则立即返回 -1。就像你看到邮箱里没有信,直接走开,不等信。

注意事项

  • 如果消息队列中有多种类型的消息,msgrcv 会按消息类型获取,而不是按先进先出的顺序。
  • 在获取某类型消息时,如果队列中有多条此类型的消息,则获取最先添加的消息,即先进先出。

返回值

  • 成功:返回读取消息的长度。
  • 失败:返回 -1,并设置 errno 以指示错误类型。 

msgctl 函数的作用

msgctl 函数就像是一个多功能的管理员,可以对消息队列进行各种操作,比如查看消息队列的状态、修改它的属性,或者直接删除它。

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
intmsgctl(intmsqid,intcmd,structmsqid_ds*buf);

参数解释

  1. msqid:这是消息队列的编号,就像每个邮箱都有一个编号一样,你需要这个编号来操作特定的消息队列。

  2. cmd:这是你告诉管理员要做什么的指令。常见的指令有:

    • IPC_RMID:删除消息队列。就像把邮箱整个拆掉,里面的信件也都没了。
    • IPC_STAT:查看消息队列的当前状态。就像你让管理员把邮箱的详细信息告诉你,比如有多少信件、谁发的等等。
    • IPC_SET:修改消息队列的属性。就像你让管理员把邮箱的某些设置改一下,比如谁可以往里面放信,谁可以取信。
  3. buf:这是一个结构体的地址,用来存放或修改消息队列的属性。就像你给管理员一个表格,他会把邮箱的信息填到这个表格里,或者根据这个表格里的信息修改邮箱的设置。

返回值

  • 成功:返回 0,表示操作成功。
  • 失败:返回 -1,并设置 errno 来指示错误类型。就像管理员告诉你操作失败了,并给你一个错误代码,说明哪里出了问题。

练习:消息队列实现多人聊天程序 

下面 我们来根据这个流程图来完善这个练习吧。

主函数

根据流程图可以看到主函数首先检查命令行参数的数量。程序需要两个参数:用户名和接收类型。如果参数数量不对,程序会打印使用说明并退出。

  • argv[1] 是用户名,例如 "Tom"。
  • argv[2] 是接收类型,例如 "1"。

如果成功接下来,程序将这两个参数传递给 chat_process 函数,开始聊天进程。

 主函数部分代码

// 主函数,程序的入口点
int main(int argc, char *argv[]) {// 检查命令行参数数量是否正确if (argc != 3) {// 参数数量不正确时,输出使用说明并退出程序fprintf(stderr, "Usage: %s <name> <recv_type>\n", argv[0]);exit(1);}// 获取命令行参数:姓名和接收类型const char *name = argv[1];long recv_type = atol(argv[2]);// 调用函数处理聊天逻辑chat_process(name, recv_type);// 程序正常结束return 0;
}

详细解释:

这段代码的主要功能是作为聊天程序的入口点,负责解析命令行参数,检查参数的正确性,并启动聊天进程。具体来说,它:

  1. 检查命令行参数的数量是否正确。
  2. 从命令行参数中提取用户名和接收类型。
  3. 调用chat_process函数,启动聊天进程,实现消息的发送和接收。

通过这种方式,程序能够根据用户输入的参数启动相应的聊天功能,实现用户之间的消息传递。

结构体

typedef struct msg {long type;       // 接收者类型char text[100];  // 发送内容char name[20];   // 发送者姓名
} MSG;

聊天进程函数

接下面 我们就要开始创建消息队列,创建进程了。

基本逻辑就是

1. 创建消息队列

  • 使用 ftok 生成一个唯一的键值。

2. 创建子进程

  • 使用 fork 创建一个子进程。
  • 如果 fork 失败,打印错误信息并退出。

3. 子进程逻辑

  • 子进程调用 receive_message 函数,负责从消息队列中接收并打印消息。

4. 父进程逻辑

  • 父进程在一个无限循环中调用 send_message 函数,负责发送消息到消息队列。
  • 父进程在循环结束后,调用 wait 等待子进程结束。
/*** 聊天处理函数* 根据提供的名称和接收类型,初始化消息队列,并根据进程类型处理消息* * @param name 用户名,用于标识消息的发送者或接收者* @param recv_type 接收消息的类型,用于区分不同类型的接收逻辑*/
void chat_process(const char *name, long recv_type) {// 生成消息队列的键值,用于后续的消息队列识别key_t key = ftok("/tmp", 65);// 使用键值创建或获取消息队列,确保消息队列的访问权限int msgid = msgget(key, 0666 | IPC_CREAT);if (msgid < 0) {// 错误处理:获取消息队列失败perror("msgget");exit(1);}// 创建子进程,用于并行处理接收和发送消息pid_t pid = fork();if (pid < 0) {// 错误处理:进程创建失败perror("fork");exit(1);} else if (pid == 0) {// 子进程负责接收消息receive_message(msgid, name, recv_type);} else {// 父进程负责发送消息while (1) {send_message(msgid, name);}// 等待子进程结束,避免僵尸进程wait(NULL);}
}

详细讲解:

1. 生成消息队列键值

key_t key = ftok("/tmp", 65);

ftok 函数用于生成一个唯一的键值,用于创建消息队列。"/tmp" 是一个路径名,65 是一个项目 ID。这两个参数的组合确保了键值的唯一性。

2. 创建或获取消息队列

int msgid = msgget(key, 0666 | IPC_CREAT);
if (msgid < 0) {perror("msgget");exit(1);
}

msgget 函数用于创建或获取一个消息队列。key 是之前生成的键值,0666 | IPC_CREAT 指定了权限和标志。0666 表示所有用户都有读写权限,IPC_CREAT 表示如果消息队列不存在则创建。如果 msgget 失败,程序会打印错误信息并退出。

3. 创建子进程

pid_t pid = fork();
if (pid < 0) {perror("fork");exit(1);
} else if (pid == 0) {// 子进程负责接收消息receive_message(msgid, name, recv_type);
} else {// 父进程负责发送消息while (1) {send_message(msgid, name);}wait(NULL);
}

fork 函数用于创建一个子进程。fork 函数会返回两次:

在父进程中,返回子进程的 PID。

在子进程中,返回 0。

如果 fork 失败,程序会打印错误信息并退出。

子进程

子进程调用 receive_message 函数,负责接收消息。receive_message 函数会不断从消息队列中读取属于当前用户的消息,并打印出来。

父进程

父进程在一个无限循环中调用 send_message 函数,负责发送消息。send_message 函数会提示用户输入接收者名字和消息内容,然后根据接收者名字确定消息类型,并将消息发送到消息队列。

父进程在发送消息的循环结束后,调用 wait 函数等待子进程结束。

 发送消息函数

这个呢就是send_message 函数,首先提示用户输入接收者名字和消息内容。然后根据接收者名字确定消息类型,并将消息内容和发送者名字填充到 MSG 结构体中。最后,使用 msgsnd 函数将消息发送到消息队列。

// 发送消息到指定接收者
// 参数:
//   msgid: 消息队列标识符
//   name: 发送者姓名
void send_message(int msgid, const char *name) {MSG snd_msg; // 定义待发送的消息结构体char recipient[20]; // 用于存储接收者姓名的字符数组// 提示并获取用户输入的接收者姓名printf("Enter recipient name: ");fgets(recipient, sizeof(recipient), stdin);recipient[strcspn(recipient, "\n")] = 0; // 去掉输入中的换行符// 根据接收者名字确定消息类型if (strcmp(recipient, "Tom") == 0) {snd_msg.type = 1;} else if (strcmp(recipient, "Lili") == 0) {snd_msg.type = 2;} else if (strcmp(recipient, "Luxi") == 0) {snd_msg.type = 3;} else {// 如果接收者未知,则打印错误信息并返回printf("Unknown recipient: %s\n", recipient);return;}// 设置消息结构体中的发送者姓名strcpy(snd_msg.name, name);// 提示并获取用户输入的消息内容printf("Enter message to send: ");fgets(snd_msg.text, sizeof(snd_msg.text), stdin);snd_msg.text[strcspn(snd_msg.text, "\n")] = 0; // 去掉输入中的换行符// 发送消息到消息队列if (msgsnd(msgid, &snd_msg, sizeof(snd_msg) - sizeof(long), 0) < 0) {// 如果发送失败,则打印错误信息并退出程序perror("msgsnd");exit(1);}
}
  1. 函数声明和变量定义

    void send_message(int msgid, const char *name) {MSG snd_msg;char recipient[20];
    
    • send_message 函数接受两个参数:消息队列的标识符 msgid 和发送者的名字 name
    • 定义了一个 MSG 结构体变量 snd_msg 用于存储要发送的消息。
    • 定义了一个字符数组 recipient 用于存储接收者的名字。
  2. 输入接收者名字

    printf("Enter recipient name: ");
    fgets(recipient, sizeof(recipient), stdin);
    recipient[strcspn(recipient, "\n")] = 0; // 去掉换行符
    
    • 提示用户输入接收者的名字。
    • 使用 fgets 从标准输入读取接收者的名字,并存储在 recipient 数组中。
    • 使用 strcspn 去掉输入字符串末尾的换行符。
  3. 确定消息类型

    if (strcmp(recipient, "Tom") == 0) {snd_msg.type = 1;
    } else if (strcmp(recipient, "Lili") == 0) {snd_msg.type = 2;
    } else if (strcmp(recipient, "Luxi") == 0) {snd_msg.type = 3;
    } else {printf("Unknown recipient: %s\n", recipient);return;
    }
    
    • 根据接收者的名字确定消息类型:
      • 如果接收者是 "Tom",消息类型设为 1。
      • 如果接收者是 "Lili",消息类型设为 2。
      • 如果接收者是 "Luxi",消息类型设为 3。
      • 如果接收者名字不在上述列表中,打印错误信息并返回。
  4. 填充消息内容

    strcpy(snd_msg.name, name);
    printf("Enter message to send: ");
    fgets(snd_msg.text, sizeof(snd_msg.text), stdin);
    snd_msg.text[strcspn(snd_msg.text, "\n")] = 0; // 去掉换行符
    
    • 将发送者的名字复制到 snd_msg.name 中。
    • 提示用户输入要发送的消息内容。
    • 使用 fgets 从标准输入读取消息内容,并存储在 snd_msg.text 中。
    • 使用 strcspn 去掉输入字符串末尾的换行符。
  5. 发送消息

    if (msgsnd(msgid, &snd_msg, sizeof(snd_msg) - sizeof(long), 0) < 0) {perror("msgsnd");exit(1);
    }
    
    • 使用 msgsnd 系统调用将消息发送到消息队列。
    • 如果发送失败,打印错误信息并退出程序。

 接收消息函数

函数持续监听指定的消息队列,接收指定类型的消患当接收到消息时,将打印出接收者名称,消息发送者名称以及消息内容函数通过调用msgrcv接收消息,如果接收失败则输出错误信息并终止程序


void receive_message(int msgid, const char *name, long type) {MSG recv_msg;while (1) {if (msgrcv(msgid, &recv_msg, sizeof(recv_msg) - sizeof(long), type, 0) < 0) {perror("msgrcv");exit(1);}printf("%s received message from %s: %s\n", name, recv_msg.name, recv_msg.text);}
}
  1. 函数声明

    void receive_message(int msgid, const char *name, long type)
    

    这个函数接收三个参数:

    • msgid:消息队列的标识符。
    • name:接收者的名字。
    • type:消息类型,用于过滤接收到的消息。
  2. 变量声明

    MSG recv_msg;
    

    CopyInsert

    声明一个 MSG 类型的变量 recv_msg,用于存储接收到的消息。

  3. 无限循环

    while (1) {
    

    使用一个无限循环来持续接收消息。

  4. 接收消息

    if (msgrcv(msgid, &recv_msg, sizeof(recv_msg) - sizeof(long), type, 0) < 0) {perror("msgrcv");exit(1);
    }
    

    调用 msgrcv 函数从消息队列中接收消息。参数解释如下:

    • msgid:消息队列的标识符。
    • &recv_msg:指向接收消息的缓冲区。
    • sizeof(recv_msg) - sizeof(long):接收消息的大小,减去 long 类型的大小是因为 msgrcv 函数需要的是消息数据的大小,而不是整个结构体的大小。
    • type:消息类型,用于过滤消息。
    • 0:标志位,这里设置为0表示默认行为。

    如果 msgrcv 调用失败,打印错误信息并退出程序。

  5. 打印接收到的消息

    printf("%s received message from %s: %s\n", name, recv_msg.name, recv_msg.text);
    

    打印接收到的消息,包括接收者的名字、发送者的名字和消息内容。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/wait.h>typedef struct msg {long type;       // 接收者类型char text[100];  // 发送内容char name[20];   // 发送者姓名
} MSG;void send_message(int msgid, const char *name) {MSG snd_msg;char recipient[20];printf("Enter recipient name: ");fgets(recipient, sizeof(recipient), stdin);recipient[strcspn(recipient, "\n")] = 0; // 去掉换行符// 根据接收者名字确定消息类型if (strcmp(recipient, "Tom") == 0) {snd_msg.type = 1;} else if (strcmp(recipient, "Lili") == 0) {snd_msg.type = 2;} else if (strcmp(recipient, "Luxi") == 0) {snd_msg.type = 3;} else {printf("Unknown recipient: %s\n", recipient);return;}strcpy(snd_msg.name, name);printf("Enter message to send: ");fgets(snd_msg.text, sizeof(snd_msg.text), stdin);snd_msg.text[strcspn(snd_msg.text, "\n")] = 0; // 去掉换行符if (msgsnd(msgid, &snd_msg, sizeof(snd_msg) - sizeof(long), 0) < 0) {perror("msgsnd");exit(1);}
}void receive_message(int msgid, const char *name, long type) {MSG recv_msg;while (1) {if (msgrcv(msgid, &recv_msg, sizeof(recv_msg) - sizeof(long), type, 0) < 0) {perror("msgrcv");exit(1);}printf("%s received message from %s: %s\n", name, recv_msg.name, recv_msg.text);}
}void chat_process(const char *name, long recv_type) {key_t key = ftok("/tmp", 65);int msgid = msgget(key, 0666 | IPC_CREAT);if (msgid < 0) {perror("msgget");exit(1);}pid_t pid = fork();if (pid < 0) {perror("fork");exit(1);} else if (pid == 0) {// 子进程负责接收消息receive_message(msgid, name, recv_type);} else {// 父进程负责发送消息while (1) {send_message(msgid, name);}wait(NULL);}
}int main(int argc, char *argv[]) {if (argc != 3) {fprintf(stderr, "Usage: %s <name> <recv_type>\n", argv[0]);exit(1);}const char *name = argv[1];long recv_type = atol(argv[2]);chat_process(name, recv_type);return 0;
}

运行的时候把打开多个程序就可以了。

相关文章:

系统编程_进程间通信机制_消息队列与共享内存

消息队列概述 消息有类型&#xff1a;每条消息都有一个类型&#xff0c;就像每封信都有一个标签&#xff0c;方便分类和查找。消息有格式&#xff1a;消息的内容有固定的格式&#xff0c;就像每封信都有固定的信纸格式。随机查询&#xff1a;你可以按类型读取消息&#xff0c;…...

一种免费的离线ocr-汉字识别率100%

一般我们手机中常用的ocr库有&#xff0c;Tesseract&#xff0c;paddle ocr&#xff0c;EasyOCR&#xff0c; ocrLite等等&#xff0c;这些ocr库中百度的paddle ocr效果最好&#xff0c;但是再好的效果也会偶尔识别错几个汉字。当我们在做自动化脚本过程中&#xff0c;如果识别…...

Maven 工程中的pom.xml 文件(图文)

基本信息 单工程项目【pom.xml文件】中最基本的信息。 依赖引入 可以在Maven 中央仓库查找所需依赖&#xff1a;【直达&#xff1a;https://mvnrepository.com/】。 在【dependencies】标签中添加所需依赖。 <dependency><groupId>com.baomidou</groupId&g…...

图像预处理-模板匹配

就是用模板图在目标图像中不断的滑动比较&#xff0c;通过某种比较方法来判断是否匹配成功,找到模板图所在的位置。 - 不会有边缘填充。 - 类似于卷积&#xff0c;滑动比较&#xff0c;挨个比较象素。 - 返回结果res大小是&#xff1a;目标图大小-模板图大小1&#xff08;H-…...

操作系统学习笔记

2.4 死锁 在学习本节时&#xff0c;请读者思考以下问题&#xff1a; 1&#xff09;为什么会产生死锁&#xff1f;产生死锁有什么条件&#xff1f; 2&#xff09;有什么办法可以解决死锁问题&#xff1f; 学完本节&#xff0c;读者应了解死锁的由来、产…...

5.4.云原生与服务网格

目录 1. Kubernetes与微服务集成 1.1 容器化部署规范 • 多环境配置管理&#xff08;ConfigMap与Nacos联动&#xff09; • 健康检查探针配置&#xff08;Liveness/Readiness定制策略&#xff09; 1.2 弹性服务治理 • HPA自动扩缩容规则设计 • Sentinel指标驱动弹性伸缩 2…...

[特殊字符]‍[特殊字符]Linux驱动开发入门 | 并发与互斥机制详解

文章目录 &#x1f468;‍&#x1f4bb;Linux驱动开发入门 | 并发与互斥机制详解&#x1f4cc;为什么驱动中需要并发和互斥控制&#xff1f;&#x1f4a1;常见的并发控制机制&#x1f510;自旋锁和信号量通俗理解&#x1f300;自旋锁&#xff08;Spinlock&#xff09;——“厕所…...

时序数据库IoTDB自研的Timer模型介绍

一、引言 时序数据库在支持时序特性写入、存储、查询等功能的基础上&#xff0c;正逐步向深度分析领域迈进。自动化异常监测与智能化趋势预测已成为时序数据管理的核心需求。为了满足这些需求&#xff0c;时序数据库IoTDB团队积极探索&#xff0c;成功自研推出了面向时间序列的…...

RabbitMQ 详解(核心概念)

本文是博主在梳理 RabbitMQ 知识的过程中&#xff0c;将所遇到和可能会遇到的基础知识记录下来&#xff0c;用作梳理 RabbitMQ 的整体架构和功能的线索文章&#xff0c;通过查找对应的知识能够快速的了解对应的知识而解决相应的问题。 文章目录 一、RabbitMQ 是什么&#xff1f…...

【数据结构和算法】6. 哈希表

本文根据 数据结构和算法入门 视频记录 文章目录 1. 哈希表的概念1.1 哈希表的实现方式1.2 哈希函数&#xff08;Hash Function&#xff09;1.3 哈希表支持的操作 2. Java实现 在前几章的学习中&#xff0c;我们已经了解了数组和链表的基本特性&#xff0c;不管是数组还是链表…...

RHCE第三次作业 搭建dns的正向解析服务器

server为服务器 client为客户端 设置主配置文件 在server下&#xff1a; [rootServer ~]#vim /etc/named.conf #进入到配置页面&#xff0c;并修改 设置区域文件 [rootServer ~]# vim /etc/named.rfc1912.zones 设置域名解析文件 [rootServer named]# cd /var/named…...

【每天一个知识点】如何解决大模型幻觉(hallucination)问题?

解决大模型幻觉&#xff08;hallucination&#xff09;问题&#xff0c;需要从模型架构、训练方式、推理机制和后处理策略多方面协同优化。 &#x1f9e0; 1. 引入 RAG 框架&#xff08;Retrieval-Augmented Generation&#xff09; 思路&#xff1a; 模型生成前先检索知识库中…...

Python深拷贝与浅拷贝:避开对象复制的陷阱

目录 一、为什么需要区分深浅拷贝&#xff1f; 二、内存中的对象真相 三、浅拷贝的真相 四、深拷贝的奥秘 五、自定义对象的拷贝 六、性能对比实验 七、常见陷阱与解决方案 八、最佳实践指南 九、现代Python的拷贝优化 结语 一、为什么需要区分深浅拷贝&#xff1f; …...

批量处理多个 Word 文档:插入和修改页眉页脚,添加页码的方法

Word 页眉页脚的设置在日常工作中非常常见&#xff0c;尤其是需要统一格式的文档&#xff0c;如毕业论文、公司内部资料等。在这些文档中&#xff0c;页眉页脚通常包含时间、公司标志、文档标题、文件名或作者姓名等信息。有时&#xff0c;我们不仅需要简单的文字页眉页脚&…...

大语言模型(LLM)的Prompt Engineering:从入门到精通

大语言模型&#xff08;LLM&#xff09;的Prompt Engineering&#xff1a;从入门到精通 系统化学习人工智能网站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 引言&#xff1a;Prompt Engineering——解锁AI生产力的金钥匙 当ChatGPT在2023年引爆…...

poi生成横向文档以及复杂表头

代码: //创建页面并且创建横向A4XWPFDocument doc new XWPFDocument();CTDocument1 document doc.getDocument();CTBody body document.getBody();if (!body.isSetSectPr()) {body.addNewSectPr();}CTSectPr section body.getSectPr();if (!section.isSetPgSz()) {section.…...

深入剖析TCP协议(内容二):从OSI与TCP/IP网络模型到三次握手、四次挥手、状态管理、性能优化及Linux内核源码实现的全面技术指南

文章目录 常见问题TCP和UDPISNUDPTCP数据可靠性TCP协议如何提高传输效率TCP如何处理拥塞 SocketTCP源码tcp_v4_connect()sys_accept()tcp_accept()三次握手客户端发送SYN段服务端发送SYN和ACK处理客户端回复确认ACK段服务端收到ACK段 常见问题 TCP和UDP TCP和UDP的区别&#…...

流程架构是什么?为什么要构建流程架构,以及如何构建流程结构?

本文从&#xff1a;流程架构是什么&#xff1f;为什么要构建流程架构&#xff1f;如何构建流程结构三个方面来介绍。 一、首先&#xff0c;我们来了解流程架构是什么&#xff1f; 流程架构是人体的骨架&#xff0c;是大楼的砌筑&#xff0c;是课本的目录&#xff0c;是流程管理…...

Visium HD多样本拼片拆分

Visium HD实验的时候一个捕获区域内可以包含多个样本拼片&#xff08;例如多个组织切片或不同样本的排列&#xff09;是常见的实验设计&#xff0c;多样本拼片能够提升实验效率&#xff0c;单张玻片处理多个样本&#xff0c;降低试剂和测序成本&#xff0c;后续分析的时候只需要…...

3DMAX零售商店生成插件RetailStore自定义贴图库方法详解

3DMAX零售商店生成插件——RetailStore&#xff0c;是一款兼具简洁性与复杂性的工具&#xff0c;专为通过样条线快速创建零售商店而设计。用户只需绘制一条街道廓线&#xff0c;轻点鼠标&#xff0c;即可生成一排随机的零售商店。该插件会在每个样条线段上自动生成一个店铺&…...

从性能到安全:大型网站系统架构演化的 13 个核心维度

大型网站系统架构的演化是一个复杂的过程&#xff0c;涉及到多个维度的技术内容&#xff0c;从关键维度进行详细分析&#xff1a; 1.性能维度 缓存技术&#xff1a;包括浏览器缓存、CDN&#xff08;内容分发网络&#xff09;缓存、服务器端缓存&#xff08;如 Memcached、Red…...

昆仑万维开源SkyReels-V2,近屿智能紧跟AI技术趋势

昆仑万维 SkyReels 团队正式发布并开源全球首个采用扩散强迫框架的无限时长电影生成模型 SkyReels-V2&#xff0c;其通过融合多模态大语言模型、多阶段预训练、强化学习与扩散强迫框架实现协同优化&#xff0c;推动视频生成技术进入新阶段。该模型聚焦解决现有技术在提示词遵循…...

Milvus(4):创建 Collections

1 创建 Collections 可以通过定义 Schema、索引参数、度量类型以及创建时是否加载来创建一个 Collection。 1.1 集合概述 Collection 是一个二维表&#xff0c;具有固定的列和变化的行。每列代表一个字段&#xff0c;每行代表一个实体。要实现这样的结构化数据管理&#xff0c…...

数据预处理:前缀和算法详解

数据预处理&#xff1a;前缀和算法详解 文章目录 数据预处理&#xff1a;前缀和算法详解1.算法原理2.算法作用3.C代码实现4.实战题目 1.算法原理 基本概念 前缀和&#xff08;Prefix Sum&#xff09;是一种常用的数据预处理技术&#xff0c;它可以快速求解区间和问题&#xf…...

盈达科技:登顶GEO优化全球制高点,以AICC定义AI时代内容智能优化新标杆

一、技术制高点——全球独创AICC系统架构&#xff0c;构建AI内容优化新范式 作为全球首个实现AI内容全链路优化的技术供应商&#xff0c;盈达科技凭借AICC智能协同中心&#xff08;自适应内容改造、智能数据投喂、认知权重博弈、风险动态响应四大引擎&#xff09;&#…...

【Linux】详细介绍进程的概念

目录 一、初识进程概念 真正的进程概念如下&#xff1a; 二、Linux中PCB的操作系统学科叫法&#xff1a;task_struct 1、简单认识task_ struct内容分类 2、问题&#xff1a;操作系统怎么知道当前程序执行到哪一行代码了&#xff1f; 三、linux关于进程的常用指令&#xff…...

mybatis框架补充

一&#xff0c;#{} 和${}区别 1.传数值 #{} 占位符&#xff0c;是经过预编译的&#xff0c;编译好SQL语句再取值&#xff0c;#方式能够防止sql注入 eg&#xff1a;#{}&#xff1a;delete from admin where id #{id} 结果&#xff1a; dalete from admin where id &#x…...

Alertmanager的安装和详细使用步骤总结

一、安装步骤 1. 二进制安装 下载与解压 从GitHub下载最新版本&#xff08;如v0.23.0&#xff09;&#xff1a;wget https://github.com/prometheus/alertmanager/releases/download/v0.23.0/alertmanager-0.23.0.linux-amd64.tar.gz tar -xzf alertmanager-0.23.0.linux-amd6…...

C++学习:六个月从基础到就业——C++学习之旅:STL迭代器系统

C学习&#xff1a;六个月从基础到就业——C学习之旅&#xff1a;STL迭代器系统 本文是我C学习之旅系列的第二十四篇技术文章&#xff0c;也是第二阶段"C进阶特性"的第二篇&#xff0c;主要介绍C STL迭代器系统。查看完整系列目录了解更多内容。 引言 在上一篇文章中…...

缓存与数据库一致性方案

一、缓存更新策略概述 在现代分布式系统中&#xff0c;缓存作为数据库的前置层&#xff0c;能显著提升系统性能。然而&#xff0c;缓存与数据库之间的数据一致性是一个经典难题。以下是三种常见的缓存更新策略及其优缺点分析。 二、方案对比分析 方案一&#xff1a;直接更新…...

国内ip地址怎么改?详细教程

在中国&#xff0c;更改IP地址需要遵守规则&#xff0c;并确保所有操作合规。在特定情况下&#xff0c;可能需要修改IP地址以满足不同需求或解决特定问题。以下是一些常见且合法的IP地址变更方法及注意事项&#xff1a; 一、理解IP地址 IP地址是设备在网络中的唯一标识&#x…...

通过Quartus II实现Nios II编程

目录 一、认识Nios II二、使用Quartus II 18.0Lite搭建Nios II硬件部分三、软件部分四、运行项目 一、认识Nios II Nios II软核处理器简介 Nios II是Altera公司推出的一款32位RISC嵌入式处理器&#xff0c;专门设计用于在FPGA上运行。作为软核处理器&#xff0c;Nios II可以通…...

拥抱基因体检,迎接精准健康管理新时代

2025年4月20日&#xff0c;由早筛网、细胞科技网联合中国食品药品企业质量安全促进会细胞医药分会、中国抗衰老促进会健康管理工作委员会、中国抗癌协会肿瘤分子医学专业委员会、广东省保健协会&#xff0c;伯温生物冠名支持的《基因体检赋能精准健康管理新时代》圆满召开。 伯…...

QT容器类控件及其属性

Group Box 使用QGroupBox实现一个带有标题的分组框&#xff0c;可以把其它的控件放到里面作为一组 例&#xff1a; 核心属性 属性 说明 title 分组框的标题 alignment 分组框内部内容的对齐方式 flat 是否”扁平模式” checkable 是否可选中 设为true&#xff0c;则…...

云原生--CNCF-3-核心工具介绍(容器和编排、服务网格和通信、监控和日志、运行时和资源管理,安全和存储、CI/CD等)

1、核心工具分类介绍 &#xff08;1&#xff09;、容器编排与管理 1、Docker 它是一款轻量级的容器化技术&#xff0c;可把应用及其依赖打包成独立的容器。借助Docker&#xff0c;开发者能够确保应用在不同环境中保持一致的运行状态。比如在开发环境中创建的容器&#xff0c…...

网络基础知识

文章目录 一、网络架构1. 网络架构图2. 各层级功能3. 机房网络常见问题及解决方案 二、交换技术1. 交换技术基础2. 交换技术分类3. 广播域相关概念4. ARP 协议5. 三层交换机6. VLAN&#xff08;虚拟局域网&#xff09; 三、路由技术1. 路由器端口类型及功能2. 路由器功能3. 路由…...

第3课:运算符与流程控制——JS的“决策者”

一切美好都值得你全力以赴。即使过程艰难&#xff0c;但只要坚持&#xff0c;必有回响。加油&#xff01; 欢迎来到「JavaScript 魔法学院」第 3 课&#xff01;今天我们将化身代码世界的“指挥官”&#xff0c;用运算符计算数据&#xff0c;用流程控制做出决策&#xff01;文…...

VUE3中使用echarts,配置都正确,不出现tooltip

在vue3中使用echarts&#xff0c;出现个别问题&#xff0c;需要注意echars对象的定义&#xff0c;不能使用reactive或者ref定义响应式变量&#xff0c;要使用shallowRef &#xff1b; shallowRef 是 Vue 3 提供的一个 API&#xff0c;它创建一个响应式引用(ref)&#xff0c;但…...

Python实现邮件发送

一、创造灵感 这几天在指导学生做毕业设计&#xff0c;学生做的是跌倒检测。然后我突然想到&#xff0c;如果这个系统只是单纯地从视频流中检测到人的跌倒动作&#xff0c;其实并没有任何的用途和意义。于是&#xff0c;我又想到&#xff0c;如果跌倒的检测结果&#xff0c;能…...

OSPF的优化

OSPF的优化 1、汇总 --- 减少骨干区域LSA的更新量 汇总 --- 区域汇总 域间路由汇总 --- 在ABR设备上&#xff0c;针对3类LSA进行汇总 [r1-ospf-1-area-0.0.0.2]abr-summary 192.168.0.0 255.255.252.0 注&#xff1a;在进行域间路由汇总时&#xff0c;必须是ABR设备自己通过拓…...

IP-guard离线卸载客户端及清除策略说明

1、控制台生成客户端工具 控制台选择“工具-客户端工具-客户端离线辅助工具”&#xff0c;可生成客户端离线卸载工具及 离线策略清除工具&#xff0c;打包生成exe后&#xff0c;可在离线的客户端上运行&#xff0c;实现离线情况下对客户端 进行卸载或者清除策略。 在控制台上“…...

腾讯秋招面试题:bug生命周期中有哪些状态?

在软件测试中,Bug生命周期是质量管控的核心脉络。不同企业的流程或有差异,但核心状态遵循行业通用规范。以下以腾讯TAPD缺陷管理模型为基础,结合互联网大厂主流实践,详解Bug生命周期的关键状态及流转逻辑。 一、基础状态图谱 图表 代码 下载 确认有效 非缺陷/需求不符…...

PyCharm 链接 Podman Desktop 的 podman-machine-default Linux 虚拟环境

#工作记录 PyCharm Community 连接到Podman Desktop 的 podman-machine-default Linux 虚拟环境详细步骤 1. 准备工作 确保我们已在 Windows 系统中正确安装并启动了 Podman Desktop。 我们将通过 Podman Desktop 提供的名为 podman-machine-default 的 Fedora Linux 41 WSL…...

学习海康VisionMaster之卡尺工具

一&#xff1a;进一步学习了 今天学习下VisionMaster中的卡尺工具&#xff1a;主要用于测量物体的宽度、边缘的特征的位置以及图像中边缘对的位置和间距 二&#xff1a;开始学习 1&#xff1a;什么是卡尺工具&#xff1f; 如果我需要检测芯片的每一个PIN的宽度和坐标&#xff…...

私有知识库 Coco AI 实战(二):摄入 MongoDB 数据

在之前的文章中&#xff0c;我们介绍过如何使用《 Logstash 迁移 MongoDB 数据到 Easyseach》&#xff0c;既然 Coco AI 后台数据存储也使用 Easysearch&#xff0c;我们能否直接把 MongoDB 的数据迁移到 Coco AI 的 Easysearch&#xff0c;使用 Coco AI 对数据进行检索呢&…...

【C/C++】插件机制:基于工厂函数的动态插件加载

本文介绍了如何通过 C 的 工厂函数、动态库&#xff08;.so 文件&#xff09;和 dlopen / dlsym 实现插件机制。这个机制允许程序在运行时动态加载和调用插件&#xff0c;而无需在编译时知道插件的具体类型。 一、 动态插件机制 在现代 C 中&#xff0c;插件机制广泛应用于需要…...

硬核科普丨2025年安全、高效网络准入控制系统深度解析

阳途网络准入控制系统&#xff08;Network Access Control&#xff0c;简称NAC&#xff09;是当代网络安全领域的重要工具&#xff0c;有效防止未经授权的访问和数据泄露&#xff0c;保障网络资源的安全性和完整性。本文将深入探讨阳途网络准入控制系统的的重要性和作用。 一、…...

2025知识管理趋势解析:AI、协作与用户体验引领变革

知识管理软件是多元化的商业工具&#xff0c;包括知识库系统、wiki、协作平台、文档管理工具、聊天机器人和帮助系统&#xff0c;针对企业信息爆炸式增长的趋势&#xff0c;这些工具正逐步成为提升知识工作者效率和客户服务水平的关键支撑。 知识成为新资产&#xff1a;知识工…...

杂谈-有感而发

今天又拜读了线性代数的几何意义这本书&#xff0c;感觉确实是很不错的&#xff0c;从几何角度讲解线性代数&#xff0c;而且将线性代数、几何、数学、物理有机结合起来了&#xff0c;不仅仅是可视化&#xff0c;重要的是能便于自己独立思考下去。   从大一开始&#xff0c;就…...

使用nodeJs的express+axios+cors做代理

使用nodeJs的expressaxioscors做代理 前端在请求后端时通常会遇到跨域cors问题&#xff0c;如果只在本地开发可以通过webpack或vite的proxy设置。但如果需要在线上或者其他地方绕过跨域&#xff0c;可以使用代理的方法。 1. 创建文件夹 并创建以下文件 package.json {"…...