C++ Webserver从零开始:基础知识(七)——多进程编程

前言

        在学习操作系统时,我们知道现代计算机往往都是多进程多线程的,多进程和多线程技术能大大提高了CPU的利用率,因此在web服务器的设计中,不可避免地要涉及到多进程多线程技术。

        这一章将简要讲解web服务器中的多进程编程,本文不会很详细,也不会在原理性的知识上多费笔墨。如果读者有什么不理解的地方,建议学习一下操作系统的基础知识。


fork系统调用

#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
  • 作用:复制当前进程,在内核进程表创建一个新的表项。新表项许多属性与原进程相同,比如堆指针,栈指针和标志寄存器的值。新进程的PPID为原进程的PID,信号位图被清除(原进程的信号处理函数对新进程不起作用;
  • 参数:无
  • 返回值(两次):一般根据fork返回值判断正在执行这段代码的是新进程还是原进程
    • 父进程中:返回子进程的PID
    • 子进程中:返回0

fork的一些注意事项:

  1. 子进程的代码与父进程完全相同
  2. 子进程采用写时复制的方式复制父进程的数据(堆数据,栈数据和静态数据)
  3. 父进程打开的文件描述符在子进程中同样打开,每fork一次文件描述符的全局引用+1


exec系统调用

        上面的fork是复制出一个新进程,类似于ctrl + c 和 ctrl + v,而exec系统调用则类似于 ctrl + x, ctrl + v。

#include<unistd.h>
extern char** environ;//设置新程序的环境变量
int execl(const char* path, const char* arg, ...);
int execlp(const char* file, const char* arg, ...);
int execle(const char* path, const char* arg, ..., char* const envp[]);
int execv(const char* path, const char* argv[]);
int execvp(const char* file, const char* arg[]);
int execve(const char* path, const char* arg[], char* const envp[]);
  • 作用:将当前进程替换成另一个进程并执行
  • 参数:
    • path: 指定可执行文件的完整路径
    • file:接收文件名,该文件的具体位置在环境遍历path中搜寻
    • arg:接收可变参数(被传递给新程序的main)
    • argv:接收参数数组(被传递给新程序的main)
    • envp[ ]:设置新程序的环境变量,若未设置则环境变量由environ指定
  • 返回值:一般不返回(这是因为当exec成功执行后,原程序的代码不会执行,返回值也就没用了)
    • 失败:-1

PS:exec不会关闭原程序打开的文件描述符


僵尸进程

        多进程中父进程一般需要跟踪子进程的退出状态,所以子进程退出时内核一般不会立刻释放其资源。这会出现以下两种情况:

  1. 子进程运行结束了,父进程还未读取其状态
  2. 父进程异常终止了,子进程继续运行直至结束,守护进程还未释放其资源
    1. 这时子进程被称为孤儿进程,孤儿进程会被守护进程init(PID为1)所收养,即其PPID被设为1
    2. 守护进程init会等待孤儿进程结束并释放其资源

        处于以上两种状态的僵尸进程,僵尸进程会占据内核的资源却不行使任何功能,所以我们要避免僵尸进程。方法是wait调用释放僵尸进程.

#include<sys/type.h>
#include<sys/wait.h>
pid_t wait(int* stat_loc);
pid_t waitpid(pid_t pid, int* stat_loc, int options);
  • 作用:等待子进程运行结束释放其资源
  • 参数
    • stat_loc(两个函数调用相同):指向一块内存,该内存存储子进程的退出信息
    • pid : 要求释放的子进程
    • options : 控制waitpid的行为,常用参数为WNOHANG(设置函数为非阻塞)
  • 返回值:
    • wait
      • 成功:返回结束运行的子进程的PID
      • 失败:-1
    • waitpid(非阻塞):
      • 成功:
        • pid指定的子进程还没结束或意外终止:0
        • pid指定的子进程正常退出:该子进程的PID
      • 失败:-1

        很显然这里有一个问题,当使用waitpid函数调用时是非阻塞的,既然是非阻塞的我们就得在得知子进程结束之后再调用它才合理,那么如何得知子进程结束了呢:

        答案是:使用SIGCHLD信号,该信号由子进程结束时给父进程发送,父进程在收到这个信号后即可在信号处理函数中调用非阻塞waitpid

static void handle_child(int sig) {pid_t pid;int stat;while ((pid == waitpid(-1 , &stat, WNOHANG)) > 0 ) {/*处理结束的子进程*/}
}


管道

        通过以上三节,我们学会了创建子进程和新进程,并学会了释放僵尸进程。接下来我们学习如何在进程之间通信,其中最简单的通信方式即管道。

        在C++ Webserver从零开始:基础知识(一)——Linux网络编程基础API-CSDN博客中我们介绍了管道使用的API,这里不再赘述,就简要介绍进程之间管道使用的注意事项

        我们知道管道由两个文件描述符组成,分别为fd[0]和fd[1]。在fork后两个文件描述符都是打开状态,而又因一个pipe管道只能实现一个方向的数据传输,因此父进程和子进程必须一个关掉fd[0],一个关掉fd[1]。

        当然,如果想要实现双向的传输,则需要创建两个管道pipe,在通信中我们称其为全双工管道。

        实现全双工管道还可以使用socketpair系统调用。

        管道通信的弊端:

        管道通信只能是两个关联进程(如父进程和子进程)之间进行通信,而要多个不相关的进程之间通信,则需要使用命名管道FIFO,本文不予介绍。


信号量

信号量原语

        在学习操作系统的时候相信大家都了解了信号量,这里简单介绍一下。

        当多个程序需要访问系统上的某一个资源时(通常为很短的一段代码,称为临界区),为了避免竞态条件,我们需要让同一时间只有一个进程进入临界区,这时就需要使用信号量来加以限制。

        信号量的原理类似于一把锁,临界区类似于一个房间内的资源,当有进程需要使用临界区的资源时,就把房间上锁再使用,这样当其他进程需要用时就只能在房外等待。当使用完毕后,进程需要把锁解开,这样下一个进程就可以进入临界区。

        操作系统中对信号量操作一般称为P,V操作,其中P为上锁,V为释放锁。

Linux中,信号量的API定义在sys/sem.h头文件中。主要包括以下三个变量:

  • semget
  • semop
  • semctl

semget系统调用

#include<sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
  • 作用:用于创建一个新的信号量集,或获取一个已经存在的信号量集
  • 参数:
    • key:键值,用来标识全局唯一的信号量集,通过信号量通信的进程需要使用相同的键值创建/获取该信号量。
    • num_sems:指定要创建的信号量集中数量的数目(如果是获取信号量集,则设为0即可)
    • sem_flas:指定一组标志来控制该API的细节,其具体格式和含义与系统调用open的mode参数相同
  • 返回值:
    • 成功:信号量集的标识符
    • 失败:-1

注意,该系统调用有两个作用:

  1. 创建一个信号量集
  2. 获得一个已经存在的信号量集

当系统调用是第一个作用时,会将一个相关联的内核数据结构体semid_ds初始化

#include<sys/sem.h>
/*该结构体用于描述IPC对象(信号量,共享内存和消息队列)的权限*/
struct ipc_perm{key_t key;/*键值*/uid_t uid;/*所有者的有效用户id*/gid_t gid;/*所有者的有效组id*/uid_t cuid;/*创建者的有效用户id*/git_t cgid;/*创建者的有效组id*/mode_t mode;/*访问权限*//*省略其他*/
}
struct semid_ds{ struct ipc_perm sem_perm;/*信号量的操作权限*/unsigned long int sem_nsems;/*该信号量集中的信号量数目*/time_t sem_otime;/*最后一次调用semop的时间*/time_t sem_ctimel;/*最后一次调用semctl的时间*//*省略其他*/
}

初始化的具体数值可自行搜索。

semop系统调用

semop系统调用改变信号量的值,即执行P,V操作。具体的PV操作实际上是对以下内核变量进行操作:

unsigned short semval;/*信号量的值*/
unsigned short semzcnt;/*等待信号量值变成0的进程数量*/
unsigned short semncnt;/*等待信号量值增加的进程数量*/
pid_t sempid;/*最后一次执行semop操作的进程id*/

以下是semop系统调用:

#include<sys/sem.h>
int semop(int sem_id, struct sembuf* sem_ops, size_t num_sem_ops);
  • 作用:改变信号量的值,执行PV操作
  • 参数:
    • sem_id:semget调用返回的信号量集标识符
    • sem_ops:一个sembuf结构体类型的数组,见下文单独介绍
    • num_sem_ops:指定要执行的操作个数,即sem_ops数组中元素的个数。(semop对sem_ops数组中的每个成员按顺序执行,且该过程时原子操作)
  • 返回值:
    • 成功:0
    • 失败:-1(且sem_ops数组指定的所有操作不执行)

sembuf结构体:

struct sembuf{unsigned short int sem_num;/*信号量集中信号量的编号,从0开始*/short int sem_op;/*操作类型,可取正整数,0,负整数,分别代表对信号量不同的操作*/short int sem_flg;/*影响sem_op的可选值*/
}

sem_op和sem_flg的类型排列组合有些多且繁杂,本文不具体介绍,建议读者真正需要使用时再去了解即可。

semctl系统调用

#include<sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...);
  • 作用:对信号量进行直接的控制
  • 参数:
    • sem_id:由semget返回的信号量
    • sem_num:被操作的信号量在信号量集中的编号
    • command:指定信号量要执行的命令
    • ...:用户自定义参数,只有个别command需要写,取决于command的取值。
  • 返回值:
    • 成功:取决于command的取值
    • 失败:-1

command参数:

注意,command命令是不要求记忆的,包括我本身也不会去看,放在这里只是为了文章完整性以及查询的作用。这些参数都建议等具体使用的时候再进行查询


共享内存

共享内存是最高效的IPC机制,它不涉及进程间任何的数据传输。与之而来的缺点是,我们必须用其他辅助手段来同步进程对共享进程测访问,否则会产生竞态条件。

Linux中,共享内存API定义在sys/shm.h头文件中,包括4个系统调用:

shmget,shmat,shmdt和shmctl。

shmget系统调用

#include<sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
  • 作用:创建一段新的共享内存,或获取一段已经存在的共享内存
  • 参数:
    • key:键值,用来标识一段全局唯一的共享内存
    • size:指定共享内存的大小(如果是获取已经存在的共享内存,则设为0)
    • shmflg:指定一组标志来控制该API的细节
  • 返回值:
    • 成功:正整数值,是 共享内存的标识符
    • 失败:-1

同样,当shmget是创建一个共享内存时,与之关联的内核数据结构shmid_ds将被创建并初始化,后面的API的部分功能将修该结构体中的部分参数

struct shmid_ds
{struct ipc_perm shm_perm;/*共享内存的操作权限*/size_t shm_segsz;/*共享内存的大小,单位字节*/time_t shm_atime;/*对这段内存最后一次调用shmat的时间*/time_t shm_dtime;/*对这段内存最后一次调用shamd的时间*/time_t shm_ctime;/*对这段内存最后一次调用shmctl的时间*/pid_t shm_cpid;/*创建者的PID*/pid_t shm_lpid;/*最后一次执行shmat或shmdt操作的进程的PID*/shmatt_t shm_nattach;/*目前关联到此共享内存的进程数量*//*省略一些字段*/
}

shmat和shmdt系统调用

#include<sys/shm.h>
void* shmat(int shm_id, const void* shm_addr, int shmflg);
  • 作用:将共享内存关联到地址空间
  • 参数:
    • shm_id:由shmget调用返回的共享内存标识符
    • shm_addr:指定将共享内存关联到进程的哪块地址空间
    • shmflg:影响最终的API的具体细节
  • 返回值:
    • 成功:返回共享内存被关联到的地址
    • 失败:返回 (void*) -1

int shmdt(const void* shm_addr);

作用:将关联到shm_addr的共享内存从进程中分离,成功返回0,失败返回 -1;

shmctl系统调用

#include<sys/shm.h>
int shmctl(int shm_id, int command, struct shmid_ds* buf);
  • 作用:控制共享内存的某些属性
  • 参数:
    • shm_id:由shmget返回共享内存标识符
    • command:要执行的命令
    • buf:取决于command
  • 返回值:
    • 成功:取决于command参数
    • 失败:-1

command命令:


消息队列

消息队列是两个进程之间传递二进制数据的一种有效的方式,每个数据块都有特定的类型,接收方可以根据类型来由选择地接收数据,而不一定像管道和命名管道那样必须以先进先出的方式接收数据。

Linux中,消息队列的API定义在sys/msg.h头文件中,包括四个系统调用 mssget,msgsnd,msgrcv,msgctl

msgget系统调用

#include<sys/msg.h>
int msgget(key_t key, int msgflg);
  • 作用:创建一个消息队列,或者获取一个已有的消息队列
  • 参数:
    • key:键值,标识一个全局唯一的消息队列
    • msgflg:同sem_flags,控制创建消息队列时的细节
  • 返回值:
    • 成功:返回一个正整数,代表消息队列的标识符
    • 失败:-1

同样,当msgget用于创建时,与之关联的内核数据结构msqid_ds将被创建并初始化:

struct msqid_ds {struct ipc_perm msg_perm;     /* 消息队列的操作权限*/time_t          msg_stime;    /* 最后一次调用msgsnd的时间*/time_t          msg_rtime;    /* 最后一次调用msgrcv的时间*/time_t          msg_ctime;    /* 最后一次被修改的时间*/unsigned long   __msg_cbytes; /* 消息队列中已有的字节数*/msgqnum_t       msg_qnum;     /* 消息队列中已有的消息数*/msglen_t        msg_qbytes;   /* 消息队列最大允许的字节数*/pid_t           msg_lspid;    /* 最后执行msgsnd的进程PID*/pid_t           msg_lrpid;    /* 最后执行msgrcv的进程PID*/
};

msgsnd系统调用

#include<sys/msg.h>
int msgsnd(int msqid, const void* msg_ptr, size_t msg_sz, int msgflg);
  • 作用:将一条消息加入消息队列
  • 参数:
    • msqid:由msgget调用返回的标识符
    • msg_ptr:指针,指向一个准备发送的消息(消息类型见下文)
    • msg_sz:消息的数据(mtxt)部分长度
    • msgflg:控制msgsnd的行为
  • 返回值:
    • 成功:0,并修改部分msqid_ds
    • 失败:-1
struct msgbuf{long mtype;/*消息类型*/char mtext[512];/*消息数据*/

msgrcv系统调用

#include<sys/msg.h>
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype,int msgflg);
  • 作用:从消息队列中获取消息
  • 参数:
    • msqid:由msgget调用返回的消息队列标识符
    • msg_ptr:用于存储接收的消息
    • msg_sz:消息数据部分的长度
    • msgtype:指定接收何种类型的消息
    • msgflg:控制msgrcv函数的行为
  • 返回值:
    • 成功:0,并修改msqid_ds的部分值
    • 失败:-1

msgctl系统调用

#include<sys/msg.h>
int msgctl(int msqid, int command, struct msqid_ds *buf);
  • 作用:控制消息队列的某些属性
  • 参数:
    • msqid:由msgget调用返回的消息队列标识符
    • command:要执行的命令
    • buf:
  • 返回值:
    • 成功:取决于command参数
    • 失败:-1

command参数:

一些废话

写完这篇文章时已经是2024年的2月1日,距离这个专栏开始(2024年1月12日)已经过去了差不多三周。说实话我并不满意这个速度,在这20天里,我真正的学习的时间只有14天而已。

我每天会带上电脑,早上九点到区图书馆的自习室学习,上午写算法,下午就看书学习写专栏和博客。最初我的设想是晚上九点图书馆闭馆再回家,但往往下午六点吃完晚饭就回去了。我看过同校的一个大佬的学习经历,他在大一的暑期时就已经天天泡市图书馆了。这也是为什么别人早早进大厂,而我却找不到工作的原因。

但我确实是无法一天十二个小时都在学习,我晚上不回去打一会游戏,没多久我就坚持不下去了。包括过去的二十天,因为幻兽帕鲁的开服,我建了个服务器天天晚上都和室友在玩,每天晚上玩两三个小时的游戏是我生活的聊聊慰藉。

除了和朋友玩,我每天睡觉前还要和异地的对象玩金铲铲之战。她本来是完全不玩游戏的,我强行拉她入坑,现在她每天晚上拉我玩,不然我真不知道异地的这些日子怎么维系感情,我白天忙学习,晚上玩游戏,本就没时间和她聊天,要是没有金铲铲,估计感情用不了多久就GG了。(感谢金铲铲)

回到正题,我现在的学习规划是在这段时间同步把这个Webserver的项目和Carl的代码随想录做完。在学有余力的情况下,学习Linux的常用操作命令(应该会再开一个专栏)。

下一个阶段的学习计划是开始看哈工大的 OS网课,保持算法手感的同时开始做OS的项目(具体做MIT6.S081还是操作系统真象还原还不确定)。

等两个项目都完成后,开始大量背八股,投简历,投实习,希望能在4月前找到个实习吧。

end

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

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

相关文章

全国疫情实时监测系统(附源码)

目录 一.项目背景 1.有力支持疫情防控知识传播 2.迅速锁定“涉疫”人员流动轨迹 3.开展疫情发展态势预测与溯源 4.一图胜过千言万语&#xff01;&#xff01;&#xff01; 二.研究过程&#xff08;项目技术的利用&#xff09; 1.总述 2.所用技术介绍 2.1Python 2.2Pyt…

基于布谷鸟搜索的多目标优化matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 1. 布谷鸟搜索算法基础 2. 多目标优化问题 3. 基于布谷鸟搜索的多目标优化算法 4. 解的存储和选择策略 5.算法步骤 5.完整程序 1.程序功能描述 基于布谷鸟搜索的多目标优化&#xff0c;…

社区店加盟多少钱?费用全解及 2024 年加盟趋势

在探讨社区店加盟费用之前&#xff0c;我们首先要明确一个概念&#xff1a;社区店不仅仅是一个简单的销售点&#xff0c;更是连接品牌与消费者的桥梁。 特别是在鲜奶行业&#xff0c;社区店承载着为消费者提供新鲜、健康产品的重任。作为一名拥有多年鲜奶吧经营经验的创业者&a…

单链表的增删改查

小伙伴们&#xff0c;顺序表的增删改查已经学会了&#xff0c;今天我们学习比顺序表还难“亿”点点的链表&#xff0c;也需要增删改查。跟顺序表一样&#xff0c;还是需要创建三个文件SList.h,SList.c和test.c&#xff0c;然后做一些准备工作&#xff0c;具体文件的说明跟顺序表…

接口测试 —— Requests库介绍

1、Requests库 Requests库是用Python语言编写&#xff0c;基于urllib3模块&#xff0c;采用Apache2 Licensed开源协议的 HTTP 库。 虽然Python的标准库中urllib3模块已经包含了平常我们使用的大多数功能&#xff0c;但是它的 API使用起来让人感觉不太友好。而Requests库使用的…

【Vue3实战】TypeScript前端实战基础

【Vue3实战】TypeScript前端实战基础 前言一、TypeScript的由来二、什么是TypeScript?三、静态类型检查四、类型注解和类型推导五、可选参数和默认参数六、接口和类型别名接口接口的可选设置类型 七、类和继承接口的继承交叉类型模拟继承 八、泛型什么是泛型泛型接口泛型函数泛…

Hgame题解(第一星期)

Hgame题解&#xff08;第一星期&#xff09; Web ezHTTP 打开靶机首先看到题目提示&#xff1a;请从vidar.club访问这个页面 根据http协议&#xff0c;需要创建一个Referer字段&#xff0c;其值设置为vidar.club&#xff08;意思是从该网页跳转到靶机网页的&#xff09;&…

嵌入式系统学习(一)

嵌入式现状&#xff08;UP经历&#xff09;&#xff1a; 大厂的招聘要求&#xff1a; 技术栈总结&#xff1a; 产品拆解网站&#xff1a; 52audio 方案查询网站iotku,我爱方案网&#xff0c; 主要元器件类型&#xff1a;

【android】对于google-webrtc的性能中, memory leak

目录 zlmediakit->webrtcplay->app webrtcutil1/3 测试程序等 zlmediakit->webrtcplay->app 编译sdk 32 有时候会从开始新增5M&#xff0c;就稳定在一个值了 webrtcutil1/3 测试程序等 编译sdk 30

Oracle和Mysql数据库

数据库 Oracle 体系结构与基本概念体系结构基本概念表空间(users)和数据文件段、区、块Oracle数据库的基本元素 Oracle数据库启动和关闭Oracle数据库启动Oracle数据库关闭 Sqlplussqlplus 登录数据库管理系统使用sqlplus登录Oracle数据库远程登录解锁用户修改用户密码查看当前语…

逸学区块链【solidity】真随机数

参考Get a Random Number | Chainlink Documentation 但是很贵&#xff0c;价格 Gas Price&#xff1a;当前gas价格&#xff0c;根据网络状况而波动。Callback gas &#xff1a;返回您所请求的随机值时&#xff0c;回调请求消耗的gas 量。验证gas &#xff1a;量gas 用于验证…

Vue3学习记录(二)--- 组合式API之计算属性和侦听器

一、计算属性 1、简介 ​ 计算属性computed()&#xff0c;用于根据依赖的响应式变量的变化&#xff0c;进行自动的计算&#xff0c;并返回计算后的结果。当依赖的响应式变量发生变化时&#xff0c;computed()会自动进行重新计算&#xff0c;并返回最新的计算结果。如果依赖的…

Map和Set讲解

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f4d5;格言&#xff1a;那些在暗处执拗生长的花&#xff0c;终有一日会馥郁传香欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 集合框架 模型 Set 常见方法和说明 Set总结 Map说明 Map常见方法和说明 Map 中HashMap的 …

Linux一些实用操作

学习笔记&#xff0c;记录以下课程中关于Linux的一些实用操作。黑马程序员新版Linux零基础快速入门到精通&#xff0c;全涵盖linux系统知识、常用软件环境部署、Shell脚本、云平台实践、大数据集群项目实战等_哔哩哔哩_bilibili 目录 1 各类小技巧&#xff08;快捷键&#xff…

天翼数科实在智能,战略合作签约!Agent最大化赋能应用领域

近日&#xff0c;天翼数智科技(北京)有限公司&#xff08;以下简称“天翼数科”&#xff09;与杭州实在智能科技有限公司&#xff08;以下简称“实在智能”&#xff09;签署战略合作协议。 基于本次战略合作&#xff0c;天翼数科将与实在智能发挥各自的专业特长&#xff0c;整合…

在CentOS 7 中配置 YUM源

目录 YUM源的功能&#xff1a; YUM 源的安装过程 ps YUM工具 配置YUM仓库/YUM源 网络源&#xff1a;使用官方源 前提&#xff1a;联网 YUM源的功能&#xff1a; YUM&#xff08;Yellowdog Updater Modified&#xff09;是一个在Red Hat、CentOS、Fedora等基于RPM的Linux发…

Nat Med | 儿童急性淋巴细胞白血病基因组景观中的药物类型

今天给同学们分享一篇实验文章“Pharmacotypes across the genomic landscape of pediatric acute lymphoblastic leukemia and impact on treatment response&#xff0c;这篇文章发表在Nat Med期刊上&#xff0c;影响因子为82.9。 结果解读&#xff1a; 与ALL药物敏感性的临…

C++(17.5)——list模拟实现扩展

在上篇文章中&#xff0c;实现了的大部分功能以及部分迭代器。本片文章将对剩下的功能进行补充。 1. const迭代器&#xff1a; 对于上篇文章中实现的迭代器只能使用于非类型的对象。对于类型的遍历&#xff0c;则需要额外编写类型的迭代器。例如对于下面的场景&#xff1a; …

【持续更新】2024牛客寒假算法基础集训营1题解 | JorbanS

文章目录 [A - DFS搜索](https://ac.nowcoder.com/acm/contest/67741/A)[B - 关鸡](https://ac.nowcoder.com/acm/contest/67741/B)[C - 按闹分配](https://ac.nowcoder.com/acm/contest/67741/C)[E - 本题又主要考察了贪心](https://ac.nowcoder.com/acm/contest/67741/E)[F -…

Unknown custom element:<xxx>-did you register the component correctly解决方案

如图所示控制台发现了爆红&#xff08;大哭&#xff09;&#xff1a; 报错解释&#xff1a; 当我们看到报错时&#xff0c;我们需要看到一些关键词&#xff0c;比如显眼的“component”和“name”这两个单词&#xff0c; 因此我们就从此处切入&#xff0c;大概与组件有关系。…