【Linux网络】多路转接poll

🎬 个人主页艾莉丝努力练剑
专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬 艾莉丝的简介:


文章目录

  • 前言
  • [1 ~> poll 整体定位与设计背景](#1 ~> poll 整体定位与设计背景)
    • [1.1 IO 多路转接的核心思想](#1.1 IO 多路转接的核心思想)
    • [1.2 select 的固有缺陷(poll 诞生的核心原因)](#1.2 select 的固有缺陷(poll 诞生的核心原因))
    • [1.3 poll 相比于select的核心优化方向](#1.3 poll 相比于select的核心优化方向)
  • [2 ~> poll 核心 API 与参数详解](#2 ~> poll 核心 API 与参数详解)
    • [2.1 函数原型与依赖头文件](#2.1 函数原型与依赖头文件)
      • [2.1.1 基础函数声明](#2.1.1 基础函数声明)
      • [2.1.2 返回值定义](#2.1.2 返回值定义)
    • [2.2 三大参数完整解析](#2.2 三大参数完整解析)
      • [2.2.1 参数一:`struct pollfd *fds`](#2.2.1 参数一:struct pollfd *fds)
      • [2.2.2 参数二:`nfds_t nfds`](#2.2.2 参数二:nfds_t nfds)
      • [2.2.3 参数三:`int timeout`](#2.2.3 参数三:int timeout)
  • [3 ~> 核心结构体 pollfd 与事件宏](#3 ~> 核心结构体 pollfd 与事件宏)
    • [3.1 struct pollfd 结构体定义](#3.1 struct pollfd 结构体定义)
      • [3.1.1 字段细则](#3.1.1 字段细则)
    • [3.2 标准事件宏(比特位标识)](#3.2 标准事件宏(比特位标识))
      • [3.2.1 可读类事件(可作为入参、出参 -- 输入输出类型参数)](#3.2.1 可读类事件(可作为入参、出参 -- 输入输出类型参数))
      • [3.2.2 可写类事件(可作为入参、出参 -- 输入输出类型参数)](#3.2.2 可写类事件(可作为入参、出参 -- 输入输出类型参数))
      • [3.2.3 异常类事件(仅内核回填,不可作为入参)](#3.2.3 异常类事件(仅内核回填,不可作为入参))
      • [3.2.4 扩展事件](#3.2.4 扩展事件)
      • [3.2.5 常用组合规则](#3.2.5 常用组合规则)
  • [4 ~> 基于 select 改造 Poll 服务端(C++ 代码实战)](#4 ~> 基于 select 改造 Poll 服务端(C++ 代码实战))
    • [4.1 公共依赖说明](#4.1 公共依赖说明)
    • [4.2 PollServer 核心类实现(PollServer.hpp)](#4.2 PollServer 核心类实现(PollServer.hpp))
    • [4.3 主函数入口(Main.cc(Main.cc))](#4.3 主函数入口(Main.cc))
    • [4.4 编译脚本(Makefile)](#4.4 编译脚本(Makefile))
    • [4.5 代码逻辑与运行说明](#4.5 代码逻辑与运行说明)
      • [4.5.1 整体执行流程](#4.5.1 整体执行流程)
      • [4.5.2 不同 timeout 运行效果](#4.5.2 不同 timeout 运行效果)
      • [4.5.3 边界场景处理](#4.5.3 边界场景处理)
    • [4.6 优化扩展方向](#4.6 优化扩展方向)
  • [5 ~> poll 与 select 深度对比 & 技术优劣分析](#5 ~> poll 与 select 深度对比 & 技术优劣分析)
    • [5.1 poll 相对 select 的核心优势](#5.1 poll 相对 select 的核心优势)
    • [5.2 poll 遗留的共性问题(与 select 一致)](#5.2 poll 遗留的共性问题(与 select 一致))
  • [6 ~> 收尾总结:重难点、面试考点](#6 ~> 收尾总结:重难点、面试考点)
    • [6.1 核心概念考点](#6.1 核心概念考点)
    • [6.2 代码实操考点](#6.2 代码实操考点)
    • [6.3 面试 & 原理高频考点](#6.3 面试 & 原理高频考点)
    • [6.4 学习延伸方向](#6.4 学习延伸方向)
  • [7 ~> 多路转接Poll相关知识图谱](#7 ~> 多路转接Poll相关知识图谱)
  • 结尾


前言

一、 开头部分(框架引入)

导入语

在 Linux 网络编程中,IO 多路转接是实现单线程高并发网络服务 的核心技术,select 是最早落地的多路转接方案,但存在文件描述符上限、参数频繁重置、输入输出参数耦合等设计缺陷。poll 作为 select 的迭代版本,针对性优化了上述短板,同时沿用了事件等待、就绪派发的核心思想,是介于 select 与高性能 epoll 之间的经典过渡方案。本文将从设计背景、函数原型、核心结构体、事件宏、代码实战、运行特性、优劣对比等维度,完整梳理 poll 的全套知识点,同时结合可运行 C++ 服务端代码完成落地验证,形成闭环的知识体系。

文章框架思维导图

bash 复制代码
poll 多路转接 知识总览
├─ 基础认知
│  ├─ 定位:IO多路转接方案、就绪事件派发机制
│  └─ 诞生原因:解决 select 三大缺陷
├─ 核心API
│  ├─ 函数原型 & 头文件
│  ├─ 参数详解:fds / nfds / timeout
│  └─ 返回值含义
├─ 核心结构体 pollfd
│  ├─ fd:待监听文件描述符(-1表示忽略)
│  ├─ events:用户设置 → 内核监听事件(入参)
│  └─ revents:内核回填 → 实际发生事件(出参)
├─ 事件宏定义(比特位标识)
│  ├─ 可读类事件:POLLIN / POLLRDNORM / POLLPRI 等
│  ├─ 可写类事件:POLLOUT / POLLWRNORM 等
│  └─ 异常类事件:POLLERR / POLLHUP / POLLNVAL(仅出参)
├─ 超时 timeout 三种工作模式
│  ├─ timeout < 0:永久阻塞
│  ├─ timeout = 0:纯非阻塞轮询
│  └─ timeout > 0:限时阻塞 + 超时轮询
├─ 代码实战:基于 select 改造 Poll 服务端
│  ├─ 整体架构:服务类封装、监听套接字初始化
│  ├─ 核心逻辑:事件循环、新连接接收、IO读写处理
│  ├─ 边界处理:客户端断开、服务满载、异常捕获
│  └─ 编译配置 & 运行测试
├─ poll 与 select 对比
│  ├─ 优化点:无fd上限、参数解耦、无需每次重置
│  └─ 遗留问题:仍需全量遍历fd数组、内核拷贝开销依旧存在
└─ 重难点总结 & 考点梳理

1 ~> poll 整体定位与设计背景

1.1 IO 多路转接的核心思想

IO 多路转接的本质是由内核代为监听多个文件描述符的 IO 事件,当任意一个描述符就绪(可读、可写、异常)时,主动通知应用程序,应用程序再执行对应的读写逻辑。该模型可以使用单线程管理大量文件描述符,避免多线程 / 多进程带来的资源开销与线程安全问题,是高并发网络服务的基础模型。

pollselect 属于同类型的多路转接实现,核心工作逻辑完全一致:阻塞等待事件就绪 → 遍历就绪描述符 → 执行业务逻辑

1.2 select 的固有缺陷(poll 诞生的核心原因)

select 在实际使用中暴露了三处关键短板,这也是 poll 被设计出来的核心动机:

  1. 文件描述符存在硬上限select 使用 fd_set 位图结构存储文件描述符,位图长度固定,系统默认限制了最大可监听的 fd 数量,无法动态扩容。
  2. 输入输出参数耦合fd_set 同时作为入参(告诉内核需要监听哪些 fd)和出参(内核回填就绪的 fd),每次调用 select 后位图都会被内核修改,应用程序必须每次循环都重新重置监听集合,增加代码冗余与运行开销。
  3. 调用后需全量遍历 :无论有多少 fd 就绪,应用程序都必须遍历整个 fd 集合判断就绪状态,该问题 poll 并未解决,直至 epoll 才完成优化。

1.3 poll 相比于select的核心优化方向

poll 针对性修复了 select 的前两大缺陷,同时保留了多路转接的基础逻辑:

  1. 摒弃固定长度位图,使用动态数组管理文件描述符,理论上无 fd 数量硬上限(仅受系统内存限制)。
  2. 监听事件就绪事件拆分为两个独立字段,实现入参、出参解耦,无需每次调用后重置监听集合。

2 ~> poll 核心 API 与参数详解

2.1 函数原型与依赖头文件

2.1.1 基础函数声明

c 复制代码
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • 头文件:<poll.h>,属于标准 C 库(libc),编译时无需额外链接库。
  • 扩展函数 ppoll:功能与 poll 一致,增加信号屏蔽字支持,日常网络编程优先使用基础 poll

2.1.2 返回值定义

poll 的返回值为整型,三种结果对应不同场景:

  1. 返回值 > 0 :代表有 N 个文件描述符产生就绪事件,N 为就绪 fd 的总数。
  2. 返回值 = 0:代表监听超时,没有任何 fd 事件就绪。
  3. 返回值 = -1 :代表函数调用出错,可通过 errno 获取具体错误信息。

2.2 三大参数完整解析

2.2.1 参数一:struct pollfd *fds

指向 pollfd 结构体数组的首地址,数组中每一个元素对应一个待监听的文件描述符及事件配置,是 poll 实现多 fd 监听的核心载体。数组可动态申请内存,因此突破了 select 的 fd 数量限制。

2.2.2 参数二:nfds_t nfds

nfds_t 是系统自定义无符号整型,代表 **fds 数组的有效元素个数 **,即当前需要内核监听的文件描述符总数。

2.2.3 参数三:int timeout

超时时间,单位为毫秒 (ms),区分三种工作模式,是控制阻塞行为的核心参数:

  1. timeout < 0(常用值 -1):永久阻塞。内核持续监听事件,直到至少一个 fd 就绪才返回。
  2. timeout = 0:非阻塞轮询。内核仅做一次事件检测,无论是否有就绪事件都立即返回,配合循环可实现轮询逻辑。
  3. timeout > 0:限时阻塞。内核阻塞等待指定毫秒数,期间有事件就绪则立即返回;超时无事件则返回 0。

3 ~> 核心结构体 pollfd 与事件宏

3.1 struct pollfd 结构体定义

poll 通过结构体分离监听事件就绪事件 ,彻底解决 select 参数耦合问题,结构体定义如下:

c 复制代码
struct pollfd
{
    int fd;         // 待监听的文件描述符
    short events;   // 入参:用户设置,需要内核监听的事件(比特位)
    short revents;  // 出参:内核回填,当前fd实际触发的事件(比特位)
};

3.1.1 字段细则

  1. fd
    1. 有效值:合法的文件描述符(套接字、管道、文件等)。
    2. 特殊值 -1:内核会直接忽略该数组元素,不做任何监听,常用于标记数组中闲置位置。
  2. events 由应用程序主动赋值,使用比特位 组合多个监听事件,调用 poll 时传递给内核。调用后该字段不会被内核修改,无需重复赋值。
  3. revents 初始置 0,poll 调用结束后由内核填充当前 fd 实际发生的事件。该字段仅作为结果输出,应用程序读取后自行处理。

3.2 标准事件宏(比特位标识)

所有事件宏均为短整型比特位,位与运算 & 是判断事件是否触发的标准方式。按照功能分为可读事件、可写事件、异常事件三大类,同时区分入参 / 出参权限。

3.2.1 可读类事件(可作为入参、出参 -- 输入输出类型参数)

宏定义 含义说明
POLLIN 普通数据、优先数据可读,最常用读事件
POLLRDNORM 普通数据可读,属于 POLLIN 的细分
POLLRDBAND 优先级带数据可读,Linux 系统不支持
POLLPRI 高优先级数据可读,典型场景:TCP 带外数据

3.2.2 可写类事件(可作为入参、出参 -- 输入输出类型参数)

宏定义 含义说明
POLLOUT 普通数据、优先数据可写,最常用写事件
POLLWRNORM 普通数据可写,属于 POLLOUT 的细分
POLLWRBAND 优先级带数据可写,Linux 极少使用

3.2.3 异常类事件(仅内核回填,不可作为入参)

这类事件由内核主动检测,应用程序无法主动监听,仅会出现在 revents 字段中:

  1. POLLERR:文件描述符发生错误。
  2. POLLHUP:挂起事件,典型场景:管道写端关闭、TCP 连接对方关闭读写端。
  3. POLLNVAL:无效文件描述符,fd 未正常打开。

3.2.4 扩展事件

POLLRDHUP:GNU 扩展事件,标识 TCP 对方关闭连接或关闭写端,常用于优雅断开连接检测。

3.2.5 常用组合规则

网络编程中三大核心事件POLLIN(读就绪)、POLLOUT(写就绪)、POLLERR(异常),基本覆盖绝大多数服务端场景。判断事件格式统一为:if (fds[i].revents & 事件宏)


4 ~> 基于 select 改造 Poll 服务端(C++ 代码实战)

本节基于传统 select TCP 服务端代码改造为 poll 版本,完整实现监听连接、接收客户端、读写数据、断开清理全流程,代码适配 C++17 标准,附带完整注释与边界处理。

4.1 公共依赖说明

项目依赖基础组件:Socket.hpp(TCP 套接字封装)、Logger.hpp(日志工具)、Mutex.hpp(互斥锁)、InetAddr.hpp(网络地址封装),下文仅展示 PollServer 核心逻辑、主函数与编译脚本。

4.2 PollServer 核心类实现(PollServer.hpp)

cpp 复制代码
#pragma once
#include <iostream>
#include <memory>
#include <poll.h>
#include "Logger.hpp"
#include "Socket.hpp"
#include "InetAddr.hpp"

// 全局常量定义
const int NUM = 1024;        // pollfd 数组最大容量
const int gdefaultfd = -1;   // 标记闲置fd的默认值

// Poll 多路转接 TCP 服务端类
class PollServer
{
public:
    // 构造函数:初始化监听套接字、pollfd数组
    PollServer(uint16_t port)
        : _port(port),
          _listensockfd(std::make_unique<TcpSocket>())
    {
        // 1. 创建、绑定、监听套接字
        _listensockfd->BuildSocket(port);

        // 2. 初始化整个pollfd数组,全部置为闲置状态
        for (int i = 0; i < NUM; ++i)
        {
            _fdevent[i].fd = gdefaultfd;
            _fdevent[i].events = 0;
            _fdevent[i].revents = 0;
        }

        // 3. 将监听套接字加入poll监听,监听读事件(新连接)
        _fdevent[0].fd = _listensockfd->Socketfd();
        _fdevent[0].events |= POLLIN;
    }

    // 事件派发主循环
    void Dispatcher()
    {
        while (true)
        {
            PrintFds(); // 打印当前监听的fd列表,用于调试
            // 超时配置:0=非阻塞轮询,2000=2秒限时阻塞,-1=永久阻塞
            int timeout = 0;
            int ret = poll(_fdevent, NUM, timeout);

            switch (ret)
            {
                case 0:
                    // 超时:无事件就绪
                    LOG(LogLevel::DEBUG) << "poll timeout...";
                    break;
                case -1:
                    // 调用出错
                    LOG(LogLevel::ERROR) << "poll call error!";
                    break;
                default:
                    // 存在就绪事件,执行事件处理
                    LOG(LogLevel::DEBUG) << "event ready, count: " << ret;
                    EventHandler();
                    break;
            }
        }
    }

private:
    // 打印当前所有被监听的文件描述符
    void PrintFds()
    {
        std::cout << "poll Server fds list: ";
        for (int i = 0; i < NUM; ++i)
        {
            if (_fdevent[i].fd == gdefaultfd)
                continue;
            std::cout << _fdevent[i].fd << " ";
        }
        std::cout << std::endl;
    }

    // 事件统一处理器:区分新连接、客户端读写
    void EventHandler()
    {
        for (int i = 0; i < NUM; ++i)
        {
            // 跳过闲置fd
            if (_fdevent[i].fd == gdefaultfd)
                continue;

            // 判断读事件就绪
            if (_fdevent[i].revents & POLLIN)
            {
                // 场景1:监听套接字就绪 → 新连接到来
                if (_fdevent[i].fd == _listensockfd->Socketfd())
                {
                    Accepter();
                }
                // 场景2:普通客户端套接字就绪 → 读取客户端数据
                else
                {
                    IOHandler(i);
                }
            }
        }
    }

    // 接收新客户端连接
    void Accepter()
    {
        InetAddr clientaddr;
        // 接受连接:poll已检测就绪,此处不会阻塞
        int new_fd = _listensockfd->Accepter(&clientaddr);
        if (new_fd < 0)
        {
            LOG(LogLevel::ERROR) << "accept new client failed!";
            return;
        }
        LOG(LogLevel::INFO) << "get new client link, fd: " << new_fd;

        // 寻找pollfd数组中的闲置位置,托管新fd
        int pos = 0;
        for (; pos < NUM; ++pos)
        {
            if (_fdevent[pos].fd == gdefaultfd)
                break;
        }

        // 数组已满,服务无法承载新连接
        if (pos >= NUM)
        {
            LOG(LogLevel::WARNING) << "server full, close client fd: " << new_fd;
            close(new_fd);
            return;
        }

        // 将新客户端fd加入poll监听,监听读事件
        _fdevent[pos].fd = new_fd;
        _fdevent[pos].events |= POLLIN;
        _fdevent[pos].revents = 0;
    }

    // 客户端IO读写处理
    void IOHandler(int idx)
    {
        int fd = _fdevent[idx].fd;
        char buffer[1024] = {0};
        // 读取数据:读事件已就绪,不会阻塞
        ssize_t n = recv(fd, buffer, sizeof(buffer) - 1, 0);

        if (n > 0)
        {
            // 读取到有效数据,回显数据
            buffer[n] = '\0';
            std::cout << "client message: " << buffer << std::endl;
            std::string echo_msg = "echo: " + std::string(buffer);
            send(fd, echo_msg.c_str(), echo_msg.size(), 0);
        }
        else if (n == 0)
        {
            // n=0:客户端正常关闭连接
            LOG(LogLevel::INFO) << "client disconnect, fd: " << fd;
            close(fd);
            // 重置pollfd元素,标记为闲置
            _fdevent[idx].fd = gdefaultfd;
            _fdevent[idx].events = 0;
            _fdevent[idx].revents = 0;
        }
        else
        {
            // n<0:读取异常
            LOG(LogLevel::ERROR) << "recv error, fd: " << fd;
            close(fd);
            _fdevent[idx].fd = gdefaultfd;
            _fdevent[idx].events = 0;
            _fdevent[idx].revents = 0;
        }
    }

private:
    uint16_t _port;                          // 服务端口
    std::unique_ptr<TcpSocket> _listensockfd; // 监听套接字
    struct pollfd _fdevent[NUM];             // pollfd 监听数组
};

4.3 主函数入口(<Main.cc>)

cpp 复制代码
#include "PollServer.hpp"
#include <memory>

// 服务端口
const uint16_t g_port = 8080;

int main()
{
    // 创建Poll服务端并启动事件循环
    std::unique_ptr<PollServer> poll_svr = std::make_unique<PollServer>(g_port);
    poll_svr->Dispatcher();
    return 0;
}

4.4 编译脚本(Makefile)

bash 复制代码
# 编译目标与编译指令
poll_server: Main.cc
	g++ -o $@ $^ -std=c++17

# 清理规则
.PHONY: clean
clean:
	rm -f poll_server

4.5 代码逻辑与运行说明

4.5.1 整体执行流程

  1. 服务初始化:创建监听套接字,初始化 pollfd 数组,将监听 fd 加入监听列表。
  2. 事件循环:循环调用 poll,根据返回值区分超时、错误、事件就绪三种状态。
  3. 事件分发:遍历 pollfd 数组,通过 revents & POLLIN 判断读就绪。
    1. 监听 fd 就绪:调用 Accepter 接收新客户端,将新 fd 加入监听数组。
    2. 客户端 fd 就绪:调用 IOHandler 读取数据、回显数据。
  4. 连接清理:客户端断开或读写异常时,关闭 fd 并重置对应 pollfd 元素。

4.5.2 不同 timeout 运行效果

  1. timeout = 0:纯非阻塞轮询,进程持续循环打印超时日志,CPU 占用率较高。
  2. timeout = 2000:2 秒限时阻塞,无事件时每 2 秒打印一次超时日志。
  3. timeout = -1:永久阻塞,无事件时进程挂起,不消耗 CPU,生产环境常用配置。

4.5.3 边界场景处理

  1. 监听数组满载:拒绝新连接,关闭客户端 fd 并打印警告日志。
  2. 客户端正常断开:recv 返回 0,主动关闭 fd 并清空监听配置。
  3. 读写异常:recv 返回 -1,关闭 fd 并做异常日志记录。

4.6 优化扩展方向

当前代码使用固定大小数组,可改造为动态数组struct pollfd* _fdevent 动态内存申请),实现数组动态扩容,彻底摆脱固定容量限制,最大化发挥 poll 无硬上限的优势。


5 ~> poll 与 select 深度对比 & 技术优劣分析

5.1 poll 相对 select 的核心优势

  1. 无文件描述符硬上限 select 依赖固定长度 fd_set 位图,fd 数量受系统宏限制;poll 使用动态数组,最大监听数量仅受系统内存限制,扩展性更强。
  2. 入参出参完全解耦 selectfd_set 会被内核改写,每次循环必须重新重置监听集合;pollevents(监听事件)与 revents(就绪事件)拆分,events 一次赋值终身有效,减少代码冗余与运算开销。
  3. 事件表达更丰富 poll 提供细分的读写事件、异常事件、TCP 专属事件,事件粒度比 select 更精细,适配更多复杂网络场景。
  4. 超时参数更友好 select 使用 timeval 结构体区分秒、微秒;poll 统一使用毫秒整型,参数配置更简洁。

5.2 poll 遗留的共性问题(与 select 一致)

  1. 全量遍历开销 无论有多少 fd 就绪,应用程序都必须遍历整个 pollfd 数组判断就绪状态,fd 数量越大,遍历耗时越高。
  2. 内核与用户态数据拷贝 每次调用 poll 都需要将整个监听数组从用户态拷贝至内核态,fd 数量庞大时,拷贝开销显著。
  3. 内核轮询检测 内核内部依旧采用轮询方式检测所有 fd 状态,高并发场景下内核效率较低。

上述遗留问题,正是 epoll 技术诞生的核心契机。


6 ~> 收尾总结:重难点、面试考点

6.1 核心概念考点

  1. 本质定位poll 是 IO 多路转接实现,核心能力是内核批量监听 fd 事件、就绪后通知应用程序,属于同步 IO 模型。
  2. 结构体核心pollfd 三字段分工必须牢记:fd 标识文件描述符,events 是用户指定监听事件,revents 是内核回填就绪事件,二者解耦是 poll 最大设计亮点。
  3. 事件宏规则 :异常类事件(POLLERR/POLLHUP/POLLNVAL)仅能由内核写入 revents,应用程序不可在 events 中设置。
  4. timeout 三模式:-1 永久阻塞、0 非阻塞轮询、正数限时阻塞,不同模式对应不同业务场景。

6.2 代码实操考点

  1. 数组初始化规则 :闲置位置 fd 必须置为 -1,内核会自动跳过该元素。
  2. 事件判断语法 :固定使用按位与 revents & 事件宏 判断事件就绪,不可使用等值判断。
  3. 连接生命周期管理 :新 fd 加入监听、客户端断开后重置 pollfd 元素,是内存与文件描述符泄漏的关键防护点。
  4. 阻塞特性poll 检测到事件就绪后,对应的 recv/send/accept 不会阻塞,这是多路转接模型的基础前提。

6.3 面试 & 原理高频考点

  1. select 与 poll 区别:重点回答 fd 上限、参数耦合、事件粒度、超时参数四点差异。
  2. poll 为什么依然不适合超高并发:全量遍历、数据拷贝、内核轮询三大性能瓶颈。
  3. poll 能否动态扩容:原生固定数组无法扩容,改用动态堆数组可实现扩容,理论上无 fd 上限。
  4. POLLHUP 与 POLLERR 区别POLLHUP 是连接正常挂起(对方关闭连接),POLLERR 是 fd 发生底层错误。

6.4 学习延伸方向

pollselectepoll 的过渡技术,掌握 poll 的所有缺陷后,可针对性学习 epoll 的水平触发 / 边缘触发、事件就绪队列、内存映射等优化设计,形成完整的 Linux 多路转接知识链路。


7 ~> 多路转接Poll相关知识图谱


结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 艾莉丝努力练剑 C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日练剑达成!" "技术之路难免有困惑,但同行的人会让前进更有方向。" |

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!

往期回顾

【Linux网络】多路转接select

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა