【APUE】进程间通信

目录

一、管道

1.1 匿名管道

1.2 命名管道

二、XSI IPC

2.1 概述 

2.2 消息队列

2.2.1 msgget 

2.2.2 msgsnd

2.2.3 msgrcv

2.2.4 msgctl

2.2.5 代码示例 

2.3 信号量数组

2.3.1 semget

2.3.2 semop

2.3.3 semctl 

2.3.4 代码示例

2.3 共享内存

2.3.1 shmget

2.3.2 shmat

2.3.3 shmdt

2.3.4 shmctl

2.3.5 代码示例​​​​​​​


一、管道

最古老的通信机制

之前我们手写了一个管道,实现了线程之间的数据交互。其实内核也给我们提供了一个管道,可用于进程间通信。内核提供的管道和我们自己写的管道具有比较类似的特点:

  • 管道通信为单工通信,一端为读端,一端为写端
  • 管道内部自带同步和互斥机制

1.1 匿名管道

所谓匿名管道,指的就是管道文件我们在磁盘上是看不到的。可以通过如下函数创建一个匿名管道:

man 2 pipe

#include <unistd.h>int pipe(int pipefd[2]);

功能:创建一个匿名管道 

  • pipefd — pipefd 是一个数组,用于返回管道两端的文件描述符。成功创建管道后,代表读端的文件描述符会被回填至 pipefd[0],代表写端的文件描述符会被回填至 pipefd[1]
  • 创建成功返回 0;失败返回 -1 并设置 errno 

匿名管道文件在磁盘上是看不到的,因此常用于有父子关系的进程间通信,因为父进程能够通过 fork 使子进程获得同一个匿名管道两端的文件描述符


代码示例:通过匿名管道实现父子进程通信,要求父进程负责往管道中写,子进程负责从管道中读。基本思路是先调用 pipe,后调用 fork

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>#define BUFSIZE 1024int main() {int pd[2];      // pd[0]用于存放读端,pd[1]用于存放写端if (pipe(pd) < 0){perror("pipe()");exit(1);}int pid = fork();if (pid < 0){perror("fork()");exit(1);}else if (pid == 0)      // child read{close(pd[1]);   // 子进程不需要写,关闭写端char buf[BUFSIZE];int len = read(pd[0], buf, BUFSIZE);write(1, buf, len);     // 在终端显示读取到的字符close(pd[0]);exit(0);}else    // parent write{close(pd[0]);   // 父进程不需要读,关闭读端write(pd[1], "Hello!", 6);close(pd[1]);exit(0);}
}

可以看到父进程写入管道的内容被子进程读取到并成功打印了出来 

1.2 命名管道

所谓命名管道,指的就是管道以文件形式存在于磁盘上。可以通过如下命令创建一个命名管道:

可以看到我们成功创建了一个命名管道,且其文件类型为 “p”。 当然,我们也可以利用如下函数创建命名管道:

man 3 mkfifo

#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);

功能:创建一个命名管道  

  • pathname — 指定管道文件名
  • mode — 指定管道文件的权限(实际创建出管道文件的权限为 mode & ~umask)
  • 创建成功返回 0,失败返回 -1 并设置 errno

因为管道需要凑齐读写双方才能正常运行,因此内核提供的命名管道有这样的一种约束:有读才能写,有写才能读

二、XSI IPC

2.1 概述 

XSI IPC 可用于同一主机内没有亲缘关系的两个进程之间通信

先了解 XSI IPC 是什么意思

  • XSI — X/Open System Interface,其中 X/Open 是一个制定标准的国际组织,这部分学习的内容都是这个组织制定的标准
  • IPC — Inter-Process Communication,即进程间通信

可以通过如下命令查看 XSI IPC 提供的进程间通信机制

我们可以看到,有三种类型的 IPC 资源,分别是消息队列、共享内存、信号量数组。每种类型的 IPC 资源可能有多个 IPC 实例,而 XSI IPC 的核心思想就是让多个进程操作同一个 IPC 实例,通过这个 IPC 实例实现进程间通信


接下来,我们基于 XSI IPC 的核心思想,分析 XSI IPC 所提供机制背后的一些设计思路

