【Linux 网络】:poll/epoll 底层机制与 Reactor 并发模型

一、 I/O 多路转接之 poll

1.1 poll 核心机制与定位

  • 概念解释

  • I/O 多路转接 (I/O Multiplexing):一种允许单线程同时监听多个文件描述符(fd)的技术,当某个或某些 fd 就绪(可读、可写或异常)时,内核通知应用程序进行相应的 I/O 操作,从而避免了线程阻塞在单一的 I/O 调用上。

  • 事件位图 (Event Bitmap):利用整型变量的不同二进制位来表示不同状态或事件的集合。在底层 I/O 中常用于按位进行"与/或"运算,以此来标记和检测对应的事件状态。

  • 笔记

  • 核心作用 :poll 只负责"等",一次可以同时等待多个 fd。当事件就绪后,它会对上层进行事件通知,整体目的与 select 相同。

  • 参数分离设计 :不同于 select 每次调用都需要重置输入参数,poll 巧妙地将输入参数(用户告知内核关心的事件)和输出参数(内核返回给用户的就绪事件)进行了分离。

  • 无上限限制 :poll 等待的 fd 个数没有最大数量限制(成功突破了 select 默认的 1024 个限制)。

  • 忽略无效 fd :如果设置 fd == -1,内核在扫描时会自动忽略,不关心这类 fd 的 events。

1.2 涉及的核心函数

  • 函数名poll
  • 函数原型
c 复制代码
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

功能与参数说明

  • fds:一个指向 pollfd 结构体数组的首地址指针,每一个元素包含了具体的文件描述符及对应的监听事件。

  • nfds:表示 fds 数组的长度(即元素的个数)。

  • timeout:poll 函数的超时时间,单位是毫秒 (ms)。

  • 返回值说明< 0 表示出错;== 0 表示等待超时;> 0 表示由于监听的 fd 就绪而返回的就绪描述符个数。

  • 函数名read

  • 函数原型

c 复制代码
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

功能与参数说明 :从文件描述符 fd 中读取最多 count 字节的数据到 buf 所指向的缓冲区中。返回实际读取的字节数,返回 0 表示到达文件末尾(EOF),返回 -1 表示发生错误。

1.3 pollfd 结构体与事件定义

  • 笔记
  • 结构体定义
c 复制代码
struct pollfd {
    int   fd;         /* file descriptor (文件描述符) */
    short events;     /* requested events (用户告知内核关心的事件) */
    short revents;    /* returned events (内核告知用户已就绪的事件) */
};
  • 事件分类与输入/输出特性对比表
    | 事件宏 | 描述 | 可作为输入 (events) | 可作为输出 (revents) |
    | --- | --- | --- | --- |
    | POLLIN | 数据(包括普通和优先)可读 | 是 | 是 |
    | POLLRDNORM | 普通数据可读 | 是 | 是 |
    | POLLRDBAND | 优先级带数据可读 (Linux 不支持) | 是 | 是 |
    | POLLPRI | 高优先级数据可读 (如 TCP 带外数据) | 是 | 是 |
    | POLLOUT | 数据(包括普通和优先)可写 | 是 | 是 |
    | POLLWRNORM | 普通数据可写 | 是 | 是 |
    | POLLWRBAND | 优先级带数据可写 | 是 | 是 |
    | POLLRDHUP | TCP 连接被对方关闭,或对方关闭写操作 (GNU引入) | 是 | 是 |
    | POLLERR | 错误 | | 是 |
    | POLLHUP | 挂起 (如管道写端关闭,读端将收到此事件) | | 是 |
    | POLLNVAL | 文件描述符没有打开 | | 是 |

1.4 poll 的优缺点对比

  • 笔记
  • 优点
  1. 不再使用 select 的三个独立位图(读、写、异常),而是使用单一的 pollfd 指针/数组统一管理。
  2. "参数-值"分离,输入事件与输出事件不互相覆盖,接口调用比 select 更优雅方便。
  3. 取消了底层固定大小的位图限制,没有最大文件描述符数量的上限。
  • 缺点
  1. 轮询开销 :当监听数目增多时,poll 返回后依然需要遍历轮询整个 pollfd 数组来获取究竟是哪个 fd 就绪,时间复杂度为 O(N)O(N)O(N)。
  2. 内存拷贝开销 :每次调用 poll 仍需要将大量的 pollfd 结构体数据从用户态完整拷贝到内核态。
  3. 性能线性下降:在包含大量客户端连接但某时刻只有极少数连接活跃的场景下,监视数量的增长会导致整体执行效率呈线性下降。

