目录
1. poll 的介绍
poll 是 Linux/Unix 系统中改进型的 I/O 多路复用机制,设计目标是解决 select 的核心痛点(如 FD 数量硬限制、fd_set 集合操作繁琐),同时保留 I/O 多路复用的核心逻辑(单个进程监控多个 FD)。
1.1 poll 函数介绍
原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
头文件:
#include <poll.h>
参数:
fds:struct pollfd 数组地址,存储所有待监控的 FD 及其事件配置。
nfds:fds 数组的长度,即需要监控的 FD 总数,无上限。
timeout:timeout > 0,阻塞 timeout 毫秒,若期间有 FD 准备就绪则立即返回,
超时无 FD 就绪则返回0;timeout = 0,非阻塞模式,有无 FD 都立即返回;timeout = -1,
阻塞等待,直到有 FD 就绪才返回。
返回值:
>0:表示具体有多少个就绪的 fd。
=0:表示在超时时间内没有 fd 准备就绪。
<0:select 错误。
功能:
让一个进程 / 线程同时监控多个文件描述符(File Descriptor,FD),
等待其中任意一个或多个 FD 变为 “就绪状态”(可读、可写或发生异常),
从而避免为每个 FD 单独创建线程 / 进程,提升 I/O 效率。
// struct pollfd 结构体
struct pollfd {
int fd; // 待监控的文件描述符(FD)
short events; // 期望监控的事件(输入参数,如 POLLIN 表示监控可读)
short revents; // 实际发生的事件(输出参数,由内核填充,如 POLLIN 表示可读就绪)
};
1.2 常用事件标记
下列常用事件标记的宏值,决定了 poll 监听 fd 的哪些事件的宏。

2. poll 的优缺点
优点:
1. 无 FD 数量硬限制:poll 用动态数组(pollfd 数组)替代 select 的 fd_set 位图,摆脱了 FD_SETSIZE(默认 1024)的硬限制。
2. FD 与事件管理更灵活:每个 FD 的 “监控事件”(如可读、可写)和 “就绪事件”(如实际发生的事件)通过 pollfd 结构体的 events(输入)和 revents(输出)分离,不会像 select 那样破坏原监控集合。减少代码冗余。
3. 跨平台兼容性强:遵循 POSIX 标准,支持 Linux、Unix、macOS 等主流类 Unix 系统,Windows 也可通过 WSAPoll 接口实现兼容(逻辑一致)。
缺点:
1. 线性扫描效率低:poll 返回后,无法直接获取 “就绪 FD 列表”,仍需遍历整个 pollfd 数组,通过检查 revents 字段判断 FD 是否就绪。时间复杂度为 O (n),当 FD 数量达到万级以上时,遍历开销会急剧增大,成为性能瓶颈。
2. 内核 / 用户态拷贝开销:每次调用 poll 时,需将整个 pollfd 数组从用户态拷贝到内核态;返回时,内核需将更新后的 revents 字段(整个数组)拷贝回用户态。FD 越多,拷贝耗时越长,浪费 CPU 资源。
3. 使用 poll 实现 TCP 回显服务器
3.1 前置代码
参考多路复用 select 中的前置代码。
3.2 代码实现
// pollServer.hpp -- poll 服务器类
#pragma once
#include <iostream>
#include <memory>
#include <unistd.h>
#include <poll.h>
#include "Socket.hpp"
using namespace SocketModule;
using namespace LogModule;
class PollServer
{
const static int size = 4096;
const static int defaultfd = -1;
public:
PollServer(int port) : _listensock(std::make_unique<TcpSocket>()), _isrunning(false)
{
// 1. 创建套接字并绑定端口号并开始进行监听
_listensock->BuildTcpSocketMethod(port);
// 2. 将 pollfd 数组中的 fd 全部默认设置为 -1
for (int i = 0; i < size; ++i)
_fds[i].fd = defaultfd;
// 3. 将 pollfd 数组中首元素的 fd 设置为监听套接字的 fd
_fds[0].fd = _listensock->Fd();
_fds[0].events = POLLIN;
}
void Start()
{
_isrunning = true;
while (_isrunning)
{
PrintFd();
int n = poll(_fds, size, -1);
switch (n)
{
case -1:
// poll 错误
LOG(LogLevel::ERROR) << "poll error";
break;
case 0:
// 2. poll 阻塞等待超时
LOG(LogLevel::INFO) << "poll time out...";
break;
default:
// 3. 读事件就绪(listen 套接字也是读事件就绪)
LOG(LogLevel::DEBUG) << "读事件就绪..., n: " << n;
Dispatcher(); // 进行事件派发
break;
}
}
_isrunning = false;
}
void Stop()
{
_isrunning = false;
}
~PollServer() {}
// 事件派发器
void Dispatcher()
{
for (int i = 0; i < size; ++i)
{
// 1. 排除不合法的 fd
if (_fds[i].fd == defaultfd)
continue;
// 2. 将合法且就绪的 fd 进行事件派发
if (_fds[i].revents & POLLIN)
{
if (_fds[i].fd == _listensock->Fd())
Accepter();
else
Recver(i);
}
}
}
// 连接管理器
void Accepter()
{
InetAddr client;
int sockfd = _listensock->Accept(&client); // 此时的 accept 不会被阻塞
if (sockfd >= 0)
{
LOG(LogLevel::INFO) << "get a new link, sockfd: " << sockfd << ", client is: " << client.StringAddr();
// 将新连接托管给 select
int pos = 0;
for (; pos < size; ++pos)
if (_fds[pos].fd == defaultfd)
break;
if (pos == size)
{
LOG(LogLevel::WARNING) << "poll server full";
close(sockfd);
}
else {
_fds[pos].fd = sockfd;
_fds[pos].events = POLLIN;
_fds[pos].revents = 0;
}
}
}
// IO处理器
void Recver(int pos)
{
char buffer[1024];
ssize_t n = recv(_fds[pos].fd, buffer, sizeof(buffer) - 1, 0); // 这里读的时候会有 bug,不能保证一次读取全部的数据
if (n > 0)
{
// 1. 读取到客户端传入的数据
buffer[n] = 0;
std::cout << "sockfd: " << _fds[pos].fd << ", client say@ " << buffer << std::endl;
}
else if (n == 0)
{
// 2. 客户端退出 -- 关闭连接并将其 fd 从 pollfd 数组中移除
LOG(LogLevel::INFO) << "sockfd: " << _fds[pos].fd << ", client quit...";
close(_fds[pos].fd);
_fds[pos].fd = defaultfd;
_fds[pos].events = 0;
_fds[pos].revents = 0;
}
else
{
// 3. 读取出错 -- 关闭连接并将其 fd 从 pollfd 数组中移除
LOG(LogLevel::ERROR) << "recv error";
close(_fds[pos].fd);
_fds[pos].fd = defaultfd;
_fds[pos].events = 0;
_fds[pos].revents = 0;
}
}
void PrintFd()
{
std::cout << "_fds[].fd: ";
for (int i = 0; i < size; ++i)
if (_fds[i].fd != defaultfd)
std::cout << _fds[i].fd << " ";
std::cout << std::endl;
}
private:
std::unique_ptr<Socket> _listensock;
bool _isrunning;
pollfd _fds[size];
};
// main.cc -- 主函数
#include "PollServer.hpp"
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cout << "Usage: " << argv[0] << " port" << std::endl;
exit(USAGE_ERR);
}
Enable_Console_Log_Strategy();
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<PollServer> svr = std::make_unique<PollServer>(port);
svr->Start();
return 0;
}
效果与使用 select 实现的服务器相同。
1194

被折叠的 条评论
为什么被折叠?