刚刚说到 XSI IPC 的核心思想是让多个进程操作同一个 IPC 实例。因此,比较关键的点是:如何让多个进程能够正确定位到某一个特定的 IPC 实例?


首先要将不同的 IPC 实例区分开。类似身份证号一样,我们需要给每个 IPC 实例提供独一无二的标识(id),以定位到特定的 IPC 实例

注意,我们刚刚说IPC实例的标识是独一无二的,指的是 不可能存在两个同类型且id相同的实例,但其实msqid和shmid的值是有可能相同的。打个比方:就算两个不同班级都有学号为01的学生,也能够区分出这两个学生个体,因为这两个学生压根不是一个班的;可如果同一个班级下有两个学号为01的学生,那就出岔子了。因此标识的唯一性强调的是同类型IPC资源实例的标识是唯一的

通过标识区分出 IPC 实例之后,接下来的任务就是让多个进程定位到同一个 IPC 实例。换言之,就是让多个进程能够获得相同标识的同类资源(因为标识能够定位到实例)

XSI IPC 所提供的机制是:通过 key 来获取 IPC 资源的 id 值!

可以将 key 看成所有进程都能够访问的背包。每个不同的 key 值代表了不同的背包,单个背包中最多可以放一个 msqid、一个 shmid 和一个 semid

这个图画得像个餐盘......

假设已经有三个背包了,每个背包中已经存放的 id 数量和值不同。通过 key 获取 IPC 资源标识的过程如下:

这样一来,我们可以像下图那样,让多个进程都能定位到相同的标识


剩下最后一个问题:某个 key 值代表所有进程都能访问的某个背包,不同 key 值代表不同背包。无论是将 id 放入背包或者是从背包中获取某资源的 id 值,都需要先获取某个背包(k 值)。那么应该如何获取呢?

XSI IPC 提供了如下调用来获取 key 值(即获取 key 为某个值的背包)

man 3 ftok

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

功能:获取某个 key 值(获取 key 为某个值的背包)

  • pathname — 指定某个可访问文件
  • proj_id — 一个整数
  • 根据 pathname 所指定文件的 inode 号与 proj_id 的值,结合某种固定规则,获取并返回一个 key 值
  • 失败返回 -1 并设置 errno
  • The resulting value is the same for all pathnames that name the same file,  when the  same value of proj_id is used. The value returned should be different when the (simultaneously existing) files or the project IDs differ

2.2 消息队列

2.2.1 msgget 

man msgget 

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

功能:获取消息队列实例的 id

  • key — 代表某个背包,从该背包内获取消息队列实例的 id
  • msgflg — 特殊要求,其含义及值如下
含义
IPC_CREAT | mode如果 key 所指定的背包中还没存入任何消息队列实例的 id,则先创建一个权限为 mode 的消息队列,并将其 id 添加进 key 所指定的背包

IPC_CREAT | mode | IPC_EXCL

强制创建一个权限为 mode 的消息队列,并将其 id 添加进 key 所指定的背包;如果 key 所指定的背包中已有某消息队列实例的 id 了,则调用失败
  • 成功则返回获取到的消息队列实例 id(非负整数);失败返回 -1 并设置 errno

2.2.2 msgsnd

man msgop

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

功能:向某个消息队列发送数据

  • msqid — 表示将数据发送到 msqid 所标识的消息队列
  • msgp — 指向某个具有如下形式的结构体的实例。msgsnd 会将这个结构体实例发送到消息队列。注意结构体中 mtype 字段后的内容才是用户实际想发送的数据
struct msgbuf {long mtype;       /* message type, must be > 0 */...               /* message data */
};
  • msgsz —  msgp 所指结构体除 mtype 字段之外的大小,代表着拟发送数据的实际字节数
  • msgflg — 特殊要求,详见 man,没有写 0
  • 成功返回 0;失败返回 -1 并设置 errno

2.2.3 msgrcv

man msgop 

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

功能:从某个消息队列中接收数据

  • msqid — 表示从 msqid 所标识的消息队列中接收数据
  • msgp — msgrcv 会将从消息队列接收到的结构体实例填充至 msgp 所指的地址
  • msgsz —  msgp 所指结构体除 mtype 字段之外的大小,代表着拟接收数据的实际字节数
  • msgtyp — 消息队列中可能已经收到了多个由 msgsnd 发送的结构体,而 msgtyp 可以指定从消息队列中选择接收哪一个结构体。其值及含义如下