1.5 附录:poll 监控标准输入示例代码

  • 笔记
c 复制代码
#include <poll.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    struct pollfd poll_fd;
    poll_fd.fd = 0; // 0代表标准输入
    poll_fd.events = POLLIN; // 关心可读事件

    for(;;) {
        int ret = poll(&poll_fd, 1, 1000); // 1000ms 超时轮询
        if(ret < 0) { 
            perror("poll"); 
            continue; 
        }
        if(ret == 0) { 
            printf("poll timeout\n"); 
            continue; 
        }

        // 走到这里说明有事件就绪,需要检查 revents
        if(poll_fd.revents == POLLIN) {
            char buf[1024] = {0};
            read(0, buf, sizeof(buf)-1);
            printf("stdin:%s", buf);
        }
    }
}

二、 多路转接 epoll 基础与接口

2.1 epoll 初识

  • 概念解释

  • epoll :为了处理大批量网络句柄而对 poll 进行深度改进的产物,于 Linux Kernel 2.5.44 版本中被引入。它被公认为 Linux 2.6 之后性能最好、最高效的多路 I/O 就绪通知机制,完美解决了 poll/select 随 fd 增多效率急剧下降的致命问题。

  • 笔记

  • 核心定位:基于对多个 fd 进行等待的就绪事件通知机制。

  • 机制演变 :epoll 的实现原理和接口使用方式与 poll 存在巨大差别,它摒弃了"一个函数包打天下"的思路,将其拆分为了三个协同工作的系统调用接口。

2.2 涉及的核心函数与结构体

  • 核心数据结构
c 复制代码
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_PACKED;
  • 函数名epoll_create
  • 函数原型
c 复制代码
#include <sys/epoll.h>
int epoll_create(int size);

功能与参数说明 :创建一个 epoll 模型句柄。size 参数在 Linux 2.6.8 之后已被内核忽略,但为了兼容性,传入的值必须大于 0。注意:epoll 句柄本身也是一个 fd,用完之后必须调用 close() 关闭。

  • 函数名epoll_ctl
  • 函数原型
c 复制代码
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

功能与参数说明 :epoll 的事件注册与管理函数。不同于 select/poll 在监听时才告诉内核,epoll 是"提前注册"。

  • epfdepoll_create 返回的句柄。

  • op:动作控制宏,包含 EPOLL_CTL_ADD (注册新fd)、EPOLL_CTL_MOD (修改已注册的fd事件)、EPOLL_CTL_DEL (从epoll中删除fd,此时 event 设为 NULL)。

  • fd:需要监听的目标文件描述符。

  • event:告诉内核需要监听的具体事件类型。

  • 函数名epoll_wait

  • 函数原型

c 复制代码
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

功能与参数说明:收集监控中已经发生的事件。

  • events:预先分配好的结构体数组,内核会严格从 0 下标开始将就绪事件拷贝赋值进来(内核只负责拷贝,不负责内存分配)。
  • maxevents:数组的容量,不能大于创建时的 size。
  • timeout:超时时间(ms)。0 表示非阻塞立即返回,-1 表示永久阻塞等待。
  • 返回值 :成功时返回已就绪的 fd 数目;0 表示超时;< 0 表示失败。

2.3 epoll 事件核心宏集合

  • 笔记
  • EPOLLIN:对应的 fd 可读(包含对端 Socket 正常关闭)。
  • EPOLLOUT:对应的 fd 可写。
  • EPOLLPRI:有紧急数据可读(例如 TCP 带外数据)。
  • EPOLLERR:对应的 fd 发生错误。
  • EPOLLHUP:对应的 fd 被挂断。
  • EPOLLET:核心参数标志,用于将 epoll 该描述符的工作模式设置为边缘触发 (Edge Triggered)
  • EPOLLONESHOT:设定该 fd 只监听一次事件。事件触发并完成后,若需继续监听,必须手动将其重新加入到 epoll 队列中。

三、 epoll 底层工作原理与核心结构

