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

【Linux网络】I/O多路转接技术 - poll

📢博客主页:https://blog.csdn.net/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 🏳️‍🌈一、什么是 POLL
  • 🏳️‍🌈二、poll 函数接口
  • 🏳️‍🌈三、poll 的优缺点
  • 🏳️‍🌈四、模拟 poll
    • 4.1 PollServer 类
      • 4.1.1 基本结构
      • 4.1.2 构造函数、析构函数
      • 4.1.3 初始化函数 InitServer()
      • 4.1.4 循环函数 Loop()
      • 4.1.5 处理函数 HandlerEvent()
      • 4.1.6 连接建立处理函数 HandlerNewConnection()
      • 4.1.7 普通链接建立处理函数 HandlerIO()
    • 4.2 主函数 PollServer.cpp
    • 4.3 运行结果
  • 🏳️‍🌈五、整体函数
    • 5.1 PollServer.hpp
    • 5.2 PollServer.cpp
  • 👥总结


🏳️‍🌈一、什么是 POLL

上一篇文章中我们介绍了IO多路转接中的 select ,但是 select 存在4个明显的缺点

  1. 每次调用 select都需要手动设置 fd 集合,从接口使用角度来说是非常不便的
  2. 每次调用 select都需要把 fd 集合从用户态拷贝到内核态,这个开销在fd很多时是很大的
  3. 每次调用 select都需要在内核遍历传递进来的所有 fd,这个开销很大
  4. select 支持的文件描述符数量太小

而这篇文章中介绍的 poll 将有效地解决上述两个问题 (1和4)

  1. 重新设定对 fd 和 关心的事件
  2. poll 等待的fd无上限

概念

poll 函数用于监视多个文件描述符以查看它们是否有 I/O(输入/输出)活动。

  • 作用:为了等待多个fd,等待fd上面的新事件就绪,通知程序员,事件已经就绪,可以进行IO拷贝了!
  • 定位:只负责进行等,等就绪事件派发!

🏳️‍🌈二、poll 函数接口

poll 函数

#include <poll.h>int poll(struct pollfd* fds, nfds_t nfds, int timeout);
  • fds指向 pollfd 结构体数组的指针,每个结构体指定一个要监视的文件描述符 及 要监视的事情
  • nfds数组 fds 中包含的结构体数量,即要监视的文件描述符数量
  • timeout超时时间(毫秒),-1 表示无限等待,0 表示立即返回,其他值表示等待指定的时间(毫秒)
  • 返回值:成功时返回活跃的文件描述符数量失败时返回 -1 并设置 errno

pollfd 结构体

struct pollfd{int fd;            short events;short revents;
};

events 和 revents 的取值:
在这里插入图片描述
在这里插入图片描述

🏳️‍🌈三、poll 的优缺点

优点
不同于 select 使用三个位图来表示三个 fdset 的方式poll 使用一个 pollfd 的指针实现.
在这里插入图片描述

  • pollfd 结构包含了要监视的 event 和发生的 event,不再使用 select“参数-值”传递的方式. 接口使用比 select 更方便.
  • poll没有最大数量限制 (但是数量过大后性能也是会下降).

缺点

poll 中监听的文件描述符数目增多时

  • 和 select 函数一样,poll 返回后,需要轮询 pollfd 来获取就绪的描述符.
  • 每次调用 poll 都需要把大量的 pollfd 结构从用户态拷贝到内核中.
  • 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降.

🏳️‍🌈四、模拟 poll

4.1 PollServer 类

4.1.1 基本结构

PollServer类的成员变量与SelectServer类的成员变量基本一致但是此处的数组(存放fd)类型是struct pollfd,还需要端口号和套接字

#include <iostream>
#include <poll.h>
#include "Socket.hpp"using namespace SocketModule;class PollServer{const static int gnum = sizeof(fd_set) * 8;const static int gdefault = -1;public:PollServer(uint16_t port);void InitServer();void HandlerNewConnection();void HandlerIO();void HandlerEvent();void Loop();void PrintDebug();~PollServer();private:uint16_t _port;SockPtr _listensock;struct pollfd fd_events[gnum];
};