含义
0返回队列中的第一个结构体
>0返回队列中 mtype 字段值等于 msgtyp 的第一个结构体
<0返回队列中 mtype 字段值小于等于 msgtyp 绝对值的结构体。如果这种结构体有若干个,则取 mtype 字段值最小的那一个
  • msgflg — 特殊要求,详见 man,没有写 0
  • 成功返回接收到数据的实际字节数;失败返回 -1 并设置 errno

2.2.4 msgctl

man msgctl

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);

功能:控制某个消息队列的实例 

  • msqid — 标识一个待控制的消息队列
  • cmd — 控制选项,详见 man
  • buf — 指向一个结构体,用于储存该消息队列实例的一些属性信息

2.2.5 代码示例 

通过消息队列,实现两个进程之间的通信:发送端发送数据给接收端

proto.h,规定通信协议(通信规则)

#ifndef PROTO_H__
#define PROTO_H__#define KEYPATH "/etc/passwd"
#define KEYPROJ 'g'    // 表示KEYPROJ是个整型数,转化成ASCALL码后 是一个整型数
#define NAMESIZE 32// 规定传输数据的格式
struct msg_st    // 规定通信双方按照如下规则解析通信数据的内容
{long mtype; // 消息类型,msgrcv可通过该字段决定接收消息队列中 的哪一个结构体实例char name[NAMESIZE];    // 姓名int math;    // 数学成绩int chinese;    // 语文成绩
};#endif

rcver.c,接收端,应该优先运行,负责创建消息队列并持续运行,以监听发送端发送端数据

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <signal.h>#include "proto.h"int msqid;void myexit() {msgctl(msqid, IPC_RMID, NULL); // 移除消息队列
}void handler(int s) {   // 信号处理函数exit(0);
}int main() {key_t key = ftok(KEYPATH, KEYPROJ); // 获取值为key的背包if (key == -1){perror("ftok()");exit(1);}// 接收端,先运行,先创建消息队列msqid = msgget(key, IPC_CREAT | 0600 | IPC_EXCL);   // 创建消息队列实例并放入背包// IPC_EXCL确保了该调用要么创建成功, 要么调用失败if (msqid < 0){perror("msgget()");exit(1);}atexit(myexit);signal(SIGINT, handler);struct msg_st rbuf; // 用于将从消息队列中接收到的结构体储存下来while (1) { // 不断从消息队列中接收数据if (msgrcv(msqid, &rbuf, sizeof(rbuf)-sizeof(long), 0, 0) < 0){perror("msgrcv()");exit(1);}printf("NAME = %s\n", rbuf.name);printf("MATH = %d\n", rbuf.math);printf("CHINESE = %d\n", rbuf.chinese);}exit(0);}

snder.c,发送端,负责向接收端创建的消息队列发送数据

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>#include "proto.h"int main() {key_t key = ftok(KEYPATH, KEYPROJ);     // 获取值为key的背包if (key == -1){perror("ftok()");exit(1);}int msqid = msgget(key, 0);     // 从值为key的背包中获取消息队列实例的idif (msqid < 0) {perror("msgget()");exit(1);}struct  msg_st sbuf;    // 待发送到消息队列的结构体sbuf.mtype = 1;strcpy(sbuf.name, "Yangjihua");sbuf.math = rand()%100;sbuf.chinese = rand()%100;if (msgsnd(msqid,&sbuf, sizeof(sbuf)-sizeof(long), 0) < 0){perror("msgsnd()");exit(1);}puts("ok!");exit(0);
}

首先 ipcs,可以看到目前没有任何可用的 IPC 资源

调用 ./rcver 后,再次查看 

可以看到创建 IPC 资源成功,创建出来的 IPC 资源是一个消息队列,该消息队列的 id 存放在 key 为 0x671087fb 的背包中,其 msqid = 3,权限为 600

接下来尝试调用 ./snder 向消息队列发送数据

成功!


2.3 信号量数组

我们在线程章节曾经接触过“信号量”的概念

那个时候介绍的信号量其实就是一个有整数值的对象。通过合理利用整数的初始值,搭配操作该值的函数,可以实现条件变量或者互斥量的功能,能够用于线程之间的同步和互斥