3.1 内核数据结构:红黑树与双向链表

  • 概念解释

  • 红黑树 (Red-Black Tree) :一种自平衡的二叉查找树,Linux 内核在这里用于高效管理和组织所有被添加进来的、需要监控的 fd 节点。其插入、删除和查找的时间复杂度稳定在 O(log⁡N)O(\log N)O(logN)。

  • 就绪队列 (Ready List) :底层表现为一个双向链表,其内部仅存放当前已经满足 I/O 就绪条件的节点。

  • 笔记

  • 当调用 epoll_create 时,内核会随之创建一个核心的 eventpoll 结构体对象:

c 复制代码
struct eventpoll {
    rwlock_t lock;             // 读写锁:保护结构并发访问,说明 epoll 操作是线程安全的
    struct rb_root rbr;        // 红黑树的根节点:存放所有需要被监控的事件句柄
    struct list_head rdlist;   // 双向链表 (就绪队列):存放满足条件、将要返回给用户的事件
    // ... 其他属性(如 wait queue 等)
};
  • 红黑树的核心作用:用户态告知内核关心的 fd 及其对应事件时,本质上就是将其封装为一个节点插入到这棵红黑树中。使用 fd 作为 Key 进行检索,能够极速识别并防止重复添加同一个 fd。
  • 每当注册一个事件,内核对应生成一个 epitem 结构体维护其信息:
c 复制代码
struct epitem {
    struct rb_node rbn;        // 链接到红黑树的节点挂载点
    struct list_head rdllink;  // 链接到就绪双向链表的节点挂载点
    struct epoll_filefd ffd;   // fd 及其对应 file 的核心映射信息
    struct eventpoll *ep;      // 反向指针:指向归属的 eventpoll 对象容器
    struct epoll_event event;  // 用户期待发生的事件类型
    unsigned int revents;      // 实际发生的就绪事件
};

3.2 回调机制与生产者消费者模型

  • 概念解释

  • 回调机制 (Callback Mechanism) :在 epoll 语境下,是指底层硬件(例如网卡接收到网络数据并触发硬件中断)响应后,自动调用操作系统内核网络栈中提前预设好的钩子函数,由该函数主动将就绪的 fd 节点推入链表,彻底抛弃了由内核主动去循环轮询所有 fd 的低效方式。

  • 笔记

  • 底层硬件关联 :所有通过 epoll_ctl 添加到红黑树的事件节点,都会与底层对应的设备驱动程序建立深度回调关系。

  • 极速激活流水线 :网卡收到数据 →\rightarrow→ 触发硬件中断 →\rightarrow→ 唤醒网络协议栈 →\rightarrow→ 调用特定回调方法 ep_poll_callback →\rightarrow→ 该回调函数精准定位到红黑树中的对应 epitem 节点 →\rightarrow→ 通过其内部的 rdllink 指针直接将其追加到 rdlist (就绪双向链表) 中。

  • O(1)O(1)O(1) 时间复杂度获取 :调用 epoll_wait 时,它的核心动作不再是去红黑树里盲目遍历所有 fd,而是直接以 O(1)O(1)O(1) 的超低复杂度去检查 rdlist 链表是否为空。如果不为空,则以 O(N)O(N)O(N) (此处的 N 是实际已经就绪的个数,而非总数量) 将链表内的事件信息打包拷贝回用户态内存。

  • 模型抽象 :epoll 的就绪队列处理,本质上就是一个基于事件就绪的生产者与消费者模型 。底层网络驱动和硬件中断是"生产者",不断地往链表里压入就绪事件;而用户进程调用 epoll_wait 则是"消费者",从链表中把数据取走。

3.3 发散思维提问:epoll 真的使用了 mmap 零拷贝机制吗?

  • 提问 :网络上盛传 epoll 性能如此之高,是因为它使用了 mmap(内存映射机制)实现了内核态与用户态的共享内存,从而避免了 epoll_wait 阶段的数据拷贝。这种说法是真的吗?
  • 解答这是计算机网络领域的经典谣言! epoll 底层绝对没有 使用 mmap 机制。当调用 epoll_wait 将就绪事件返回给用户时,内核势必还是需要调用类似于 __put_user 等标准的内核到用户态拷贝方法,将 rdlist 中的就绪事件挨个复制到用户预先分配的 events 内存数组中。
    epoll 性能极其优异的真正原因在于:
  1. 通过事件和节点抽离,避免了每次调用时都向内核全量拷贝所有要监听的 fd(只在 EPOLL_CTL_ADD 时拷贝一次)。
  2. 凭借底层网卡中断回调,省去了唤醒后盲目遍历全体 fd 寻找就绪状态的巨大开销。

