深入理解网络 I/O 多路复用:Epoll

在这里插入图片描述

🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者
📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代
🌲文章所在专栏:网络 I/O
🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识
💬 向我询问任何您想要的东西,ID:vnjohn
🔥觉得博主文章写的还 OK,能够帮助到您的,感谢三连支持博客🙏
😄 代词: vnjohn
⚡ 有趣的事实:音乐、跑步、电影、游戏

目录

  • 前言
  • Epoll 函数
    • EPOLL_CREATE
    • EPOLL_CTL
    • EPOLL_WAIT
    • epoll_event 数据结构
    • 边沿触发、水平触发
    • 小结
  • Epoll 内核源码
  • 图解分析
    • epoll VS select/poll 工作原理
    • epoll VS select/poll 日志追踪
    • Epoll 优势之处
  • 总结

前言

Unix/Linux 下可用的 I/O 模型有以下五种:

  1. 阻塞式 I/O
  2. 非阻塞式 I/O
  3. I/O 复用(select、poll)
  4. 信号驱动式 I/O(SIGIO)
  5. 异步 I/O

在 Linux 中操作内核时,所有的无非三种操作,分别是输入、输出、报错输出

0-输入
1-输出
2-报错输出

一个输入操作通常包括两个不同的阶段:

  • 等待数据准备好
  • 从内核向进程复制数据

对于一个套接字(Socket)的输入操作,第一步通常涉及等待数据从网络中;当所等待分组到达时,它被复制到内核中的某个缓冲区,第二步就是把数据从内核缓冲区复制到应用进程缓冲区

Epoll 函数

在 Epoll 多路复用模型中,最主要的是涉及到了三个系统函数指令,分别是:epoll_create、epoll_ctl、epoll_wait

EPOLL_CREATE

借助:man 2 epoll_create 帮助文档来学习该函数

通过 epoll_create、epoll_create1 函数,打开 epoll 文件描述符

int epoll_create(int size);
int epoll_create1(int flags);

epoll_create 返回指向新的 Epoll 实例的文件描述符,该文件描述符用于对 epoll 接口的所有后续调用,当不再需要时,将调用 close 函数关闭 epoll_create 返回的文件描述符,当引用 epoll 实例的所有文件描述符都关闭时,内核将销毁该实例并释放相关资源以供资源重用

epoll_create1:若 flags 为 0,除了删除过时的 size 参数之外,epoll_create1 与 epoll_create 是相同的

如果指向新的 epoll 实例描述符成功的话,这些系统调用函数将会返回一个非负数的文件描述符,若出现错误,将返回 -1,并设置 errno 来指示错误.

EPOLL_CTL

借助:man 2 epoll_ctl 帮助文档来学习该函数

通过 epoll_ctl 来承担 epoll 描述符的控制接口

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

这个系统调用对文件描述符 epfd 引用的 epoll 实例执行控制操作,它请求对目前文件描述符 fd 执行 op 操作

op 参数可选值如下:

  1. EPOLL_CTL_ADD:在文件描述符 epfd 引用的 epoll 实例上注册目标文件描述符 fd,并关联事件,内部文件链接到 fd
  2. EPOLL_CTL_MOD:更改与目标文件描符 fd 关联的事件
  3. EPOLL_CTL_DEL:从 epfd 引用的 epoll 实例中删除或注销目标文件描述符 fd,该事件被忽略,并且可以为空

EPOLL_WAIT

借助:man 2 epoll_wait 帮助文档来学习该函数

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

epoll_wait 系统调用等待文件描述符 epfd 引用的 epoll 实例,事件指向的内存区域将包含调用者可用的事件,epoll_wait 最多返回 maxevents 个事件,其参数必须大于 0

timeout 函数指定 epoll_wait 将阻塞的最小毫秒数,若指定 timeout 为 -1 会导致 epoll_wait 无限期阻塞,而指定 timeout 为 0 会导致 epoll_wait 立即返回,即使没有事件可用

在 epoll_wait 调用时,在给定的 timeout 时间内,当在监控的所有 fd 中有事件发生时,就返回用户态的进程

epoll_event 数据结构

typedef union epoll_data {void    *ptr;int      fd;uint32_t u32;uint64_t u64;
} epoll_data_t;struct epoll_event {uint32_t     events;    /* Epoll events */epoll_data_t data;      /* User data variable */
};

作为 epoll 中重要返回的数据结构,每个返回结构的数据将包含用户使用:epoll_ctl「EPOLL_CTL_ADD、EPOLL_CTL_MOD」设置的相同数据,而 events 成员将包含返回的事件位字段

边沿触发、水平触发

Epoll 提供两种事件接口:边沿触发(ET:edge-triggered)、水平触发(LT:level-triggered)

边沿触发

  • socket 接收缓冲区状态变化时触发读事件,即空的接收缓冲区刚接收到数据时触发读事件
  • socket 发送缓冲区状态变化时触发写事件,即满的缓冲区刚空出空间时触发读事件

水平触发

  • socket 接收缓冲区不为空时,有数据可读,读事件一直触发
  • socket 发送缓冲区不满,可以继续写入数据,写事件一直触发

使用 EPOLLET 标志的应用程序应该使用非阻塞文件描述符,以避免在处理多个文件描述符的任务中出现阻塞读写。建议使用 epoll 作为边缘触发(EPOLLET)接口的方法如下:

  1. 使用非阻塞文件描述符
  2. 在 read 或 write 之后等待事件返回 EAGAIN

例如,两条线分别有数据 ABC、DEF,水平触发的处理顺序:ADBECF,边缘触发的处理顺序:ABCDEF

Nginx、Redis 都使用了 Epoll 多路复用模型

Nginx 使用的是边缘触发 ET、Redis 使用的是水平触发 LT

再举例而言说明两者的区别:你有急事打电话找人,如果对方一直不接,那你只有一直打,直到他接电话为止,这就是 LT 模式;如果不急,电话打过去对方不接,那就等有空再打,这就是 ET 模式

小结

从调用方式可以看出 epoll 对比 select/poll 优越之处,因为 每次调用时都要传递你所要监控的所有 socket 给 select/poll 进行系统调用,这也就意味着需要将用户态的 socket 列表 copy 到内核态,若以万计数的 socket 会导致每次都要 copy 几十或几百 KB 的内存到达内核态,非常的低效,而当我们调用 epoll_wait 时就相当于以往调用 select/poll,但这此时却不用传递 socketfd 给到内核,因为内核通过 epoll_ctl 函数已经拿到了所要监控的 socketfd 列表

实际在你调用 epoll_create 后,内核就已经在开始准备帮你存储要监控的 socket 了,每次调用 epoll_ctl 只是在往内核的数据结构 > 红黑树,塞入新的 socketfd 罢了

Epoll 内核源码

#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;/* Set up listening socket, 'listen_sock' (socket(),bind(), listen()) */epollfd = epoll_create(10);
if (epollfd == -1) {perror("epoll_create");exit(EXIT_FAILURE);
}ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {perror("epoll_ctl: listen_sock");exit(EXIT_FAILURE);
}for (;;) {nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);if (nfds == -1) {perror("epoll_pwait");exit(EXIT_FAILURE);}for (n = 0; n < nfds; ++n) {if (events[n].data.fd == listen_sock) {conn_sock = accept(listen_sock,(struct sockaddr *) &local, &addrlen);if (conn_sock == -1) {perror("accept");exit(EXIT_FAILURE);}setnonblocking(conn_sock);ev.events = EPOLLIN | EPOLLET;ev.data.fd = conn_sock;if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,&ev) == -1) {perror("epoll_ctl: conn_sock");exit(EXIT_FAILURE);}} else {do_use_fd(events[n].data.fd);}}
}