而其实 XSI IPC 也给我们提供了用于进程间同步和互斥的信号量数组。之所以是“信号量数组”,是因为进程与进程之间可能需要利用多个信号量才能够完成任务(比如可能有多个共享资源的访问,每个共享资源都需要一个信号量来实现互斥)

注意,所提供信号量数组中的信号量和我们之前介绍的信号量挺类似的,这意味着我们这里所介绍的信号量也是一个“包含某个整数值的对象”,这个整数值我们称之为“信号量的值”

2.3.1 semget

man 2 semget

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);

功能:获取信号量数组实例的 id

  • key — 代表某个背包,从该背包内获取信号量数组实例的 id
  • nsems — 若创建,则创建的信号量数组包含 nsems 个信号量
  • msgflg — 特殊要求,其含义及值如下
含义
IPC_CREAT | mode如果 key 所指定的背包中还没存入任何信号量数组实例的 id,则先创建一个权限为 mode 的信号量数组,并将其 id 添加进 key 所指定的背包

IPC_CREAT | mode | IPC_EXCL

强制创建一个权限为 mode 的信号量数组,并将其 id 添加进 key 所指定的背包;如果 key 所指定的背包中已有某信号量数组实例的 id 了,则调用失败
  • 成功则返回获取到的信号量数组实例 id(非负整数);失败返回 -1 并设置 errno

2.3.2 semop

man 2 semop

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semop(int semid, struct sembuf *sops, size_t nsops);

功能:操作信号量数组中的信号量

  • semid —  表示操作 semid 所标识信号量数组中的信号量
  • sops — 指向结构体数组(的首元素),数组中的每个结构体实例都是 struct sembuf 类型,代表着对某个信号量的特定操作
  • nsops — sops 指向的是多大容量的结构体数组
  • 成功返回 0;失败返回 -1 并设置 errno

其中,struct sembuf 代表了对某个信号量的特定操作,其成员包括如下:

unsigned short sem_num;  /* semaphore number *//* 对信号量数组中哪个下标的信号量操作 */
short          sem_op;   /* semaphore operation *//* 对信号量的值增加sem_op的值 */
short          sem_flg;  /* operation flags *//* 特殊要求 */

sem_op 的正负和 sem_flg 的设置会影响 semop 的行为。当 sem_flg 设置为 0 时,默认 sem_op 的正负含义如下:

含义
sem_op > 0释放相应的资源数。将 sem_op 的值加到信号量的值上

sem_op = 0

进程阻塞直到信号量的相应值为 0。当信号量的值已经为 0,函数立即返回
sem_op < 0请求 sem_op 的绝对值的资源。如果相应的资源数可以满足请求,则将该信号量的值减去 sem_op 的绝对值,然后立即返回;否则阻塞直到相应的资源数可以满足请求

2.3.3 semctl 

man semctl

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);

功能:控制某个信号量数组的实例 

  • semqid — 标识一个待控制的信号量数组
  • semnum — 指定控制信号量数组中的哪些信号量
  • cmd — 控制选项,详见 man

2.3.4 代码示例

代码示例:多进程同时操作一个文件。每个进程都会:

  1. 从文件中获取值
  2. 将值加一
  3. 用加一后的值替换文件中原来的值