3.4 epoll 原理澄清与核心优势总结

  • 笔记
  • 缓冲区未处理完的兜底机制 :如果用户层传递给 epoll_wait 的 events 数组容量太小(不够装填当前全部的就绪事件),那些没被拿完的就绪节点默认依然会保留在内核就绪队列 rdlist 。在下一次调用 epoll_wait 时,只要条件允许(如 LT 模式下),它们会继续被返回,确保绝对不会丢失事件。
  • 全量优势总结
  1. 接口灵活方便:创造性地将 create / ctl / wait 拆分为独立三部曲,避免了每次轮询时的全量重置。
  2. 数据拷贝极度轻量:将参数和状态分离,仅在 ADD 动作时将数据传入内核结构中,大幅削减 CPU 总线的数据搬运压力。
  3. 硬核回调,告别盲扫 :不遍历全体集合,高度依赖硬件驱动中断响应和 O(1)O(1)O(1) 回调链表判定,在"连接海量但活跃度低"的环境下,展现出碾压级别的性能。
  4. 无数量天花板:红黑树设计彻底解放了监听数量上限,能轻松应对 C10K 甚至 C100K 级别的高并发挑战。

四、 epoll 核心系统调用与基础机制 (原章节 I 顺延)

4.1 IO 多路转接与 epoll 机制

  • 概念解释

  • IO 多路转接 (IO Multiplexing):一种允许单线程/进程同时监控多个文件描述符(fd)的 IO 事件(如可读、可写)的机制,当某个 fd 就绪时,通知系统进行后续处理,从而提高并发效率。

  • epoll :Linux 下多路转接的高效实现方式,相较于 selectpoll,它通过红黑树管理监听事件,并通过回调机制将就绪事件放入就绪队列,避免了轮询遍历,适合处理海量并发连接。

  • 笔记

  • epoll 的适用场景 :高性能依赖于特定场景。对于多连接,且多连接中只有一部分连接比较活跃时(例如处理上万个客户端的互联网 APP 入口服务器),极度适合。

  • 不适用场景:如果是系统内部服务器之间的通信,只有少数几个活跃的持久连接,使用 epoll 并不合适,反而可能因系统调用开销导致性能适得其反。

4.2 涉及的核心函数

  • 函数名epoll_create
  • 函数原型
cpp 复制代码
#include <sys/epoll.h>
int epoll_create(int size);

功能与参数说明 :创建一个 epoll 实例,返回一个文件描述符(epoll_fd)。size 参数在较新的 Linux 内核中已被忽略,但需大于 0。

  • 函数名epoll_ctl
  • 函数原型
cpp 复制代码
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

功能与参数说明 :用于控制某个 epoll 文件描述符上的事件。epfdepoll_create 返回的句柄;op 为操作类型(如 EPOLL_CTL_ADD 添加、EPOLL_CTL_DEL 删除);fd 为要监听的目标文件描述符;event 为需要监听的具体事件(如 EPOLLIN, EPOLLOUT, EPOLLET)。

  • 函数名epoll_wait
  • 函数原型
cpp 复制代码
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

功能与参数说明 :等待 epoll 实例中的事件就绪。events 数组用于回传就绪的事件;maxevents 为最大回传数量;timeout 为超时时间(-1 为阻塞等待)。注意:遍历处理时,必须严格循环到返回值 nfds 的数量,绝不能多循环


五、 水平触发 (LT) 与 边缘触发 (ET) 深度解析