4.1.2 构造函数、析构函数

基本不变,与 select一样

PollServer(uint16_t port): _port(port), _listensock(std::make_shared<TcpSocket>()) {_listensock->BuildListenSocket(_port);
}
~PollServer() {}

4.1.3 初始化函数 InitServer()

InitServer() 函数将结构体类型的数组fd成员设置为默认fd,其他两个事件先设置为0
将listensockfd添加到结构体数组的第一个元素的fd成员,并将events事件设置为读!

void InitServer() {for (int i = 0; i < gnum; ++i) {fd_events[i].fd = gdefault;fd_events[i].events = 0;fd_events[i].revents = 0;}fd_events[0].fd = _listensock->Sockfd();fd_events[0].events = POLLIN; // POLLIN: 可读
}

4.1.4 循环函数 Loop()

Loop()函数调用poll系统调用,根据返回值执行对应的操作:

  1. 返回值为0 :打印超时日志,并退出循环

  2. 返回值为-1 :打印出错日志,并退出循环

  3. 返回值大于0 :处理事件

void Loop() {while (true) {int timeout = 1000;int n = poll(fd_events, gnum, timeout);switch (n) {case 0:LOG(LogLevel::DEBUG) << "poll timeout";break;case -1:LOG(LogLevel::ERROR) << "poll error";break;default:LOG(LogLevel::INFO) << "haved fd ready , " << n;HandlerEvent();PrintDebug();break;}}
}

4.1.5 处理函数 HandlerEvent()

在执行 HandlerEvent() 函数之前,赋值数组中一定存在大量的fd就绪,可能是普通sockfd,也可能是listensockfd,此处主要分以下两步:

  1. 判断fd是否合法
  2. 判断fd是否就绪
    2.1. 就绪是 listensockfd,调用 Accepter() 处理新链接函数
    2.2. 就绪是 normal sockfd,调用 HandlerIO() 处理普通fd就绪函数
void HandlerEvent() {for (int i = 0; i < gnum; ++i) {// 1. 判断 fd 是否合法if (fd_events[i].fd == gdefaultfd)continue;// 2. 判断 fd 是否就绪// 必须使用 & 运算符,否则会出现错误if (fd_events[i].revents & POLLIN) {// 读事件就绪if (_listensock->Sockfd() == fd_events[i].fd) {HandlerNewConnection();}// 其他事件就绪else {HandlerIO(i);}}}
}

4.1.6 连接建立处理函数 HandlerNewConnection()

Accepter() 函数处理新链接,主要分为以下三步

1、获取链接
2、获取链接成功将新的fd 和 读事件 添加到数组中
3、数组满了,需关闭sockfd,此处可以扩容并再次添加新的fd和事件

void HandlerNewConnection() {InetAddr client;// 这个时候一定不会阻塞,因为监听套接字已经就绪了int sockfd = _listensock->Accepter(&client);if (sockfd > 0) {LOG(LogLevel::DEBUG)<< "get a new connection from " << client.AddrStr().c_str()<< ", sockfd : " << sockfd;bool flag = false;for (int pos = 1; pos < gnum; ++pos) {if (fd_events[pos].fd == gdefaultfd) {fd_events[pos].fd = sockfd;fd_events[pos].events = POLLIN; // POLLIN: 可读LOG(LogLevel::DEBUG)<< "set fd_events[" << pos << "] to " << sockfd;break;}}// 数组满了if (!flag) {LOG(LogLevel::WARNING) << "fd_events is full";::close(sockfd);// 扩容// 添加}}
}

4.1.7 普通链接建立处理函数 HandlerIO()

HandlerIO() 函数处理普通fd情况,直接读取文件描述符中的数据,根据recv()函数的返回值做出不一样的决策,主要分为以下三种情况:

  1. 返回值大于0,读取文件描述符中的数据,并使用 send() 函数做出回应!
  2. 返回值等于0,读到文件结尾,打印客户端退出的日志,关闭文件描述符,并将该下标的文件描述符设置为默认fd,事件都设置为0
  3. 返回值小于0,读取文件错误,打印接受失败的日志,然后同上!
void HandlerIO(int i) {char buffer[1024];// 这里同样不会被阻塞,因为是就绪后才会执行ssize_t n = ::recv(fd_events[i].fd, buffer, sizeof(buffer), 0);if (n > 0) {buffer[n] = 0;std::cout << "client say# " << buffer << std::endl;std::string content = "<html><body><h1>hello linux</h1></body></html>";std::string echo_str = "HTTP/1.0 200 OK\r\n";echo_str += "Content-Type: text/html\r\n";echo_str +="Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";echo_str += content;::send(fd_events[i].fd, echo_str.c_str(), echo_str.size(), 0);} else if (n == 0) {LOG(LogLevel::DEBUG) << "client " << fd_events[i].fd << " closed";::close(fd_events[i].fd);fd_events[i].fd = gdefaultfd;fd_events[i].events = 0;fd_events[i].revents = 0;} else {LOG(LogLevel::ERROR) << "recv error";::close(fd_events[i].fd);fd_events[i].fd = gdefaultfd;fd_events[i].events = 0;fd_events[i].revents = 0;}
}

4.2 主函数 PollServer.cpp

#include "PollServer.hpp"int main(int argc, char* argv[]){if(argc != 2){std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;exit(0);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<PollServer> svr = std::make_unique<PollServer>(port);svr->InitServer();svr->Loop();return 0;
}