之前是通过文件锁实现,在这里我们用信号量数组实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>#define PROCNUM 2000    //创建2000个进程
#define LINESIZE 1024#define FILENAME "./tmp"static int semid;static void P() {struct sembuf op;op.sem_num = 0;	// 对数组中下标为0的信号量操作op.sem_op = -1;	// 请求资源op.sem_flg = 0;	// 无特殊要求,默认行为while (semop(semid, &op, 1) < 0){	if (errno != EINTR || errno != EAGAIN){perror("semop()");exit(1);}}return;
}static void V() {struct sembuf op;op.sem_num = 0;op.sem_op = 1;	// 释放资源op.sem_flg = 0;if (semop(semid, &op, 1) < 0){	perror("semop()");exit(1);}
}static void func_add(void) {FILE * fp = fopen(FILENAME, "r+");if (fp == NULL){perror("fopen()");exit(1);}char linebuf[LINESIZE];P();	// 请求资源,若信号量的值为0,则阻塞fgets(linebuf, LINESIZE, fp);   // 获取fseek(fp, 0, SEEK_SET); // 将文件位置指针pos定位到文件首,这样才能实现覆盖原值fprintf(fp, "%d\n", atoi(linebuf)+1);   // 加一后写回去fflush(fp);V();	// 释放资源fclose(fp);
}int main() {pid_t pid;semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);	// 获取数组大小为1的信号量数组(我们仅需一个信号量)// IPC_PRIVATE适用于具有亲缘关系的IPCif (semid < 0) {perror("semget()");exit(1);}if (semctl(semid, 0, SETVAL, 1) < 0)	// 设置信号量数组中下标为0的信号量的值为1{perror("semctl()");exit(1);}for (int i = 0; i < PROCNUM; ++i) {     // 父进程不断创建子进程pid = fork();if (pid < 0)    // error{perror("fork()");exit(1);}else if (pid == 0)      // child do sth{func_add();     // do sthexit(0);}else    // parent continue to fork{continue;}}for (int i = 0; i < PROCNUM; ++i) {     // 对PROCNUM个进程收尸wait(NULL);}semctl(semid, 0, IPC_RMID);exit(0);}

注意,在我们调用 semget 时,并没有先利用 ftok 获取 key 值,然后传入 semget 的第一个形参,而是直接使用了 IPC_PRIVATE,为什么能这样?

我们之前说过,key 值代表着不同的背包,这个背包是用来装 IPC 资源实例的 id 的,采用这样的机制的原因是希望不同进程能够顺利获取 id 并定位到相同的 IPC 资源实例。但是,对于有亲缘关系的进程,通过 fork 能够很容易地使子进程获取到对应相同 IPC 资源实例的 id,因此不需要通过 key 这种比较麻烦的机制来获取 IPC 资源实例的 id 了。因此,对于有亲缘关系的进程,XSI IPC 为我们提供了一个偷懒的方式 —— 将 IPC_PRIVATE 传入获取资源的首个形参


2.3 共享内存

2.3.1 shmget

man shmget

#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);

功能:获取共享内存实例的 id 

  • key — 代表某个背包,从该背包内获取共享内存实例的 id
  • size — 若创建,则创建共享内存的大小为 size 四舍五入至 PAGE_SIZE 的整倍数
  • msgflg — 特殊要求,IPC_CREAT 表示允许创建,需要或运算一个 mode 表示创建出的共享内存的权限

2.3.2 shmat

man shmop

#include <sys/types.h>
#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);

功能:将共享内存映射至进程空间中的某个地址

  • shmid —  标识一块共享内存实例,表示将哪个共享内存映射进来
  • shmaddr — 指定将共享内存映射进当前进程空间的哪个地址,若为 NULL 则让系统自动决定映射到哪个地址
  • shmflg — 特殊要求,详见 man,没有特殊要求则填 0
  • 映射成功则返回实际映射进了当前进程空间的哪个地址;失败返回 (void*)-1 并设置 errno

补充一下:不要混淆了“获取共享内存实例”和“将共享内存映射至进程空间”

2.3.3 shmdt

man shmop

#include <sys/types.h>
#include <sys/shm.h>int shmdt(const void *shmaddr);

功能:解除已映射至某个地址的共享内存

  • shmaddr — 某个进程空间中的地址,有共享内存被映射至该地址
  • 成功返回 0;失败返回 -1 并设置 errno

2.3.4 shmctl

man 2 shmctl

#include <sys/ipc.h>
#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);

功能:控制某个共享内存的实例  

  • shmid —  表示控制 shmid 所标识的共享内存实例
  • cmd — 控制选项,详见 man
  • buf — 指向一个结构体,用于储存该共享内存实例的一些属性信息

2.3.5 代码示例