5.1 工作模式对比与底层逻辑

  • 概念解释

  • 水平触发 (Level Triggered, LT):只要文件描述符的缓冲区中有数据,系统就会一直通知用户进程。名称源自示波器的电平信号持续状态。

  • 边缘触发 (Edge Triggered, ET):只有当底层数据状态发生变化(从无到有,从有到多)时,系统才会触发一次通知。名称源自示波器的边缘跳变。

  • 笔记

  • LT 模式特点 (epoll 默认模式,select/poll 仅支持此模式)

  • 触发机制:只要底层有报文或缓冲区没读完,就会一直通知上层。上层可以不一次性读完,因为下次还会通知。

  • 比喻:亲妈喊你吃饭(喊一次没动,继续喊);快递员张三(包裹没取走,天天打电话)。

  • 兼容性:支持阻塞读写和非阻塞读写。

  • ET 模式特点 (Nginx 默认采用)

  • 触发机制:数据状态变化时仅通知一次。即便上层取走了一部分数据,若后续无新增数据,ET 再也不会通知。

  • 比喻:后妈喊你吃饭(喊一次不管了);快递员李四(效率高,只通知一次,倒逼你必须一次性取完所有包裹)。

  • 强制要求 :用户进程被通知就绪后,必须将缓冲区本轮的数据全部读完 。必须配合非阻塞 IO 使用。

  • 效率对比

  • 效率取决于有效通知的数量。ET 通知效率更高,有效通知数量最少(epoll_wait 返回次数少),减少了内核到用户的拷贝和上下文切换。

  • LT 如果代码写得好,每次就绪也能立刻处理完所有数据,不让就绪被重复提示,其实性能和 ET 是一样的,但 ET 从系统机制上约束/倒逼程序员必须编写高效的读取代码,增加了 IO 读写方式的确定性。

  • 网络传输层面的意义 :ET 迫使尽快读完所有数据,可以使本端接收缓冲区迅速腾出空间,从而在 TCP 协议中给对方更新/提供一个更大的滑动窗口 (Win 大小),提高了网络发送报文的并发度和传输效率。(注:TCP 中涉及 PSH 标志位和底层底水位线机制来触发就绪)。

5.2 涉及的核心函数

  • 函数名recv
  • 函数原型
cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

功能与参数说明 :从已连接的套接字接收数据。当 ET 模式下循环调用时,若返回值为 < 0errnoEAGAINEWOULDBLOCK,说明底层缓冲区已被彻底读空。


六、 ET 模式的工程实践约束与异常处理

6.1 为什么 ET 必须配合非阻塞 IO?

  • 概念解释

  • 非阻塞 IO (Non-blocking IO) :当请求的 IO 操作无法立即完成时,系统调用不会挂起(阻塞)进程,而是立即返回一个错误码(通常是 EAGAIN)。

  • 笔记

  • 死锁困境场景分析

  • 假设服务器接收到一个 10k 的请求,必须读完 10k 才能给客户端返回响应,而客户端必须收到响应才发下一个请求。

  • 服务器 epoll_wait 触发,调用 read 读了 1k 数据。此时缓冲区剩 9k。

  • 因为是 ET 模式,状态不再变化,epoll_wait 无法再次返回。

  • 结果:服务器等客户端发数据才读剩下的,客户端等服务器发响应才发下一个数据 ------ 双端死锁等待。

  • 解决方案 :必须循环轮询调用 read/recv 读取缓冲区,保证完整请求被读出。

  • 为什么不能是阻塞 IO :如果使用阻塞 IO 循环读取,当缓冲区内的数据被完全读完时,最后一次 readrecv 会因为没有数据而被永远阻塞,导致整个执行流卡死。

  • 正确姿势 :**ET + 非阻塞 IO + 无脑循环 recv 直到返回 EAGAIN**

6.2 发散思维问答:读事件与写事件的监听策略有何不同?

  • 提问:在使用 epoll 时,针对文件描述符的读事件(EPOLLIN)和写事件(EPOLLOUT),我们的监听策略是否一样?为什么?
  • 解答:不一样。
  • 读事件 (EPOLLIN) :应该常设。因为我们无法预知对端什么时候发送数据,需要一直保持关心。
  • 写事件 (EPOLLOUT) :必须按需设置 。因为发送缓冲区的写事件默认就是就绪的(只要有空间就可以写)。如果将写事件常设,epoll_wait 就会一直被触发立刻返回,造成 CPU 空转。
  • 正确发送姿势 :需要发送数据时,直接发送。如果发送的数据过大,把发送缓冲区写满了(导致写事件不就绪返回 EAGAIN),此时再把对写事件的关心(EPOLLOUT)设置到 epoll 内部。一旦底层网络将数据发走,缓冲区有空间了,epoll 默认会触发一次写事件就绪,此时再继续发送剩余数据。发送完毕后,立即关闭对 EPOLLOUT 的关心。

