【Linux 36】多路转接 - epoll

文章目录

  • 🌈 一、epoll 初步认识
  • 🌈 二、epoll 相关接口
    • ⭐ 1. 创建 epoll -- epoll_create
    • ⭐ 2. 控制 epoll -- epoll_ctr
    • ⭐ 3. 等待 epoll -- epoll_wait
  • 🌈 三、epoll 工作原理
    • ⭐ 1. 红黑树和就绪队列
    • ⭐ 2. 回调机制
    • ⭐ 3. epoll 的使用过程
  • 🌈 四、epoll 工作模式
    • ⭐ 1. 水平触发 LT
    • ⭐ 2. 边缘触发 ET
    • ⭐ 3. 对比 LT 和 ET
  • 🌈 五、epoll 的特点
    • ⭐ 1. epoll 的优点
    • ⭐ 2. 与 select 和 poll 的区别
  • 🌈 六、epoll 使用示例

🌈 一、epoll 初步认识

  • epoll 系统调用可以让程序同时监视多个文件描述符上的事件是否就绪(多路转接接口都能),与 select 和 poll 的使用场景相同。
  • epoll 在命名上比 poll 多了一个 e(extend),它是为了同时处理大量的文件描述符而产生的 poll 的 plus 版本。
  • epoll 在 Linux 2.5.44 被引入,几乎具备了 select 和 poll 的所有优点,被公认为 Linux 2.6 下性能最好的多路 IO 就绪通知方法。

举个例子

  • epoll 不仅能够告知用户,被监视的文件描述符上有事件就绪。还能够告诉用户是哪些文件描述符上的事件就绪了。
  • 这样就避免了像 select 和 poll 那样的需要遍历文件描述符集,才能知道是哪些文件描述符上的事件就绪的情况。

在这里插入图片描述

