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

【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 做了一个较为详细的介绍,不知道对你有没有帮助呢

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

相关文章:

  • Dagster资产工厂实战:从Python到YAML配置的高效ETL流程
  • 面试手撕——迭代法中序遍历二叉树
  • Python 装饰器基础知识科普
  • 【嵌入式———通用定时器基本操作——实验需求2:案列:测量PWM的频率/周期】
  • 【二】数字图像处理基础(上)【数字图像处理】
  • Linux日常使用与运维的AI工具全景调研:效率革命的终极指南
  • SpringBoot使用分组校验解决同一个实体对象在不同场景下需要不同校验规则的问题
  • 坚鹏:平安保险集团《保险行业发展趋势与AI应用方法及案例》培训
  • SpringAI整合DeepSeek生成图表
  • 工行手机银行安全吗?在应用商店下载工商银行安全吗?
  • 前端八股 CSS 1
  • py使用uniad原生sdk 3, 放弃Buildozer,使用BeeWare
  • 审计专员简历模板
  • 【LeetCode Hot100】图论篇
  • WSGI(Web Server Gateway Interface)服务器
  • css中盒模型有哪些
  • WPF处理大规模激光数据计算与安全传输处理
  • WebDeveloper 流量分析、sudo提权,靶场通关WP
  • Codeforces Round 1008 (Div. 2) C
  • 精品推荐-湖仓一体电商数据分析平台实践教程合集(视频教程+设计文档+完整项目代码)
  • 五大白酒去年净利超1500亿元:贵州茅台862亿领跑,洋河营收净利齐降
  • 前行中的“模速空间”:要攻克核心技术,也要成为年轻人创业首选地
  • 韩国检方结束对尹锡悦私宅的扣押搜查
  • 量子传感新技术“攻克”退相干难题
  • 美航母一战机坠海,美媒:为躲避胡塞武装攻击,损失超六千万美元
  • 美军空袭也门拘留中心,已致68人死亡