七、 Reactor 模式与高并发架构封装

7.1 Reactor 反应堆模式与解耦

  • 概念解释

  • Reactor 模式 (反应堆模式):一种事件驱动的并发模型。它通过一个或多个事件分发器(Dispatcher)监听多个事件源。当事件发生时,分发器将事件分派给注册的处理器(Handler)进行回调处理。

  • One Thread One Loop (OTOL):Reactor 架构的一种最佳实践。即一个线程绑定一个事件循环(Loop),事件的监听、分发和执行都在这个闭环中完成,避免了多线程锁竞争引发的复杂性。

  • 笔记

  • 模块解耦合 :在处理读取到的数据时,不能让所有的逻辑混杂在一起。为了保证读取到的碎片化数据在未来能拼凑完整,我们需要为每一个 fd 定义并维护一个独立的 buffer

  • 连接的封装 (Connection / Channel) :将 fd、相关的 buffer 以及处理事件的回调函数封装为一个 Connection 对象。通过 std::unordered_map 等结构将 fd 与具体的 Connection 绑定(回指),以便 fd 就绪时调用封装好的方法处理业务。

  • ListenSocket 与普通 Socket 的统一

  • 无论是监听套接字(Listen Socket)还是通信套接字(Normal Socket),在 Reactor 中都是一个 Channel。

  • 触发 EPOLLIN 读事件时,两者的区别仅仅在于:ListenSocket 使用的是 accept 接收新连接,而普通 Socket 使用的是 recv/read 读取数据。在更高层的封装下,这两者被完美统一处理。

  • 业务分离:将网络 IO(Reactor 底座)、协议解析(Protocol)和实际业务处理(业务回调)彻底解耦,例如:

cpp 复制代码
std::shared_ptr<Connection> conn = std::make_shared<Connection>(port);
conn->RegisterHandler([&protocol](std::shared_ptr<Channel> channel){
    // TODO: 业务逻辑交给注册的 Handler 处理
});

八、 附录

8.1 惊群问题 (Thundering Herd Problem)

  • 概念解释

  • 惊群问题 :当多个进程或线程同时阻塞在同一个事件上(如多个进程同时 epoll_wait 监听同一个套接字的连接事件),当该事件发生时,所有的进程/线程都会被内核唤醒,但最终只有其中一个能够成功处理该事件(如成功 accept),其他的会收到错误(如 EAGAIN)并重新进入休眠。这种无效的唤醒会造成严重的系统资源消耗和上下文切换开销。

  • 笔记

  • 面试高频考点。在旧版本的 Linux 内核中存在该问题,新版本内核中对于 accept 已经解决,但 epoll 下的多线程共享 fd 仍需通过 EPOLLONESHOTEPOLLEXCLUSIVE 标志等特殊机制在工程层面避免。

相关推荐
晚风吹红霞1 小时前
C++ list 容器完全指南:从入门到手撕双向链表
c++·链表·list
kebidaixu1 小时前
深入解析 Linux GPIO 采集与控制程序(DI/DO 篇)
linux
jiayong231 小时前
CI/CD与DevOps、Jenkins、K8s关系深度解析
运维·git·ci/cd
Cloud_Shy6181 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第二章 Item 10 - 12)
c语言·开发语言·网络·人工智能·windows·python·编辑器
cpp_25011 小时前
P10109 [GESP202312 六级] 工作沟通
数据结构·c++·算法·题解·洛谷·gesp六级
霸道流氓气质1 小时前
导入历史跟踪机制实战指南
java·linux·服务器
Xeon_CC1 小时前
vs2026远程开发debian12容器的C++程序笔记
开发语言·c++·笔记
遇见火星1 小时前
Jenkins + Ansible 集成实战:把配置管理焊进流水线里
运维·ansible·jenkins
做个文艺程序员1 小时前
第03篇:K8s 网络深度解析:Ingress、Service Mesh 与 CoreDNS——Java 微服务通信全链路剖析(生产级实战)
网络·kubernetes·service_mesh