Linux16 多路复用(二)

多路复用(二)

  • 1. 多路复用 - poll
    • 功能
    • 函数原型
    • 关于 pollfd 结构体类型
    • poll 代码框架
    • 一些示例代码
    • poll相较于select优点
    • poll的缺点
  • 2. 多路复用 - epoll
    • 关于epoll的三个系统调用
      • 创建 - epoll_create
      • 控制 - epoll_ctl
        • 函数原型
        • 关于 epoll_event 结构体
      • 等待 - epoll_wait
        • 函数原型
        • wait 过程
  • 3. epoll 原理
    • 两个核心成员
    • 过程
    • 回调机制
      • 注册回调函数
      • 事件触发与回调函数调用
      • 回调机制优点
  • 4. epoll 代码框架
  • 5. epoll 的优点
  • 6. epoll 的工作方式
    • Level Triggered - 水平触发(LT 工作模式)
    • Edge Triggered - 边缘触发(ET 工作模式)
    • 对比 LT 和 ET

1. 多路复用 - poll

功能

  • 主要作用和select一样,只负责等待事件就绪,不负责拷贝
  • 但主要解决select两个缺点
  1. select 支持文件数量太少
  2. 接口调用麻烦

函数原型

#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • fds输入输出型参数,这是一个指向struct pollfd类型数组的指针,每个数组元素代表一个要监视的文件描述符及其相关的事件和状态信息
  • nfds表示fds数组中元素的个数,即要监视的文件描述符的数量
  • timeout整型变量,指定poll函数的超时时间,单位是毫秒
  • 如果设置为 -1,则表示poll函数会一直阻塞,直到有事件发生或被信号中断;
  • 如果设置为 0,则poll函数会立即返回,不阻塞
  • 如果设置为大于0的值,则表示poll函数会阻塞指定的毫秒数,直到有事件发生或超时

关于 pollfd 结构体类型

struct pollfd {int   fd;         /* 文件描述符 */short events;     /* 监视的事件 */short revents;    /* 实际发生的事件 */
};
  • fd:要监视的文件描述符,可以是任何有效的文件描述符,如标准输入输出、网络套接字等。
  • events:指定要监视的事件类型,可以是以下几种事件的按位或组合
  • POLLIN:表示对应的文件描述符有数据可读。(读关心)
  • POLLOUT:表示对应的文件描述符可以写入数据。(写关心)
  • POLLERR:表示对应的文件描述符发生了错误。(错误关心)
  • POLLHUP:表示对应的文件描述符被挂断或关闭。
  • POLLNVAL:表示对应的文件描述符无效。
  • revents:这是一个由poll函数在返回时填充的字段,用于表示实际发生在该文件描述符上的事件。程序可以根据revents的值来判断具体发生了哪些事件,并进行相应的处理。
  • 返回值:poll函数的返回值表示满足事件条件的文件描述符数量。如果返回值大于0,则表示有相应的文件描述符发生了指定的事件,可以通过检查revents字段来确定具体是哪些文件描述符和事件;如果返回值为0,则表示在指定的超时时间内没有任何文件描述符发生事件;如果返回值为-1,则表示poll函数调用失败,此时可以通过errno变量获取错误码,以确定具体的错误原因。

poll 代码框架

  1. 首先需要一个 pollfd 类型数组 fd_array[] 来装下要关心的文件描述符数组并初始化(不关心的fd默认为 -1,关心的fd添加进数组中并设置event)
  2. 调用poll函数
  3. 等待 poll 的成功返回,函数的返回值代表等待成功的文件描述符数量
  4. 就绪的文件描述符需要通过遍历 fd_array,查看每一个元素的 revent 是否有对应的关系事件,如果有,则需要对该描述符进行对应操作(这些操作可能会产生新的文件描述符,需要在fd_array中添加关心。比如TCP监听套接字负责处理连接,这会产生新的文件描述符)
  5. 如果还需要重新对这些文件描述符进行等待,则回到第2步循环即可

一些示例代码