在高级 IO 中我们曾通过 mmap 实现过共享内存,在这里我们使用 XSI IPC 提供的共享内存再实现一遍

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>#define MEMSIZE 1024int main() {int shmid = shmget(IPC_PRIVATE, MEMSIZE, IPC_CREAT | 0600);	// 父子进程之间通信,用IPC_PRIVATE就行了if (shmid < 0){perror("shmget()");exit(1);}int pid = fork();if (pid < 0){perror("fork()");exit(1);}else if (pid == 0) {    // child writechar * ptr = shmat(shmid, NULL, 0);	// 将shmid所标识的共享内存映射进来// NULL表示让操作系统自己决定映射到哪里// 0表示无特殊需求// 返回值为实际将共享内存映射到了哪里if (ptr == (void *)-1){perror("shmat()");exit(1);}strcpy(ptr, "Hello!");	// 像操作字符串一样操作这片内存shmdt(ptr);	// 解除映射exit(0);}else    // parent read{wait(NULL);char * ptr = shmat(shmid, NULL, 0);if (ptr == (void *)-1){perror("shmat()");exit(1);}puts(ptr);shmdt(ptr);exit(0);}shmctl(shmid, IPC_RMID, NULL);	// 销毁共享内存实例exit(0);
}

子进程写进共享内存的字符串成功在父进程中打印了出来,通信成功!  


我们可以看到,本板块讲解的管道和 XSI IPC 允许在同一台计算机上运行的进程相互通信,其核心的思路都是通过不同进程都能够访问到的中介(管道和各种 XSI 资源),来实现进程间通信。那么,如果是不同计算机上的进程该如何通信呢?这将是我们接下来会讲解的部分:网络套接字

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/176103.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

selenium已知一个元素定位同级别的另一个元素

1.需求与实际情况 看下图来举例 &#xff08;1&#xff09;需求 想点击test22&#xff08;即序号-第9行&#xff09;这一行中右边的“复制”这一按钮 &#xff08;2&#xff09;实际情况 只能通过id或者class定位到文件名这一列的元素&#xff0c;而操作这一列的元素是不…

零基础可以学编程吗,不懂英语怎么学编程,中文编程工具实例

零基础可以学编程吗&#xff0c;不懂英语怎么学编程&#xff0c;中文编程工具实例 上图是中文编程工具界面、标尺实例。 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#x…

基于单片机体温心率脉搏检测仪系统设计

**单片机设计介绍&#xff0c; 基于单片机体温心率脉搏检测仪系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机体温心率脉搏检测仪是一种用于检测人体体温、心率和脉搏等基本生理指标的医疗设备。下面是一个简要…

QT 界面切换

先新建一个Widget工程 ui界面设置如下 在添加一个QT设计师界面类 右键点击添加 第二个UI界面设置如下 代码 链接&#xff1a;https://pan.baidu.com/s/1ovDIG2pno9mJ7mMFh2tq3Q 提取码&#xff1a;6q3m –来自百度网盘超级会员V2的分享

python+pytest接口自动化(2)-HTTP协议基础

HTTP协议简介 HTTP 即 HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09;&#xff0c;是互联网上应用最为广泛的一种网络协议。所有的 WWW 文件都必须遵守这个标准。 设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。HTTP 协议在 OSI 模型…

vue3 + element-plus + ts el-table封装

vue3 element-plus ts el-table封装 博客参考https://blog.csdn.net/weixin_45291937/article/details/125523244 1. 文件位置&#xff08;根据自己的需求&#xff09; 2. 在 custom 文件夹下面 创建 mytable 文件夹 3. 直接上代码 // index.vue<template><div …