4.3 运行结果

在这里插入图片描述
在这里插入图片描述

🏳️‍🌈五、整体函数

5.1 PollServer.hpp

#pragma once#include <iostream>
#include <poll.h>
#include "Socket.hpp"using namespace SocketModule;class PollServer{const static int gnum = sizeof(fd_set) * 8;const static int gdefaultfd = -1;public:PollServer(uint16_t port): _port(port),_listensock(std::make_shared<TcpSocket>()){_listensock->BuildListenSocket(_port);}void InitServer(){for(int i = 0; i < gnum; ++i){fd_events[i].fd = gdefaultfd;fd_events[i].events = 0;fd_events[i].revents = 0;}fd_events[0].fd = _listensock->Sockfd();fd_events[0].events = POLLIN;   // POLLIN: 可读}void HandlerNewConnection(){InetAddr client;// 这个时候一定不会阻塞,因为监听套接字已经就绪了int sockfd = _listensock->Accepter(&client);if(sockfd > 0){LOG(LogLevel::DEBUG) << "get a new connection from " << client.AddrStr().c_str() << ", sockfd : " << sockfd;bool flag = false;for(int pos = 1; pos < gnum; ++pos){if(fd_events[pos].fd == gdefaultfd){flag = true;fd_events[pos].fd = sockfd;fd_events[pos].events = POLLIN;   // POLLIN: 可读LOG(LogLevel::DEBUG) << "set fd_events[" << pos << "] to " << sockfd;break;}}//数组满了if(!flag){LOG(LogLevel::WARNING) << "fd_events is full" ;::close(sockfd);// 扩容// 添加}}}void HandlerIO(int i){char buffer[1024];// 这里同样不会被阻塞,因为是就绪后才会执行ssize_t n = ::recv(fd_events[i].fd, buffer,sizeof(buffer), 0);if(n > 0){buffer[n] = 0;std::cout << "client say# " << buffer << std::endl;std::string content = "<html><body><h1>hello linux</h1></body></html>";std::string echo_str = "HTTP/1.0 200 OK\r\n";echo_str += "Content-Type: text/html\r\n";echo_str += "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";echo_str += content;::send(fd_events[i].fd, echo_str.c_str(), echo_str.size(), 0);} else if(n == 0){LOG(LogLevel::DEBUG) << "client " << fd_events[i].fd << " closed";::close(fd_events[i].fd);fd_events[i].fd = gdefaultfd;fd_events[i].events = 0;fd_events[i].revents = 0;} else{LOG(LogLevel::ERROR) << "recv error";::close(fd_events[i].fd);fd_events[i].fd = gdefaultfd;fd_events[i].events = 0;fd_events[i].revents = 0;}}void HandlerEvent(){for(int i = 0; i < gnum; ++i){// 1. 判断 fd 是否合法if(fd_events[i].fd == gdefaultfd)continue;// 2. 判断 fd 是否就绪// 必须使用 & 运算符,否则会出现错误if(fd_events[i].revents & POLLIN){// 读事件就绪if(_listensock->Sockfd() == fd_events[i].fd){HandlerNewConnection();}// 其他事件就绪else{HandlerIO(i);}}}}void Loop(){while(true){int timeout = 1000;int n = poll(fd_events, gnum, timeout);// 返回 ·0 表示超时,返回-1 表示出错switch(n){case 0:LOG(LogLevel::DEBUG) << "poll timeout";break;case -1:LOG(LogLevel::ERROR) << "poll error";break;default:// 如果事件就绪,但是不处理,select就会一直通知我,知道我处理了LOG(LogLevel::INFO) << "haved fd ready , " << n;HandlerEvent();PrintDebug();sleep(1);break;}}}void PrintDebug(){std::cout << "fd list: ";for (int i = 0; i < gnum; i++){if (fd_events[i].fd == gdefaultfd)continue;std::cout << fd_events[i].fd << " ";}std::cout << "\n";}~PollServer(){}private:uint16_t _port;SockPtr _listensock;struct pollfd fd_events[gnum];
};