const size_t fd_Num = 1024; //数组最大值,等待文件描述符最大值,可以是其他值
pollfd fd_array[fd_Num]; //定义pollfd数组
// 初始化数组函数
void InitFdArray()
{for (int i = 0; i < fd_Num; i++){fd_array[i].fd = -1;fd_array[i].events = 0;fd_array[i].revents = 0;}
} // 添加一个描述符进关心数组
void AddFdInArray(int sockfd, int pollevent)
{for(int i = 0;i<fd_Num;i++){if(fd_array[i].fd == -1){fd_array[i].fd = sockfd; //设置对应元素的 fd 为关心的文件描述符fd_array[i].events |= pollevent; //设置添加关心break;}}//走到这表示要扩容,表示数组里面没有闲置的位置}// 从数组中移除一个文件描述符关心
void RemoveFdFromArray(int sockfd)
{for(int i = 0;i<fd_Num;i++){if(fd_array[i].fd == sockfd){fd_array[i].fd = -1;fd_array[i].events = 0;fd_array[i].revents = 0;break;}}
}// 循环等待事件
void Loop()
{while (true){int n = poll(fd_array,fd_Num,-1);if (n > 0){// 已经有事件就绪了// 跳到其他函数处理文件描述符事件}else if (n == 0){cout<<"Time is over!"<<endl;}else{cerr<<"Poll Error"<<endl;exit(0);}}
}// 主函数调用 poll
int main()
{InitFdArray();AddFdInArray(1,POLLIN);AddFdInArray(2,POLLIN);	Loop();return 0;
}

poll相较于select优点

  • 支持文件描述符数量大大增加(取决于数组大小)
  • select的读事件、写事件和异常事件关心是作为参数输入,是分开的,而poll封装了pollfd结构体,需要事件关心直接设置 event 即可,同时返回就绪的事件也可以通过 revent 读取

poll的缺点

  • 和 select 函数一样,poll 返回后,需要轮询 pollfd 来获取就绪的描述符
  • 每次调用 poll 都需要把大量的 pollfd 结构从用户态拷贝到内核中
  • 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降

2. 多路复用 - epoll

epoll是改进的poll,几乎消除了poll的所有缺点

关于epoll的三个系统调用

创建 - epoll_create

  • 功能:创建一个epoll实例
int epoll_create(int size);
  • 自从 linux2.6.8 之后,size 参数是被忽略的
  • 成功时,epoll_create函数返回一个非负整数,这个整数代表所创建的epoll实例的文件描述符。通过这个文件描述符,可以对epoll实例进行各种操作。失败则返回-1,错误码在errno当中
  • 文件描述符必须被 close() 关闭

控制 - epoll_ctl

函数原型
  • 功能:epoll 的事件注册函数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

它不同于 select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型

  • epfd: epoll_create()的返回值
  • op:表示动作,用三个宏来表示.
  1. EPOLL_CTL_ADD:将文件描述符fd添加到epoll实例epfd中,并监听event所指定的事件。
  2. EPOLL_CTL_MOD:修改已经添加到epoll实例epfd中的文件描述符fd的监听事件为event所指定的事件。
  3. EPOLL_CTL_DEL:从epoll实例epfd中删除文件描述符fd,不再监听其相关事件。
  • fd:表示需要监听的文件描述符
  • event一个指向struct epoll_event结构体的指针,是告诉内核需要监听什么事
  • 返回值:成功时,epoll_ctl函数返回0。失败时,函数返回-1,并且会设置相应的错误码errno,常见错误码如下
  • EINVAL:参数无效,如epfd不是一个有效的epoll实例文件描述符,或者op参数取值无效,或者event中的事件类型无效等。
  • EBADF:文件描述符无效,如epfd或fd不是一个有效的文件描述符。
  • EEXIST:当使用EPOLL_CTL_ADD操作时,如果要添加的文件描述符fd已经存在于epoll实例epfd中,则会返回此错误。
  • ENOENT:当使用EPOLL_CTL_MOD或EPOLL_CTL_DEL操作时,如果指定的文件描述符fd不存在于epoll实例epfd中,则会返回此错误。
  • ENOMEM:内核内存不足,无法完成相应的操作。
  • ENOSPC:系统对epoll实例中所能添加的文件描述符数量有限制,当超过这个限制时,会返回此错误。
关于 epoll_event 结构体
struct epoll_event {uint32_t events;  /* Epoll events */epoll_data_t data;  /* User data variable */
};

上面是 epoll_event结构体原型,其中 成员events 表示用于指定要监听的事件类型,可以是以下事件的按位或组合

  • EPOLLIN:表示对应的文件描述符可以读数据。
  • EPOLLOUT:表示对应的文件描述符可以写数据。
  • EPOLLERR:表示对应的文件描述符发生错误。
  • EPOLLHUP:表示对应的文件描述符被挂断。
  • EPOLLET:设置边缘触发模式,与水平触发模式相对,边缘触发模式下只有在事件状态发生变化时才会触发通知,而水平触发模式下只要文件描述符满足事件条件就会一直触发通知。

data成员变量是一个联合体,具体原型如下

typedef union epoll_data {void *ptr;int fd; //常用uint32_t u32;uint64_t u64;
} epoll_data_t;

它可以用来存储与该文件描述符相关的用户数据,通常可以将文件描述符本身或者指向包含该文件描述符相关信息的结构体指针存储在其中,以便在事件发生时能够方便地获取和使用相关信息。

等待 - epoll_wait

函数原型
  • 功能:收集在 epoll 监控的事件中已经发送的事件
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • 参数:
  • epfd: epoll_create()的返回值
  • events输出型参数,自定义的 epoll_event 结构体数组
  • maxevents: 告诉内核这个 events 数组有多少元素(即该函数返回后可以最多同时输出多少已经就绪的文件描述符)
  • timeout:超时时间,单位毫秒(0 会立即返回,-1 是永久阻塞)
wait 过程
  • epoll 将会把发生的事件赋值到 events 数组中 (events 不可以是空指针,内核只负责把数据复制到这个 events 数组中,不会去帮助我们在用户态中分配内存)
  • 如果函数调用成功,返回对应 I/O 上已准备好的文件描述符数目,如返回 0 表示已超时, 返回小于 0 表示函数失败
  • 如果复制的事件数量等于maxevents,则表示就绪队列中的事件数量超过了用户指定的最大容量,此时epoll_wait返回maxevents,用户需要再次调用epoll_wait来获取剩余的就绪事件。

3. epoll 原理

两个核心成员

  • 当某一进程调用 epoll_create 方法时,Linux 内核会创建一个 eventpoll 结构体,这个结构体中有两个成员与 epoll 的使用方式密切相关,一个是红黑树,一个是双链表(就绪队列)
struct eventpoll
{..../*红黑树的根节点,这颗树中存储着所有添加到 epoll 中的需要监控的事件*/struct rb_root rbr;/*双链表中则存放着将要通过 epoll_wait 返回给用户的满足条件的事件*/struct list_head rdlist;....
};

过程

  • 每一个 epoll 对象都有一个独立的 eventpoll 结构体,用于存放通过 epoll_ctl 方法向 epoll 对象中添加进来的事件.
  • 这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是 logN,其中 n 为树的高度).
  • 而所有添加到 epoll 中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法.
  • 这个回调方法在内核中叫 ep_poll_callback,它会将发生的事件添加到 rdlist 双链表中.
  • 在 epoll 中,对于每一个事件,都会建立一个 epitem 结构体(原型如下)
struct epitem
{struct rb_node rbn;//红黑树节点struct list_head rdllink;//双向链表节点struct epoll_filefd ffd; //事件句柄信息struct eventpoll *ep; //指向其所属的 eventpoll 对象struct epoll_event event; //期待发生的事件类型
}
  • 当调用 epoll_wait 检查是否有事件发生时只需要检查 eventpoll 对象中的rdlist 双链表中是否有 epitem 元素即可.
  • 如果 rdlist 不为空,则把发生的事件复制到用户态(即复制给函数 epoll_wait 的第二个参数),同时将事件数量返回给用户. 这个操作的时间复杂度是 O(1).

回调机制

  • epoll的回调机制是其高效处理 I/O 事件的核心所在

注册回调函数

当使用epoll_ctl向epoll实例中添加一个要监听的文件描述符时,内核会为该文件描述符对应的设备驱动程序注册一个回调函数。这个回调函数的主要作用是当该文件描述符上的事件发生时,由内核自动调用该函数来通知epoll实例相应事件的发生。不同的设备驱动程序会根据自身的特点和功能实现相应的回调函数,例如对于网络套接字的驱动程序,其回调函数会在套接字的状态发生变化时被调用,如数据可读、可写、连接建立、连接断开等情况。

事件触发与回调函数调用

当被监听的文件描述符上的事件发生时,例如网络套接字接收到了新的数据,对应的设备驱动程序会检测到这个事件,并自动调用之前注册的回调函数。在回调函数中,会将该文件描述符及其对应的事件信息传递给epoll核心模块,epoll核心模块收到这些信息后,会将该文件描述符及其事件信息添加到epoll实例的就绪队列中,从而表示该文件描述符上有相应的事件已经就绪,可以被用户程序获取和处理

回调机制优点

  • 高效性:通过回调机制,内核能够在事件发生的第一时间通知epoll实例,而不需要像传统的select或poll那样,用户程序需要不断地轮询所有被监听的文件描述符来检查事件是否发生。这样大大减少了不必要的系统调用和上下文切换,提高了系统的整体性能和效率,尤其在处理大量并发连接但只有少数活跃连接的场景下,能够显著降低 CPU 的使用率。
  • 实时性回调函数在内核层面被触发,能够及时地将事件信息传递给epoll实例,从而保证了用户程序能够尽快地获取到就绪事件并进行处理,提高了系统对 I/O 事件的响应速度和实时性。
  • 灵活性:设备驱动程序可以根据不同的设备类型和事件类型实现各自的回调函数,这样可以针对不同的硬件设备和 I/O 场景进行定制化的事件处理,提高了系统的灵活性和可扩展性。

4. epoll 代码框架

  • epoll 代码与上面三个函数息息相关,大致也就三步(创建,控制,等待)
const size_t maxsize = 1024;
struct epoll_event ret_events[maxsize]; //创建一个epoll_event数组来接收已就绪的文件描述符//添加进epoll关心文件描述符的函数
void AddFdToEpoll(int epfd, int fd,)
{struct epoll_event event;event.data.fd = fd;event.events = EPOLLIN; //默认添加读关心int n = epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&event);
}//从epoll删除关心文件描述符的函数
void DeleteFdToEpoll(int epfd, int fd)
{int n = epoll_ctl(epfd,EPOLL_CTL_DEL,fd,nullptr);
}//循环等待epoll关心事件void Loop(int epfd)
{while(true){int n = epoll_wait(epfd,ret_events,maxsize,-1);if(n > 0){//表示一些文件描述符已经就绪//这里是处理这些文件描述符代码}else if(n == 0){cout<<"time out"<<endl;break;}else{cout<<"epoll Fail"<<endl;break;}}
}
int main()
{int epfd = epoll_create(1); //创建epoll实例//假设这部分申请了文件描述符3,4做tcp套接字AddFdToEpoll(epfd, 3);AddFdToEpoll(epfd, 4);Loop(epfd);DeleteFdToEpoll(epfd,3);DeleteFdToEpoll(epfd,4);close(3);close(4);close(epfd);return 0;
}

5. epoll 的优点

  • 接口使用方便:虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
  • 数据拷贝轻量:只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而 select/poll 都是每次循环都要进行拷贝
  • 事件回调机制:避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度 O(1). 即使文件描述符数目很多, 效率也不会受到影响
  • 没有数量限制:件描述符数目无上限.

6. epoll 的工作方式

Level Triggered - 水平触发(LT 工作模式)

例子:快递员通知你拿快递,你不拿或者没拿完,快递员一直通知你

  • 当 epoll 检测到 socket 上事件就绪的时候
  • 如果不进行处理. 或者只处理一部分.在第二次调用epoll_wait 时, epoll_wait 仍然会立刻返回并通知 socket 事件就绪
  • 直到缓冲区上所有的数据都被处理完,epoll_wait 才不会立刻返回.
  • 支持阻塞读写和非阻塞读写

Edge Triggered - 边缘触发(ET 工作模式)

例子:快递员通知你拿快递,你不拿或者没拿完,快递员直接跑路;直到下一次你又有新的快递,才会通知你

  • 当 epoll 检测到 socket 上事件就绪时, 必须立刻处理.
  • 无论你是忽略,或者只处理一部分,在第二次调用epoll_wait 的时候, epoll_wait 不会再返回了
  • 也就是说, ET 模式下, 文件描述符上的事件就绪后, 只有一次处理机会.
  • ET 的性能比 LT 性能更高( epoll_wait 返回的次数少了很多). Nginx 默认采用ET 模式使用 epoll.
  • 只支持非阻塞的读写(需要设置套接字为非阻塞)

对比 LT 和 ET

  • LT 是 epoll 的默认行为. 使用 ET 能够减少 epoll 触发的次数. 但是代价就是强逼着程序猿一次响应就绪过程中
    就把所有的数据都处理完. 相当于一个文件描述符就绪之后, 不会反复被提示就绪, 看起来就比 LT 更高效一些.
  • 但是在 LT 情况下如果也能做到每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话, 其实性能也是一样的. 另一方面, ET 的代码复杂程度更高了.
  • select 和 poll 其实也是工作在 LT 模式下. epoll 既可以支持 LT, 也可以支持 ET

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

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

相关文章

数字化那点事:一文读懂物联网

一、物联网是什么&#xff1f; 物联网&#xff08;Internet of Things&#xff0c;简称IoT&#xff09;是指通过网络将各种物理设备连接起来&#xff0c;使它们可以互相通信并进行数据交换的技术系统。通过在物理对象中嵌入传感器、处理器、通信模块等硬件&#xff0c;IoT将“…

Jmeter的后置处理器(二)

5--JSR223 PostProcessor 功能特点 自定义后处理逻辑&#xff1a;使用脚本语言编写自定义的后处理逻辑。支持多种脚本语言&#xff1a;支持 Groovy、JavaScript、BeanShell 等脚本语言。动态参数传递&#xff1a;将提取的数据存储为变量&#xff0c;供后续请求使用。灵活性高…

非对称之美(贪心)

非对称之美(贪心) import java.util.*; public class Main{public static void main(String[] arg) {Scanner in new Scanner(System.in);char[] ch in.next().toCharArray(); int n ch.length; int flag 1;for(int i 1; i < n; i) {if(ch[i] ! ch[0]) {flag …

(11)(2.2) BLHeli32 and BLHeli_S ESCs(一)

文章目录 前言 1 参数说明 前言 BLHeli 固件和配置应用程序的开发是为了允许配置 ESC 并提供额外功能。带有此固件的 ESC 允许配置定时、电机方向、LED、电机驱动频率等。在尝试使用 BLHeli 之前&#xff0c;请按照 DShot 设置说明进行操作(DShot setup instructions)。 根据…

初始化列表和在构造函数体内赋值有什么区别?

1. 成员初始化方式 初始化列表&#xff1a; 在构造函数参数列表后面&#xff0c;使用冒号 : 进行初始化&#xff0c;成员变量在对象创建时直接初始化。语法&#xff1a;ClassName(Type1 arg1, Type2 arg2) : member1(arg1), member2(arg2) { }对于基本类型&#xff0c;使用初始…

Redis持久化、主从及哨兵架构详解

Redis持久化 RDB快照&#xff08;snapshot&#xff09; 在默认情况下&#xff0c;Redis将内存数据库快照保存在名字为dump.rdb的二进制文件中。 你可以对Redis进行设置&#xff0c;让它在“N秒内数据集至少有M个改动”这一条件被满足时&#xff0c;自动保存一次数据集。 比…

【Linux庖丁解牛】—Linux基本指令(下)!

目录 1、grep指令 2、zip/unzip指令 3、sz/rz指令 4、tar指令 ​编辑 5、scp指令 6、bc指令 7、uname –r指令 8、重要的几个热键 9、关机 10、完结撒花 1、grep指令 grep是文本过滤器&#xff0c;其作用是在指定的文件中过滤出包含你指定字符串的内容&#xff0c;…

Flutter踩坑记录(一)debug运行生成的项目,不能手动点击运行

问题 IOS14设备&#xff0c;切后台划掉&#xff0c;二次启动崩溃。 原因 IOS14以上 flutter 不支持debugger模式下的二次启动 。 要二次启动需要以release方式编译工程安装至手机。 操作步骤 清理项目&#xff1a;在命令行中运行flutter clean来清理之前的构建文件。重新构…

十六:请求与响应的上下文

在Web开发中,请求与响应是数据流动的核心。客户端发出请求,服务器返回响应,这一过程看似简单,但背后涉及复杂的上下文管理。理解并优化Web请求与响应的上下文,不仅能提升系统性能,还能改善用户体验。 请求与响应的基础 Web请求是由客户端(通常是浏览器或移动应用)向服…

联通光猫(烽火通信设备)改桥接教程

一、获得超级密码 1.打开telnet连接权限 http://192.168.1.1/telnet?enable1&key9070D3BECD70&#xff08;MAC地址&#xff09;2.连接光猫获取密码 telnet 192.168.1.1 用户名&#xff1a;admin 密码&#xff1a;Fh9070D3BECD70连接成功后 load_cli factory show admin_…

新华三H3CNE网络工程师认证—子接口技术

子接口&#xff08;subinterface&#xff09;是通过协议和技术将一个物理接口&#xff08;interface&#xff09;虚拟出来的多个逻辑接口。在VLAN虚拟局域网中&#xff0c;通常是一个物理接口对应一个 VLAN。在多个 VLAN 的网络上&#xff0c;无法使用单台路由器的一个物理接口…

基于Amazon Bedrock:一站式多模态数据处理新体验

目录 引言 关于Amazon Bedrock 基础模型体验 1、进入环境 2、发现模型及快速体验 3、打开 Amazon Bedrock 控制台 4、通过 Playgrounds 体验模型 &#xff08;1&#xff09;文本生成 &#xff08;2&#xff09;图片生成 关于资源清理 结束语 引言 在云计算和人工智能…

react native 安装好apk后无法打开

react native 打包好apk安装完成&#xff0c;没有打开app按钮&#xff0c; 在AndroidManifest.xml中 <intent-filter><action android:name"android.intent.action.MAIN" /><category android:name"android.intent.category.LAUNCHER" /&…

【C++】踏上C++学习之旅(九):深入“类和对象“世界,掌握编程的黄金法则(四)(包含四大默认成员函数的练习以及const对象)

文章目录 前言1. 实现Date类的构造函数2. 实现Date类的拷贝构造函数3. 实现Date类的赋值运算符重载4. 实现各Date对象之间的比较接口5. 实现Date对象的加减接口6. const成员7. 取地址及const取地址操作符重载 前言 在我们前面学习到了"类和对象"的四大默认成员函数(…

项目中排查bug的思路案例

bug描述&#xff1a;调用了删除的接口&#xff0c;执行成功了&#xff0c;也删掉了选中的数据&#xff0c;但是不执行删除后的处理操作&#xff0c;会报一个“系统未知错误&#xff0c;请反馈给管理员” 解决&#xff1a; 成功删掉了数据&#xff0c;但删除后的操作没有执行&a…

Mysql数据库1——基本原理和基础操作

文章目录 Mysql数据库1——基本原理和基础操作1. 基本概念2. Mysql体系结构2.1 连接层2.2 服务层2.3 存储引擎层 3. 三级范式与反范式4. 完整性约束4.1 实体完整性约束4.2 参照完整性约束 5. CRUDDDLDMLDCLDQL 6. 高级查询基础查询条件查询分页查询查询结果排序分组聚合查询联表…

初学 flutter 问题记录

windows搭建flutter运行环境 一、运行 flutter doctor遇到的问题 Xcmdline-tools component is missingRun path/to/sdkmanager --install "cmdline-tools;latest"See https://developer.android.com/studio/command-line for more details.1&#xff09;cmdline-to…

【STM32】时钟系统

在我们学习STM32之前&#xff0c;我们需要先了解STM32系列芯片的时钟系统&#xff0c;这个是我们学习这个芯片的基础。为什么时钟系统这么重要呢&#xff1f;举个例子&#xff0c;如果把STM32比作我们的整个人体&#xff0c;那么时钟就是维持我们人体正常工作的心脏。STM32芯片…

Android Studio 设置不显示 build-tool 无法下载

2024版本查看build-tool版本 File -> Settings -> Languages & Frameworks -> Android SDK 或者直接打开Settings后搜索“SDK” 解决方案 将 Android Studio 升级到2022.2.1以上的版本将 C:/Windows/System32/drivers/etc/hosts 文件用管理员身份打开&#xff0c…

H.265流媒体播放器EasyPlayer.js H5流媒体播放器关于如何查看手机端的日志信息并保存下来

现今流媒体播放器的发展趋势将更加多元化和个性化。人工智能的应用将深入内容创作、用户体验优化等多个方面&#xff0c;带来前所未有的个性化体验。 EasyPlayer.js H.265流媒体播放器属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#…