Python实现WOA智能鲸鱼优化算法优化XGBoost回归模型(XGBRegressor算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 鲸鱼优化算法 (whale optimization algorithm,WOA)是 2016 年由澳大利亚格里菲斯大学的Mirjalili 等提…

CloudCompare简单开发

一、概述 CloudCompare如何进行二次开发&#xff1f;_cloudcompare 二次开发-CSDN博客 开发一个功能&#xff0c;在原始CC的基础上添加一个拓展功能&#xff0c;如下&#xff1a; 二、功能开发 1、修改MainWindow.UI 重点是&#xff1a;要编译&#xff0c;不然在mainwindow.…

JAVA进阶之路JVM-2:类加载机制,类的生命周期,类加载过程,类加载时机,类加载器,双亲委派模型,对象创建过程

JVM类加载机制 类加载 ​ 在JVM虚拟机实现规范中&#xff0c;通过ClassLoader类加载把*.class字节码文件&#xff08;文件流&#xff09;加载到内存&#xff0c;并对字节码文件内容进行验证&#xff0c;准备&#xff0c;解析和初始化&#xff0c;最终形成可以被虚拟机直接使用…

点盾云出现“操作失败,错误码1002”如何解决?

在使用点盾云学习看课时&#xff0c;老师会先将视频或者是在线播放链接发给我们&#xff0c;我们通过下载文件的方式或通过直接在线点播的形式来观看&#xff0c;那么在操作的过程中&#xff0c;有时候我们会遇到一些问题&#xff0c;今天以百度网盘中下载的视频文件为例&#…

浙江启用无人机巡山护林模式,火灾扑救效率高

为了保护天然的森林资源&#xff0c;浙江当地林业部门引入了一种创新技术&#xff1a;林业无人机。这些天空中的守护者正在重新定义森林防火和护林工作的方式。 当下正值天气干燥的季节&#xff0c;这些无人机开始了它们的首次大规模任务。它们在指定的林区内自主巡逻&#xff…

B树与B+树的对比

B树&#xff1a; m阶B树的核心特性&#xff1a; 树中每个节点至多有m棵子树&#xff0c;即至多含有m-1个关键字根节点的子树数属于[2, m]&#xff0c;关键字数属于[1, m-1]&#xff0c;其他节点的子树数属于 [ ⌈ m 2 ⌉ , m ] [\lceil \frac{m}{2}\rceil, m] [⌈2m​⌉,m]&am…

excel对号怎么打

对号无论是老师批改作业&#xff0c;还是在标注某些数据的时候都会用到&#xff0c;但这个符号在键盘上是没有的&#xff0c;那么excel对号怎么打出来呢&#xff0c;其实只要使用插入符号功能就可以了。 excel对号怎么打&#xff1a; 第一步&#xff0c;选中想要打出对号的单…

世界共赢电影在行动 ——世界共赢电影签约仪式在京举行

2023年11月23日&#xff0c;秋景冬温的北京&#xff0c;迎来了美国、韩国、俄罗斯、德国、英国、法国、日本、印度、南非、加拿大、巴西、新加坡、印度尼西亚、伊朗、土耳其、马来西亚、越南、意大利、西班牙、波兰、南非、尼日利亚、澳大利亚等23个国家的影视行业代表&#xf…

第二十章总结

创建线程 继承Thread 类 Thread 类时 java.lang 包中的一个类&#xff0c;从类中实例化的对象代表线程&#xff0c;程序员启动一个新线程需要建立 Thread 实例。 Thread 对象需要一个任务来执行&#xff0c;任务是指线程在启动时执行的工作&#xff0c;start() 方法启动线程&am…

C/C++ Zlib实现文件压缩与解压

在软件开发和数据处理中&#xff0c;对数据进行高效的压缩和解压缩是一项重要的任务。这不仅有助于减小数据在网络传输和存储中的占用空间&#xff0c;还能提高系统的性能和响应速度。本文将介绍如何使用 zlib 库进行数据的压缩和解压缩&#xff0c;以及如何保存和读取压缩后的…

【开源】基于Vue和SpringBoot的数字化社区网格管理系统

项目编号&#xff1a; S 042 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S042&#xff0c;文末获取源码。} 项目编号&#xff1a;S042&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、开发背景四、系统展示五、核心源码5…

六、源NAT实验

学习防火墙之前&#xff0c;对路由交换应要有一定的认识 源NAT1.私网用户通过NAT No-PAT访问Internet2.私网用户通过NATP访问Internet3.私网用户通过Easy-IP访问Internet4.私网用户通过三元组NAT访问Internet5.双出口环境下私网用户通过NAPT访问Internet 源NAT ———————…

预览功能实现

需求&#xff1a;将后端返回来的文字或者图片和视频展示在页面上。 <!-- 预览 --><el-dialog title"预览" :visible.sync"dialogPreviewVisible" width"50%" append-to-body :close-on-click-modal"false" close"Previe…

微服务--03--OpenFeign 实现远程调用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 OpenFeign其作用就是基于SpringMVC的常见注解&#xff0c;帮我们优雅的实现http请求的发送。 RestTemplate实现了服务的远程调用 OpenFeign快速入门1.引入依赖2.启用…