5.2 PollServer.cpp

#include "PollServer.hpp"int main(int argc, char* argv[]){if(argc != 2){std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;exit(0);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<PollServer> svr = std::make_unique<PollServer>(port);svr->InitServer();svr->Loop();return 0;
}

👥总结

本篇博文对 【Linux网络】I/O多路转接技术 - poll 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

http://www.xdnf.cn/news/3347.html

相关文章:

  • DAY01:Vue零基础入门:环境搭建与核心语法深度解析
  • 数据库Mysql_约束
  • 第二章 日志分析-apache日志分析(玄机系列)
  • 【论文阅读26】贝叶斯-滑坡预测-不确定性
  • 图解 Git 工作流:理解 Rebase、Merge 与 Pull Request 的区别
  • 基于Redis实现-用户签到
  • C++——入门基础(2)
  • podman/docker国内可用的docker镜像源(2025-05)
  • 前端八股 3
  • Linux-04-搜索查找类命令
  • WPF实现数据库操作与日志记录
  • 工行手机银行安全吗?在应用商店下载工商银行安全吗?
  • 工 厂 模 式
  • 17. LangChain流式响应与实时交互:打造“类ChatGPT“体验
  • 数字智慧方案5974丨智慧农业大数据应用平台综合解决方案(79页PPT)(文末有下载方式)
  • 数据结构与算法学习笔记(Acwing提高课)----动态规划·背包模型(二)
  • 经典算法 青蛙跳杯子
  • 【大模型实战篇】华为信创环境采用vllm部署QwQ-32B模型
  • 【MySQL】复合查询与内外连接
  • 补题( Convolution, 二维卷积求输出矩阵元素和最大值)
  • 【方案分享】基于Three.js和Stencil Buffer的AR实物遮挡方案,支持不规则动态区域(AR地下设施、AR虚实遮挡)
  • 前端面经-webpack篇--定义、配置、构建流程、 Loader、Tree Shaking、懒加载与预加载、代码分割、 Plugin 机制
  • ruoyi-plus Spring Boot + MyBatis 中 BaseEntity 的设计与动态查询实践
  • AVDictionary 再分析
  • 安全学习基础入门5集
  • curl详解
  • 综合案例建模(1)
  • 毕业论文 | 基于STM32的自动烟雾报警系统设计
  • 4.30阅读
  • Seata客户端@GlobalTransactional核心源码解析