当前位置: 首页 > news >正文

【IO多路转接】深入解析 poll:从接口到服务器实现


请添加图片描述


半桔:个人主页

 🔥 个人专栏: 《IO多路转接》《手撕面试算法》《C++从入门到入土》

🔖当你最认为困难的时候,其实就是你最接近成功的时候。《当幸福来敲门》

文章目录

  • 前言
  • 一. poll的接口
  • 二. poll服务器实现
    • 2.1 对网络套接字进行封装
    • 2.2 构建poll类
    • 2.3 进行初始化
    • 2.4 对任务进行派发
    • 2.5 服务器主循环
  • 三. poll相对于select的优势

前言

在高性能网络编程领域,IO 多路复用是应对高并发场景的核心技术之一 —— 它允许程序同时监控多个文件描述符(File Descriptor)的状态变化,从而高效处理多客户端的网络 IO 请求,解决了传统阻塞 IO 在高并发下效率低下的问题。poll 作为 IO 多路复用的经典实现机制,在 Linux 等操作系统中被广泛应用,是理解高并发服务器设计的重要基础。

本文将围绕 poll 展开,从技术原理到实践实现,逐步讲解如何基于 poll 构建高效的网络服务器。首先会剖析 poll 的核心接口与工作机制,为后续实践打下理论基础;随后聚焦于服务器的具体实现:从网络套接字的封装入手,逐步完成 poll 类的构建、初始化流程设计、任务派发策略,以及服务器主循环的实现(这部分是服务器 “持续工作” 的核心逻辑);最后,还会对比 poll 与更早出现的 select 机制,阐释 poll 在技术上的优势与改进。

通过本文的讲解,希望读者能深入理解 poll 的工作逻辑,并掌握基于 poll 开发高并发服务器的方法,为后续探索更复杂的网络编程技术(如 epoll)奠定基础。

一. poll的接口

int poll(struct pollfd *fds , nfds_t nfds , int timeuot)

struct pollfd是操作系统内提供的一个数据结构,用来存储要进行管理的相关信息:

struct pollfd {int fd;              // 要进行等待的文件描述符short events;        // 要进行等待的事件,是读事件,写事件还是什么short revents;       // 输出型参数,告诉用户那些事件已经就绪了
};

其中的fd可以设置为-1,表示该pollfd操作系统不需要进行处理。

  1. 参数一fds:告诉操作系统要对那些文件进行等待;
  2. 参数二nfds:一共要进行等待的文件个数;
  3. 参数三timeout:设置时间,时间到了/有事件就绪就返回;
  4. 返回值:表示有多少个事件已经就绪了,-1表示不进行等待。

二. poll服务器实现

此处我们仅仅是对poll服务器进行一个简单的实现,使用以下对应的接口,我们假设TCP接收时接收到的是一个完整的报文。

2.1 对网络套接字进行封装

首先我们先对网络套接字的接口进行封装:创建套接字,绑定,监听;关于这方面的知识可以查看之前的TCP相关内容,此时就直接贴实现方法:

const std::string defaultip_ = "0.0.0.0";
enum SockErr
{SOCKET_Err,BIND_Err,
};class Sock
{
public:Sock(uint16_t port): port_(port),listensockfd_(-1){}void Socket(){listensockfd_ = socket(AF_INET, SOCK_STREAM, 0);if (listensockfd_ < 0){Log(Fatal) << "socket fail";exit(SOCKET_Err);}Log(Info) << "socket sucess";}void Bind(){struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(port_);inet_pton(AF_INET, defaultip_.c_str(), &server.sin_addr);if (bind(listensockfd_, (struct sockaddr *)&server, sizeof(server)) < 0){Log(Fatal) << "bind fail";exit(BIND_Err);}Log(Info) << "bind sucess";}void Listen(){if (listen(listensockfd_, 10) < 0){Log(Warning) << "listen fail";}Log(Info) << "listen sucess";}int Accept(){struct sockaddr_in client;socklen_t len = sizeof(client);int fd = accept(listensockfd_ , (sockaddr*)&client , &len);if(fd < 0){Log(Warning) << "accept fail";}return fd;}int Accept(std::string& ip , uint16_t& port){struct sockaddr_in client;socklen_t len = sizeof(client);int fd = accept(listensockfd_ , (sockaddr*)&client , &len);if(fd < 0){Log(Warning) << "accept fail";}port = ntohs(client.sin_port);char bufferip[64];inet_ntop(AF_INET , &client.sin_addr , bufferip , sizeof(bufferip) - 1);ip = bufferip;return fd;}int Get_fd(){return listensockfd_;}~Sock(){close(listensockfd_);}private:uint16_t port_;int listensockfd_;
};

2.2 构建poll类

  1. 设置一个上述网络套接字的类,对网络套接字接口进行封装;
  2. 设置一个数组来管理每一个要进行等待的文件描述符,此处可以直接使用struct pollfd
const int defaultfd = -1;
class Pollserver
{static const int fds_array_num = 1024;         // 设置默认要进行等待的数组长度
public:Pollserver(uint port):_sock_ptr(new Sock(port)){for(int i = 0 ; i < fds_array_num ; i++){_fds_array[i].fd = defaultfd;             }}private:std::shared_ptr<Sock> _sock_ptr;               // 套接字结构体struct pollfd _fds_array[fds_array_num];       // 存储所有文件描述符相关信息
};

2.3 进行初始化