🌈 二、epoll 相关接口

  • epoll 有 3 个相关的系统调用接口,分别是 epoll_create、``epoll_ctlepoll_wait`。

⭐ 1. 创建 epoll – epoll_create

#include <sys/epoll.h>int epoll_create(int size);
  • 函数功能:在内核中创建一个 epoll 模型,也能理解为创建一个 epoll 句柄。
  • 函数参数:自 Linux 2.6.8 后,size 参数通常会被忽略,但 size 的值必须得 > 0
  • 函数返回值:创建 epoll 模型成功时,返回对应的文件描述符;否则返回 -1,并设置错误码。
  • 注意事项:当不再使用时,必须调用 close 函数关闭 epoll 模型所对应的文件描述符。

⭐ 2. 控制 epoll – epoll_ctr

  • 该函数用于向指定的 epoll 模型中注册事件,就是用户告诉内核该干什么
#include <sys/epoll.h>int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

1. 参数说明

  • epfd:想要操纵的 epoll 模型(epoll_create 的返回值)。
  • op:对 epoll 模型具体要执行的操作,用 3 个宏来表示,分别如下:
    • EPOLL_CTL_ADD:注册新的文件描述符 fd 到指定的 epoll 模型中。
    • EPOLL_CTL_MOD:修改已经注册的文件描述符 fd 的监听事件。
    • EPOLL_CTL_DEL:从 epoll 模型中删除指定的文件描述符 fd(想从 epoll 中移除 fd 时,这个 fd 必须是健康 & 合法的,否则会移除出错。如果想关闭 fd,则必须先将该 fd 从 epoll 模型中移除,再执行 close)。
  • fd:需要监视的文件描述符。
  • event:需要监视 fd 这个文件描述符上的哪些事件。

2. 返回值说明

  • 调用成功时:返回 0
  • 调用失败时:返回 -1,并设置错误码

3. struct epoll_event 的结构

image-20250113203129457

  • struct epoll_event 中有两个成员:
    1. events:表示需要监视的事件
    2. data:联合体结构,一般使用该结构中的 fd 字段(四选一),表示需要监听的文件描述符。
  • events 字段的常用取值如下:
events 的取值说明
EPOLLIN表示对应的文件描述符可读(包括对端 SOCKET 正常关闭)
EPOLLIN表示对应的文件描述符可写
EPOLLPRI表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR表示对应的文件描述符发送错误
EPOLLHUP表示对应的文件描述符被挂断,即对端将文件描述符关闭了
EPOLLET将 epoll 的工作方式设置为边缘触发 ET(Edge Triggered)模式
EPOLLONESHOT只监听一次事件,当监听完这次事件之后,如果还需要继续监听该文件描述符的话,需要重新将该文件描述符添加到 epoll 模型中

⭐ 3. 等待 epoll – epoll_wait

  • epoll_wait 函数用于收集所监视的事件中所有已经就绪的事件,就是内核告知用户,哪些事件就绪了
#include <sys/epoll.h>int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

1. 参数说明

  • epfd:指定的 epoll 模型。
  • events:内核会将已经就绪的事件拷贝到 events 数组当中。events 不能是空指针,内核只负责将就绪事件拷贝到该数组中,不会帮我们在用户态中分配内存。
  • maxevents:events 数组中的元素个数,该值不能大于在创建 epoll 模型时所传入的 size 参数的值。
  • timeout:表示 epoll_wait 函数的超时时间(单位 ms)。

2. 参数 timeout 的取值

  • -1:调用 epoll_wait 后进行阻塞等待,直到被监视的文件描述符上有事件就绪为止。
  • 0:调用 epoll_wait 后进行非阻塞等待,无论被监视的文件描述符上是否有事件就绪,都会立即返回。
  • 特定的时间值:调用 epoll_wait 后在指定的时间内进行阻塞等待,如果被监视的文件描述符上一直没有事件就绪,则在该时间后 epoll_wait 进行超时返回。

3. 返回值说明

  • 调用函数成功:返回已经就绪了事件的文件描述符的个数。
  • 调用函数失败:返回 -1,并设置错误码;错误码的取值如下:
    • EBADF:传入的 epoll 模型对应的文件描述符无效。
    • EFAULT:events 指向的数组空间无法通过写入权限访问。
    • EINTR:此调用被信号所中断。
    • EINVAL:epfd 不是一个 epoll 模型对应的文件描述符,或传入的maxevents 值 <= 0。
  • timeout 时间耗尽:返回 0

🌈 三、epoll 工作原理

⭐ 1. 红黑树和就绪队列

image-20250113220439292

  • 当某个进程调用了 epoll_create 函数时,Linux 内核会创建一个 eventpoll 结构体,该结构体中有两个成员和 epoll 的使用方式相关。
struct eventpoll
{// ...struct rb_root rbr;			// 红黑树的根节点,这棵树中存储着所有添加到 epoll 中的需要监视的事件struct list_head rdlist;	// 就绪队列中则存放着将要通过 epoll_wait 返回给用户的满足条件的事件// ...
};
  • 每一个 epoll 对象都有一个独立的 eventpoll 结构体,用于存放通过 epoll_ctl 函数 epoll 对象中添加进来的事件。
  • 这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是 lgn,其中 n 为树的高度)。
  • 而所有添加到 epoll 中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法。
  • 这个回调方法在内核中叫 ep_poll_callback,它会将发生的事件添加到 rdlist 双链表中。
  • 在 epoll 中,对于每一个事件,都会建立一个 epitem 结构体。红黑树和就绪队列当中的节点分别是基于 epitem 结构中的 rbn 成员和 rdllink 成员的,epitem 结构当中的成员 ffd 记录的是指定的文件描述符值,event 成员记录的就是该文件描述符对应的事件。
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 不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户,这个操作的时间复杂度是 O(1)。

⭐ 2. 回调机制

  • 所有添加到红黑树当中的事件,都会与设备(网卡)驱动程序建立回调方法,这个回调方法在内核中叫 ep_poll_callback
  • 对于 select 和 poll 来说,操作系统在监视多个文件描述符上的事件是否就绪时,需要让操作系统主动对这多个文件描述符进行轮询检测,这一定会增加操作系统的负担。
  • 而对于 epoll 来说,操作系统不需要主动进行事件的检测,当红黑树中监视的事件就绪时,会自动调用对应的回调方法,将就绪的事件添加到就绪队列当中。
  • 当用户调用 epoll_wait 函数获取就绪事件时,只需要关注底层就绪队列是否为空,如果不为空则将就绪队列当中的就绪事件拷贝给用户即可。
  • 采用回调机制最大的好处,就是不再需要操作系统主动对就绪事件进行检测了,当事件就绪时会自动调用对应的回调函数进行处理。

⭐ 3. epoll 的使用过程

  1. 调用 epoll_create 函数创建一个 epoll 模型。
  2. 调用 epoll_ctl 函数告知内核要监视哪些文件描述符。
  3. 调用 epoll_wait 获取已经有事件就绪的文件描述符。

🌈 四、epoll 工作模式

  • epoll 有两种工作模式,分别是水平触发工作模式边缘触发工作模式

⭐ 1. 水平触发 LT

  • 水平触发(LT,Level Triggered):只要底层有事件就绪,就会一直通知用户
    • 例:就像数电中的高电平触发,只要一直处于高电平,就会一直触发。

image-20250114191036684

LT 是 epoll 的默认工作模式

  • select、poll、epoll 默认采用的都是 LT 工作模式。
  • 在 LT 工作模式中,只要底层有事件就绪就会一直通知用户。因此,当 epoll 检测到底层有读事件就绪时,可以不用立刻进行处理 / 只处理一部分。
  • LT 工作模式支持阻塞读写和非阻塞读写

⭐ 2. 边缘触发 ET

  • 边缘触发(ET,Edge Triggered):只有底层就绪事件的数量 【从无到有】 或 【从有到多】时,epoll 才会通知用户
    • 例:就像数电中的上升沿触发一样,只有当电平由低到高的那一瞬间才会触发。

image-20250115161939067

1. 如何将 epoll 设置成 ET 工作模式

  • 如果想将默认为 LT 工作模式的 epoll 修改为 ET 工作模式,需要在调用 epoll_ctl 函数时,为 struct epoll_event 参数中的 events 字段设置 EPOLLET 选项。
event.data.fd = fd;
event.events |= EPOLLET;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);

2. ET 模式下必须立即处理就绪事件

  • 在 ET 模式下,当 epoll 检测到地城有事件就绪时,必须立即进行处理,且必须全部处理完毕。
  • 如果不一次性处理完所有事件,如果之后没有新的事件就绪,epoll 就不会再通知用户去处理,此时这些还没处理完的数据就丢失了。
  • ET 模型下,epoll 通知用户的次数一般要比 LT 少,因此 ET 的性能一般是比 LT 高的。

3. ET 模式只支持非阻塞的读写

  • 由于用户在 ET 工作模式下必须一次性处理完所有的数据,因此,在读 / 写数据时必须循环调用 recv / send 函数。具体的读写方式如下:
    • 当底层读事件就绪时,循环调用 recv 函数进行读取,直到某次调用 recv 读取时,实际读取到的字节数 < 期望读取到的字节数,说明本次底层数据已经读取完毕了。
    • 但有可能在最后一次调用 recv 读取时,实际读取的字节数刚好 = 期望读取的字节数,但此时底层数据也恰好读取完毕了,如果再调用 recv 函数进行读取,recv 就会因为底层没有数据而阻塞住。
    • 如果写的是单进程的服务器,如果 recv 被阻塞住,并且此后该数据再也不就绪,就相当于服务器挂掉了。因此,在 ET 工作模式下循环调用 recv 函数进行读取时,必须将对应的文件描述符设置为非阻塞状态。
    • 调用 send 函数写数据时也是同样的道理,需要循环调用 send 函数进行数据的写入,并且必须将对应的文件描述符设置为非阻塞状态。
  • 由于 ET 工作模式只支持非阻塞的读写,因此 recv 和 send 所操作的文件描述符必须被设置为非阻塞状态

⭐ 3. 对比 LT 和 ET

1. 举例说明 LT 和 ET 的区别

在这里插入图片描述

  • LT 模式比较 “宽容”,会持续提醒你文件描述符上的情况,直到处理完成。
  • ET 模式更像是 “一次性通知”,只会在状态变化时提醒你,你需要确保自己处理好这个状态变化,否则可能会错过某些数据,因为它不会持续提醒。

2. 对比 LT 和 ET

  • 在 ET 模式下,一个文件描述符就绪之后,用户不会反复收到通知,看起来比 LT 更高效,但如果在 LT 模式下能够做到每次都将就绪的文件描述符立即全部处理,不让操作系统反复通知用户的话,其实 LT 和 ET 的性能也是一样的。
  • ET 模式下可以避免某些情况下的无效通知,因此在实际使用中,ET 模式更常用,尤其是在高并发场景下。
  • ET 的代码复杂度(编程难度)比 LT 更高。
  • 由于 ET 模式强制用户层必须尽快 & 一次性将数据读取完,在 TCP 网络层面看,就能够给对端主机通告一个更大的接收窗口,从而提升 IO 效率。
  • ET 和 LT 的根本区别:ET 和 LT 都可以设置成非阻塞的循环读写数据,但是 ET 有强制性要求,倒逼着程序员必须非阻塞的循环读取数据,从而保证能够提高 ET 的通知效率,以及 IO 效率。

3. 如何选择 LT 还是 ET

  • 选择 ET 的场景:在某些需要更高的通知效率或 IO 效率的场景可以选择 ET。
  • 选择 LT 的场景:在报文过长时,可能会被拆分成多个小报文发送。LT 可以更好的保证在这种情况下收到报文的有序性。

🌈 五、epoll 的特点

⭐ 1. epoll 的优点

  • 接口使用方便:虽然拆分成了三个函数,但是反而使用起来更方便高效。不需要每次循环都设置要关注的文件描述符,也做到了将输入输出参数分离。
  • 数据拷贝轻量:只在新增监视事件的时候调用 epoll_ctl 将数据从用户拷贝到内核,而 selectpoll 每次都需要重新将需要监视的事件从用户拷贝到内核。此外,调用 epoll_wait 获取就绪事件时,只会拷贝就绪的事件,不会进行不必要的拷贝操作。
  • 事件回调机制:避免操作系统主动轮询检测事件就绪,而是采用回调函数的方式,将就绪的文件描述符结构加入到就绪队列中。调用 epoll_wait 时直接访问就绪队列就知道哪些文件描述符已经就绪,检测是否有文件描述符就绪的时间复杂度是 O(1),因为本质只需要判断就绪队列是否为空即可。
  • 没有数量限制:可监视的文件描述符的数量没有上限。只要内存足够,就能一直往红黑树中添加结点。

⭐ 2. 与 select 和 poll 的区别

  • 在使用 selectpoll 时,都需要借助数组来维护文件描述符以及需要监视的事件.这个数组是由用户自己维护的,对该数组的增删改操作都需要用户自己来进行。
  • 而使用 epoll 时,不需要用户自己维护这个数组,epoll 底层的红黑树就充当了这个第三方数组的功能。并且该红黑树的增删改操作都是由内核维护的,用户只需要调用 epoll_ctl 让内核对该红黑树进行对应的操作即可。
  • 在使用多路转接接口时,数据流都有两个方向,分别是用户告知内核内核告知用户selectpoll 将这两件事情都交给了同一个函数来完成,而 epoll 在接口层面上就将这两件事进行了分离。epoll 通过调用 epoll_ctl 完成用户告知内核,通过调用 epoll_wait 完成内核告知用户。

🌈 六、epoll 使用示例

#pragma once#include <iostream>
#include <sys/epoll.h>#include "socket.h"using std::make_unique;
using std::unique_ptr;using namespace socket_ns;class epoll_server
{const static int size = 1024;const static int maxevents = 1024; // 就绪事件的个数private:uint16_t _port;unique_ptr<Socket> _listen_socket;int _epfd;struct epoll_event ready_events[maxevents]; // 存储所有已经就绪的事件public:epoll_server(uint16_t port): _port(port), _listen_socket(make_unique<tcp_socket>()){_listen_socket->build_listen_socket(_port);// 创建 epoll 实例,由于整个服务器都是基于 epoll 运行的,如果 epoll 创建失败那就不要继续_epfd = ::epoll_create(size);if (_epfd < 0){LOG(ERROR, "epoll_create error");exit(1);}LOG(INFO, "epoll_create success, epfd: %d", _epfd);}void init_server(){struct epoll_event ev;ev.events = EPOLLIN;                   // 让 epoll 监听读事件ev.data.fd = _listen_socket->sockfd(); // 关心 listen 套接字上的事件// 将 listen 套接字添加到 epoll 模型中int n = ::epoll_ctl(_epfd, EPOLL_CTL_ADD, _listen_socket->sockfd(), &ev);if (n < 0){LOG(ERROR, "epoll_ctl error");exit(2);}LOG(INFO, "epoll_ctl success, epfd: %d, sockfd: %d", _epfd, _listen_socket->sockfd());}void loop(){int timeout = 1000;while (true){// 等待事件就绪(获取就绪事件)int n = ::epoll_wait(_epfd, ready_events, maxevents, timeout);switch (n){case 0:LOG(INFO, "timeout");break;case -1:LOG(ERROR, "epoll_wait error");break;default:LOG(INFO, "epoll_wait success, haved event happend, ready event num: %d", n);handle_event(n);break;}}}// 将事件转换为字符串string events_to_string(uint16_t events){string events_str;if (events & EPOLLIN)events_str += "EPOLLIN ";if (events & EPOLLOUT)events_str += "EPOLLOUT ";if (events & EPOLLPRI)events_str += "EPOLLPRI ";if (events & EPOLLERR)events_str += "EPOLLERR ";if (events & EPOLLHUP)events_str += "EPOLLHUP ";return events_str;}// 处理连接事件void accepter_connection(){Inet_addr addr;int sockfd = _listen_socket->accepter(&addr);if (sockfd > 0){LOG(INFO, "get a new link success, client info: %s:%d", addr.ip().c_str(), addr.port());// 将新的 fd 交给 epoll 管理struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = sockfd;int n = ::epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);if (n < 0){LOG(ERROR, "epoll_ctl error");::close(sockfd);}else{LOG(INFO, "epoll_ctl success, epfd: %d, sockfd: %d", _epfd, sockfd);}}else if (sockfd < 0){LOG(ERROR, "accepter error");return;}}// 处理普通的读事件void handle_normal_read_event(int i){int fd = ready_events[i].data.fd;char buffer[1024];ssize_t n = ::recv(fd, buffer, sizeof(buffer) - 1, 0);if (n > 0){buffer[n] = '\0';cout << "client say: " << buffer << endl;// 服务器给客户端回复string content = "<html><body><h1>hello world</h1></body></html>";string echo_str = "HTTP/1.0 200 OK\r\n";echo_str += "Content-Type: text/html\r\n";echo_str += "Content-Length: " + to_string(content.size()) + "\r\n\r\n";echo_str += content;// 发送数据::send(fd, echo_str.c_str(), echo_str.size(), 0);}else if (0 == n){// 对端关闭连接LOG(INFO, "client quit, fd: %d", fd);// 将 fd 从 epoll 模型中删除//  想从 epoll 中移除 fd 时,这个 fd 必须是健康 & 合法的,否则会移除出错::epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);::close(fd);}else{// 读取失败LOG(ERROR, "recv error, fd: %d", fd);// 将 fd 从 epoll 模型中删除::epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);::close(fd);}}// 处理已经就绪的 n 个事件void handle_event(int n){for (size_t i = 0; i < n; i++){int fd = ready_events[i].data.fd;uint16_t revents = ready_events[i].events;LOG(INFO, "已经有事件就绪了,具体事件是:  %s", events_to_string(revents).c_str());// 处理就绪的读事件if (revents & EPOLLIN){if (fd == _listen_socket->sockfd()){    // 如果是 listen 套接字就绪,说明有新的连接到来accepter_connection();}else{// 普通的读事件就绪handle_normal_read_event(i);}}}}~epoll_server(){if (_epfd >= 0)close(_epfd);if (_listen_socket)close(_listen_socket->sockfd());}
};// 如果是 listen 套接字就绪,说明有新的连接到来accepter_connection();}else{// 普通的读事件就绪handle_normal_read_event(i);}}}}~epoll_server(){if (_epfd >= 0)close(_epfd);if (_listen_socket)close(_listen_socket->sockfd());}
};

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

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

相关文章

微信小程序订阅消息提醒-云函数

微信小程序消息订阅分2种&#xff1a; 1.一次性订阅&#xff1a;用户订阅一次就可以推送一次&#xff0c;如果需要多次提醒需要多次订阅。 2.长期订阅&#xff1a;只有公共服务领域&#xff0c;如政务、医疗、交通、金融和教育等。‌在用户订阅后&#xff0c;在很长一段时间内…

使用 Charles 调试 Flutter 应用中的 Dio 网络请求

为了成功使用 Charles 抓取并调试 Flutter 应用程序通过 Dio 发起的网络请求&#xff0c;需遵循特定配置步骤来确保应用程序能够识别 Charles 的 SSL 证书&#xff0c;并正确设置代理服务器。 配置 Charles 以支持 HTTPS 请求捕获 Charles 默认会拦截 HTTP 流量&#xff1b;…

《HTML在网络安全中的多面应用:从防范攻击到安全审查》

Html基础 Html简介 HTML&#xff08;HyperText Markup Language&#xff0c;超文本标记语言&#xff09;是用于描述网页内容和结构的标准语言。以下是对HTML的简要介绍&#xff1a; 基本概念 定义&#xff1a; HTML不是一种编程语言&#xff0c;而是一种标记语言。 它使用标…

算法每日双题精讲 —— 二分查找(二分查找,在排序数组中查找元素的第一个和最后一个位置)

&#x1f31f;快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 &#x1f31f; 别再犹豫了&#xff01;快来订阅我们的算法每日双题精讲专栏&#xff0c;一起踏上算法学习的精彩之旅吧&#xff01;&#x1f4aa…

《C++11》深入剖析正则表达式库:解锁文本处理的高效之道

在现代编程领域&#xff0c;文本处理是一项不可或缺的任务&#xff0c;而正则表达式无疑是这一领域的强大利器。C11标准库的引入&#xff0c;为C开发者带来了正则表达式库&#xff0c;极大地丰富了C在文本处理方面的能力。本文将全方位、多角度地深入探讨C11正则表达式库&#…

Cosmos:英伟达发布世界基础模型,为机器人及自动驾驶开发加速!

1. 简介 在2025年消费电子展&#xff08;CES&#xff09;上&#xff0c;NVIDIA发布了全新的Cosmos平台&#xff0c;旨在加速物理人工智能&#xff08;AI&#xff09;系统的开发&#xff0c;尤其是自主驾驶车辆和机器人。该平台集成了生成式世界基础模型&#xff08;WFM&#x…

Hive集群的安装准备

Hive的安装与集群部署详细指南 一、环境与软件准备 在开始Hive的安装与集群部署之前&#xff0c;确保您准备好以下环境和软件&#xff1a; 虚拟机软件&#xff1a; VMware Workstation 17.5&#xff1a;用于创建和管理虚拟机&#xff0c;确保可以在其上安装Linux操作系统。 …

SpringBoot集成Mongodb

SpringBoot集成Mongodb 本文简要介绍SpringBoot集成mongodb&#xff0c;并实现增删改查 1. 引入依赖 spring-boot-starter-data-mongodb 提供了mongoTemplate供底层操作及mongodb驱动等 <dependency><groupId>org.springframework.boot</groupId><arti…

java根据模板导出word,并在word中插入echarts相关统计图片以及表格

引入依赖创建word模板创建ftl模板文件保存的ftl可能会出现占位符分割的问题&#xff0c;需要处理将ftl文件中的图片的Base64删除&#xff0c;并使用占位符代替插入表格&#xff0c;并指定表格的位置在图片下方 Echarts转图片根据模板生成word文档DocUtil导出word文档 生成的wor…

RabbitMQ的工作模式

&#xff08;一&#xff09;工作模式 RabbitMQ有7种工作模式来进行消息传递&#xff0c;我们上一篇博客就是简单模式 1.简单模式&#xff08;simple&#xff09; 也就是点对点的形式 P就是生产者&#xff0c;C就是消费者&#xff0c;Queue就是消息队列&#xff08;生产者向qu…

晨辉面试抽签和评分管理系统之十:如何搭建自己的数据库服务器,使用本软件的网络版

晨辉面试抽签和评分管理系统&#xff08;下载地址:www.chenhuisoft.cn&#xff09;是公务员招录面试、教师资格考试面试、企业招录面试等各类面试通用的考生编排、考生入场抽签、候考室倒计时管理、面试考官抽签、面试评分记录和成绩核算的面试全流程信息化管理软件。提供了考生…

迅为RK3568开发板篇OpenHarmony配置HDF驱动控制LED-新增 topeet子系统-编写 bundle.json文件

bundle.json 文件内容如下所示&#xff1a; 下面是对各个字段的解释&#xff1a; 1. name: "ohos/demos" - 这是组件或项目的名称&#xff0c;这里表示它属于 OHOS&#xff08;OpenHarmony OS&#xff09;生态系统下的一个名为"demos"的组件。 2. descri…

JavaScript-正则表达式方法(RegExp)

RegExp 对象用于将文本与一个模式匹配。 有两种方法可以创建一个 RegExp 对象&#xff1a;一种是字面量&#xff0c;另一种是构造函数。 字面量由斜杠 (/) 包围而不是引号包围。 构造函数的字符串参数由引号而不是斜杠包围。 new RegExp(pattern[, flags])一.符集合 1.选择…

信凯科技业绩波动明显:毛利率远弱行业,资产负债率偏高

《港湾商业观察》施子夫 1月8日&#xff0c;深交所官网显示&#xff0c;浙江信凯科技集团股份有限公司&#xff08;以下简称“信凯科技”&#xff09;主板IPO提交注册。 自2022年递交上市申请&#xff0c;信凯科技的IPO之路已走过两年光景&#xff0c;尽管提交注册&#xff0…

Windows远程桌面网关出现重大漏洞

微软披露了其Windows远程桌面网关&#xff08;RD Gateway&#xff09;中的一个重大漏洞&#xff0c;该漏洞可能允许攻击者利用竞争条件&#xff0c;导致拒绝服务&#xff08;DoS&#xff09;攻击。该漏洞被标识为CVE-2025-21225&#xff0c;已在2025年1月的补丁星期二更新中得到…

4G DTU赋能智能配电环网柜通信运维管理

在智能电网建设持续推进下&#xff0c;智能配电环网柜作为配电网的关键节点设备&#xff0c;其稳定、高效运行对保障电力可靠供应是品质生活的基本保障。通信系统是实现智能配电环网柜远程监控与管理的核心纽带&#xff0c;而4G DTU&#xff08;数据传输单元&#xff09;凭借其…

STC的51单片机LED点灯基于KEIL

前言&#xff1a; 该文源于回答一个朋友的问题&#xff0c;代码为该朋友上传&#xff0c;略作修改&#xff0c;在此说明问题以及解决问题的思路&#xff0c;以减少新手错误。 电路图&#xff1a; 该位朋友未上传电路图&#xff0c;说明如下&#xff1a; stc8g1k08a-sop8控制…

C++ 文字识别OCR

一.引言 文字识别&#xff0c;也称为光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;&#xff0c;是一种将不同形式的文档&#xff08;如扫描的纸质文档、PDF文件或数字相机拍摄的图片&#xff09;中的文字转换成可编辑和可搜索的数据的技术。随着技…

基于springboot的自习室预订系统

作者&#xff1a;学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等 文末获取“源码数据库万字文档PPT”&#xff0c;支持远程部署调试、运行安装。 项目包含&#xff1a; 完整源码数据库功能演示视频万字文档PPT 项目编码&#xff1…

TCP 连接状态标识 | SYN, FIN, ACK, PSH, RST, URG

注&#xff1a;本文为“TCP 连接状态标识”相关文章合辑。 TCP 的状态&#xff1a;SYN, FIN, ACK, PSH, RST, URG 简介及 ACK 确认机制 llzhang_fly 于 2020-09-19 05:25:26 发布 1、TCP 的状态 FLAGS 字段状态 在 TCP 层&#xff0c;有个 FLAGS 字段&#xff0c;这个字段有…