socket()、bind()、listen() 是所有的 I/O 模型都必须要经过的操作

  • epollfd=epoll_create(10):创建一个 epoll 文件描述符 > epfd,用于执行后续所有的 epoll 操作
  • 若 epoll_create 函数返回 -1,代表操作失败,失败的原因可能是内核中文件描述符的大小超出了限制
  • 通过 epoll_ctl 函数将新获取的 socket 套接字放入到 epoll 红黑树中,在内核会单独为 epoll 开辟一些数据结构,存放这些 socket 信息
  • EPOLL_CTL_ADD 代表新增 op 操作,成功返回 0,失败则返回 -1
  • 第一个死循环进行 wait 阻塞等待,主要是调用 epoll_wait 函数,类似 select/poll 的操作,从红黑树中复制出来的链表有状态的文件描述符,将拿到的结果存放在 events 数组中

epoll_wait(epollfd, events, MAX_EVENTS, -1):第四个参数的 -1 代表不超时

  • 当拿到有结果的 events 数组以后,对这些有状态的文件描述符进行遍历,若当前文件描述符等于传入的文件描述符,那么则对当前描述符进行 accept 函数调用,将套接字对应的 IP、Port 进行绑定,成功则返回文件描述符,否则返回 -1

此时,代表有新的客户端连接了,需要进行 accept 监听,生成一个新的 socketfd,并且设置为非阻塞运行的方式,调用 epoll_ctl 将新的 socketfd 放入到红黑树中

  • 若当前文件描述符不等于传入的文件描述符,那么就使用已被监听的文件描述符中的数据

通过命令:cat /proc/sys/fs/epoll/max_user_watches,可以查看系统上所有 epoll 实例注册的文件描述符总数的最大限制

在这里插入图片描述

图解分析

其实 Epoll 的模型大致上和 select/poll 模型大致上是一样的,只不过它们额外做的处理工作不一样而已,下面具体来介绍

epoll VS select/poll 工作原理

在这里插入图片描述

如上图,所有的 I/O 模型,都会经过三个函数的调用:socket -> bind -> listen,然后 accept 等待客户端建立连接再分配新的 fd 文件描述符!

经历过三个函数调用以后,epoll、select/poll 做以下对比:

  1. 经过通用的函数调用以后,在 Epoll 中会调用 epoll_create 函数生成 fd7,再调用 epoll_ctl 将应用程序与客户端之间新创建的文件描述符放入到内核中维护的红黑树数据结构中,当有客户端有数据 Send-Queue 发送到网卡,然后会到达对应文件描述符 fd4「socket 函数生成的文件描述符」Buffer 缓冲区中,在此时,epoll 对比 select/poll 多做了一下延伸处理工作:将红黑树里有状态的文件描述符 fds 拷贝到链表中,随即在调用 epoll_wait 函数就可以从链表中取出所有有状态(R/W)的 fds
  2. 经过通用的函数调用以后,在 select/poll 中会通过 socket 生成的文件描述符 fd 到内核循环遍历全量的文件描述符以后,返回哪些有状态(R/W)的 fds,当我们在应用程序什么时候调用 select 方法就会触发一次全量的 fds 遍历
  3. 无论是 epoll 还是 select/poll,即使不调用内核,内核也会随着中断的处理机制完成所有 fd 文件描述符的状态设置,而 Epoll 就是在中断处理时多做了一件事情:将红黑树里有状态的 fd 拷贝到了链表中,当应用程序要取来进行处理时,直接取链表里面的 fds 即可(O(1)),而不需要像 select/poll 那样去循环遍历(O(n)

epoll VS select/poll 日志追踪

可以通过 strace 命令来追踪 epoll、select/poll 内核底层的源码是如何处理的,Java 代码与上一篇讲解的 select/poll 多路复用是一致的。

select/poll

运行命令:

strace -ff -o poll java -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.PollSelectorProvider SelectMultiplexingSocketThread

JVM native 分配了数组来保存 fd 信息,strace 源码追踪:

socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 4
# 如 Java 代码:server.configureBlocking(false)
fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK)    = 0bind(4, {sa_family=AF_INET6, sin6_port=htons(8090), inet_pton(AF_INET6, "::", &sin6_addr), ...) = 0listen(4, 50)
# 返回 = 1,代表一个 fd 有事件到来
# 返回 = -1,代表非阻塞情况下,没有事件
# 返回 = 0,代表调用超时且没有事件返回
ppoll([{fd=5, events=POLLIN}, {fd=4, events=POLLIN}], 2, NULL, NULL, 0) = 1 ([{fd=4, revents=POLLIN}])
# 新的客户端连接进来,分配的是 fd7
accept(4, {sa_family=AF_INET6, sin6_port=htons(60292), inet_pton(AF_INET6, "::1", ...) = 7
fcntl(7, F_SETFL, O_RDWR|O_NONBLOCK)    = 0

epoll

strace -ff -o epoll java -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.EPollSelectorProvider SelectMultiplexingSocketThread

运行命令:

socket(AF_UNIX, SOCK_STREAM, 0)         = 4
fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
bind(4, {sa_family=AF_INET6, sin6_port=htons(8090), inet_pton(AF_INET6, "::",...) = 0
listen(4, 50) 
# 创建了 epfd 7
epoll_create1(0)                        = 7
# 将 socket 返回的文件描述符放入红黑树中
epoll_ctl(7, EPOLL_CTL_ADD, 4, {EPOLLIN, {u32=4, u64=545460846596}}) = 0
# 未设置超时时间会一直阻塞,设置了超时时间,在时间内无事件会返回 0
epoll_pwait(7,
# 新的客户端连接进来,分配 fd8
accept(4, {sa_family=AF_INET6, sin6_port=htons(60294), inet_pton(AF_INET6, "::1", &sin6_addr), ...) = 8
fcntl(8, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
# 将新客户端 fd8 天假到红黑树中
epoll_ctl(7, EPOLL_CTL_ADD, 8, {EPOLLIN, {u32=8, u64=545460846600}}) = 0
# 继续循环 epoll_wait
epoll_pwait(7,

在 Java NIO 包下 Selector 通过一套代码在底层实现了 select/poll、epoll 两种 I/O 模型,对应的实现类分别是:sun.nio.ch.PollSelectorProvider、sun.nio.ch.EPollSelectorProvider

Epoll 优势之处

Epoll 高效在于:当我们调用 epoll_ctl 往内核塞入百万个 socket 时,epoll_wait 仍然可以飞快的返回,并会有效的将有发生事件的 socket 给到应用程序;这主要是在调用 epoll_create 时,内核除了在文件系统里建了 epfd,还在内核中建立了一个红黑树结构用于存储以后 epoll_ctl 传来的 socket 以外,还会再建立一个链表,用于存储哪些准备就绪的事件,当 epoll_wait 调用时,只需要仅仅观察这个链表有没有数据即可,有数据就返回,无数据就 sleep 等待 timeout 时间到,所以,epoll_wait 非常快.

每次都是 O(1) 的操作,不会在内核中发生循环遍历寻找的动作,以及也会减少用户态、内核态之间的大额数据交互,减少了资源的浪费及无效时间的行为.

总结

该篇博文主要介绍的就是比较重要比较核心的多路复用模型 Epoll,先简略说明 Epoll 重要的三大函数:epoll_create、epoll_ctl、epoll_wait,在其中说到了 Epoll 事件接口:边沿触发(ET:edge-triggered)、水平触发(LT:level-triggered),提及到了 Epoll 内核中关键的源码部分,使用三大函数巧妙结合起来实现 epoll 高效的多路复用,在底层采用红黑树结构存储所有的 socket fd 信息,采用链表结构存储所有有事件状态的 socket fd 信息,最后图解+日志追踪分析了 Epoll 与 select/poll 之间的区别以及介绍 Epoll 优势之处,希望您能够喜欢,感谢三连支持!

参考文献:

  1. 《UNIX网络编程 卷1:套接字联网API(第3版)》— [美] W. Richard Stevens Bill Fenner Andrew M. Rudoff
  2. Epoll原理 — 千寻
  3. Epoll — dreamgoing

学习帮助文档:

  • man pages:yum install man
  • pthread man pages:yum -y install man-pages

🌟🌟🌟愿你我都能够在寒冬中相互取暖,互相成长,只有不断积累、沉淀自己,后面有机会自然能破冰而行!

博文放在 网络 I/O 专栏里,欢迎订阅,会持续更新!

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring、MySQL,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!

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

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

相关文章

Odoo:行业领先的免费开源供应链管理系统

先进且开源的供应链管理系统和全球供应链协作优化方案 为满足复杂的供应链和库存管理要求&#xff0c;如今绝大多数企业都不得不部署多个供应链管理软件和库存管理系统软件。如何利用一个库存管理与供应链管理软件&#xff0c;跨地区、跨时区地管理现代供应链&#xff1f;Odoo…

使用入耳耳机对耳朵有损害吗?入耳耳机和骨传导耳机哪款更值得入手?

由于入耳式耳机的传声原理&#xff0c;长时间使用是会对耳朵造成损害的&#xff0c;骨传导耳机相比与入耳耳机&#xff0c;不用入耳佩戴&#xff0c;还能在一定程度上保护听力&#xff0c;所以骨传导耳机更值得入手。 一、入耳耳机和骨传导耳机有什么不同 人的听觉系统分为搜…

lammps编译(2Aug2023、intel2020、rtx4070ti)

说明&#xff1a; [rootnode101 ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) [rootnode101 ~]# gcc -v Using built-in specs. COLLECT_GCCgcc COLLECT_LTO_WRAPPER/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper Target: x86_64-redhat-lin…

Vmd+lstm代码详解 完整代码数据可直接运行

项目视频讲解:Vmd+lstm时间序列预测分类回归预测代码详解 完整代码可直接运行_哔哩哔哩_bilibili 项目演示效果: 代码详解: # -*- coding: utf-8 -*- # 导入库pip install openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple import pandas as pd import numpy as np fr…

QVTK 可视化

#ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow>#include <vtkNew.h> // 智能指针 #include <QVTKOpenGLNativeWidget.h> #include <vtkCylinderSource.h> // 圆柱#include <vtkPolyDataMapper.h&g…

OpenHarmony应用开发——在标准OpenHarmony上运行应用-标准OpenHarmony工程设置

一、前言 前面我们创建了一个工程并使其在HarmonyOS系统上运行&#xff0c;本文我们来阐述一下如何在标准的OpenHarmony开发板或系统上运行。 二、详细步骤 1.下载并配置OpenHarmony SDK 首先&#xff0c;打开Settings. 将SDK选择为OpenHarmony&#xff0c;第一次选择路径应该…

java SSM教师工作量管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM 教师工作量管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要…

2023年12月16日(星期六)骑行樱花谷

2023年12月16日 (星期六) 骑行樱花谷(赏冬樱花&#xff09;&#xff0c;早8:30到9:00&#xff0c; 郊野公园西门集合&#xff0c;9:30准时出发 【因迟到者&#xff0c;骑行速度快者&#xff0c;可自行追赶偶遇。】 偶遇地点:郊野公园西门集合 &#xff0c;家住东&#xff0c;南…

vue实现自动打字效果(带光标效果)

代码介绍(其实就是通过字符串截取加定时拼接完成的,我相信有时间都能琢磨出来,来这里就是为了省事) 上vue页面代码: <template><div idApp><h2>{{text}}<span ref"fou" class"fousdis">{{_}}</span></h2></div>…

【数学建模】《实战数学建模:例题与讲解》第十二讲-因子分析、判别分析(含Matlab代码)

【数学建模】《实战数学建模&#xff1a;例题与讲解》第十二讲-因子分析、判别分析&#xff08;含Matlab代码&#xff09; 基本概念时间判别费歇判别贝叶斯判别 习题10.31. 题目要求2.解题过程3.程序4.结果 习题10.6&#xff08;1&#xff09;1. 题目要求2.解题过程——对应分析…

任意文件读取漏洞

使用方法php://filter/readconvert.base64-encode/resourcexxx 任意文件读取漏洞 php://filter/readconvert.base64-encode/resourceflag 在url后边接上 以base64的编码形式 读取flag里面的内容 php://filter/readconvert.base64encode/resourceflag 用kali来解码 创建一个文…

使用Python实现单链表

目录 一、引言 二、节点的定义 三、链表的创建 四、插入节点 五、删除节点 六、遍历链表 七、节点的查找 八、总结 一、引言 单链表是一种常用的数据结构&#xff0c;它由一系列节点组成&#xff0c;每个节点包含一个数据元素和指向下一个节点的指针。单链表可以用来存…

Qt 中文处理

windows下 Qt显示中文的几种方式&#xff1a; 1&#xff0c; 环境&#xff1a;Qt 5.15.2 vs2019 64位 win11系统 默认用Qt 创建的文件使用utf-8编码格式&#xff0c;此环境下 中文没有问题 ui->textEdit->append("中文测试"); 2&#xff0c; 某些 低于…

【MySQL备份】MySQL备份工具-MyDumper

目录 什么是MyDumper MyDumper优势有哪些 如何安装MyDumper 参数解释 1 mydumper参数解释 备份流程 一致性快照如何工作&#xff1f; 如何排除&#xff08;或包含&#xff09;数据库&#xff1f; 输出文件 Metadata文件 ​编辑 表数据 文件 表结构 文件 建库文件…

【Unity学习笔记】光照简介

本节主要是简单介绍一些常见的光照组件和渲染设置。 文章目录 灯光类型平行光Directional Light点光源Point Light聚光灯Spot Light面积光 Area Light 阴影设置全局光照明光照模式直接光照与间接光照Mixed Lighting 光照探针Light Probe Group光照探针组 反射探针 灯光类型 在…

工具应用:Robot Framework->对协议级接口进行测试

实验简介 本节实验主要为大家讲解如何利用Robot Framework结合常用的关键字完成对Agileone系统中的“需求提案”模块进行协议级接口的自动化测试脚本开发。 实验目的 &#xff08;1&#xff09; 掌握RF的Requests库的常用关键字及用法。 &#xff08;2&#xff09; 能够熟练…

一文速览字节最新分布式操作系统KubeWharf

一文速览字节最新分布式操作系统KubeWharf KubeWharf 是字节跳动基础架构团队在对 Kubernetes 进行了大规模应用和不断优化增强之后的技术结晶。 这是一套以 Kubernetes 为基础构建的分布式操作系统&#xff0c;由一组云原生组件构成&#xff0c;专注于提高系统的可扩展性、功…

怪兽吃糖果

欢迎来到程序小院 怪兽吃糖果 玩法&#xff1a;左右飞出的糖果&#xff0c;点击鼠标糖果即为怪兽吃掉&#xff0c;不同的糖果不同的分数奖励&#xff0c; 吃不掉的糖果会扣除一次生命&#xff0c;共三次生命值&#xff0c;点击炸弹游戏结束&#xff0c;快去吃糖果吧^^开始游戏…

基于ssm大学生创新创业平台项目管理子系统设计与实现论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对大学生创新创业项目信息管理混乱&#xff0c;出错率高&#xff0c;信…

Redis持久化AOF详解

基础面试题 什么是AOF AOF&#xff08;Append-Only File&#xff09;用于将Redis服务器收到的写操作追加到日志文件&#xff0c;通过该机制可以保证服务器重启后依然可以依靠日志文件恢复数据。 它的工作过程大抵分为以下几步&#xff1a; 收到客户端的写入命令(例如SET、DE…