对于服务器的初始化,我们只进行一些简单的操作:

  1. 创建套接字;
  2. 绑定;
  3. 设置监听模式;
  4. 将网络套接字加入到等待数组中。
    void AddToArray(int fd , short events){// 1. 找空位置// 2. 加入fdint pos = 0;for(; pos < fds_array_num && _fds_array[pos].fd != -1 ; pos++) ;if(pos == fds_array_num){// 数组不够了// 1. 打印日志信息// 2. 关闭文件描述符Log(Warning) << "array is full";close(fd);}else{// 加入fd_fds_array[pos].fd = fd;_fds_array[pos].events = events;Log(Info) << "get a connect  , fd : " << fd; }}void Init(){// 1. 创建套接字// 2. 进行绑定// 3. 设置监听模式// 4. 将网络套接字加到_fds_array数组中_sock_ptr->Socket();_sock_ptr->Bind();_sock_ptr->Listen();AddToArray(_sock_ptr->Get_fd() , POLLIN);}

2.4 对任务进行派发

poll进行等待的时候有文件描述符读写事件就绪,我们就需要进行处理。

此时我们使用一个Dispatcher函数对任务进行派发:

  1. 遍历整个_fds_array数组,找文件描述符已经就绪的位置;
  2. 判断对应的文件描述符是不是套接字;
  3. 是套接字将建立好的连接拿上来;
  4. 是普通文件描述符就对缓冲区进行读写操作。
    void Sockfd_Ready(){int listensock = _sock_ptr->Get_fd();int newfd = _sock_ptr->Accept();AddToArray(newfd , POLLIN);}void Normalfd_Ready(int fd  , int pos){char buffer[1024];int n = read(fd  , buffer , sizeof(buffer) - 1);if(n > 0){buffer[n] = 0;std::string ret = "server get a message : ";;ret += buffer;write(fd  , ret.c_str() , ret.size());}else if(n == 0){// 对方断开连接了// 1. 将文件描述符从等待的队列中移除// 2. 关闭文件_fds_array[pos].fd = -1;close(fd);}else{// 出错了, 打印日志信息Log(Warning) << "read  fail";}}void Dispatcher(){int listensock = _sock_ptr->Get_fd();for(int i = 0 ; i < fds_array_num ; i++){int fd = _fds_array[i].fd;short eventds = _fds_array[i].revents;if(fd == defaultfd || !(eventds & POLLIN)) continue;if(fd == listensock) {Sockfd_Ready();}else{Normalfd_Ready(fd , i);}}}

2.5 服务器主循环

服务器的主循环只需要进行等待即可:

    void Run(){while(1){int n = poll(_fds_array , fds_array_num , -1);if(n > 0){Dispatcher();}else if(n == 0){Log(Info) << " no file is ready";}else{Log(Error) << "poll fail";}}}

以上就是整个pollserver服务器类的整个实现逻辑了。

三. poll相对于select的优势

与select相比:

  1. poll等待的文件描述符的个数是没有限制的;
  2. poll将输入型参数与输出型参数进行分离,使得用户使用的时候不需要每次都进行设置。

但是与select一样,两者都需要对这个数组进行遍历进行检查,对于无效位置也要进行遍历,因此我们可以使用epoll进行优化。

class Pollserver
{static const int fds_array_num = 1024;         // 设置默认要进行等待的数组长度
public:Pollserver(uint port):_sock_ptr(new Sock(port)){for(int i = 0 ; i < fds_array_num ; i++){_fds_array[i].fd = -1;             }}private:std::shared_ptr<Sock> _sock_ptr;               // 套接字结构体struct pollfd _fds_array[fds_array_num];       // 存储所有文件描述符相关信息
};

后续我们将进行epoll的讲解,来解决pollselect存在的问题。

http://www.dtcms.com/a/552826.html

相关文章:

  • 【Spring Boot】Spring Boot解决循环依赖
  • 网站开发发展趋势2018网上建立网站赚钱
  • SuperMap Hi-Fi 3D SDK for Unreal 使用蓝图接口加载多源数据
  • 【Java】如何使用jdbc连接并操作MySQL,一文读
  • SSM宠物寄养系统ih041gj7(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 网站广告素材php网站免费模板
  • 还在用JDK8?JDK8升级JDK11:一次价值千万的升级指南
  • 深圳网站建设模板乐云seo与设计行业相关的网站
  • 缓存异常:缓存穿透、缓存击穿、缓存雪崩
  • 【计算机网络】IO复用方法(二)——Select
  • 【Java EE进阶 --- SpringBoot】统一功能处理(拦截器)
  • 主流数据分析工具全景对比:Excel / Python / R / Power BI / Tableau / Qlik / Snowflake
  • 从被动防御到主动管控:雷池SafeLine的远程安全运营之道
  • 人体静电消除器安全设计 蒙冬智能
  • 我想要个网站深圳最新招聘
  • Hybrid OCR-LLM框架用于在大量复杂密集企业级文档信息提取
  • 仙居做网站在哪里做项目网格化管理方案
  • ubuntu部署whisper+speaker_large+qwen【一】
  • 四大主流平台深度测评:2025企业自动化运维平台选型指南,自动化巡检平台适配关键场景
  • 计算机毕业设计 基于Python的热门游戏推荐系统的设计与实现 Django 大数据毕业设计 Hadoop毕业设计选题【附源码+文档报告+安装调试】
  • 每周读书与学习->JMeter主要元件详细介绍(三)逻辑控制器
  • QML学习笔记(四十八)QML与C++交互:QML中可实例化C++对象
  • 深信服上网行为 SANGFOR_AC_v11.0_AD域密码认证配置
  • RKNN-Toolkit2入门
  • 服务器公网IP、私网IP、弹性IP是什么?区别与应
  • 无锡哪家做网站好怎么做公司网站文案
  • php做网站架构图建站时候源码有验证怎么办
  • 10. 引用计数
  • 利用DeepSeek辅助改写luadbi-duckdb支持日期和时间戳数据类型
  • 用 Redis 的 List 存储库存队列,并通过 LPOP 原子性出队来保证并发安全案例