文章目录
🌈 一、poll 初步认识
- poll 的诞生主要是为了解决 select 以下两个缺点(select 剩下的缺点在 poll 中依然存在):
- select 可以等待的文件描述符的数量太少。
- select 的输入输出参数混合。
- 系统调用 poll 也可以让程序同时监视多个文件描述符上的事件是否就绪,和 select 的定位相同 & 使用场景一致。
🌈 二、poll 函数原型
⭐ 1. poll 函数介绍
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
1. 参数说明
fds
:一个由 poll 函数所监视的结构体数组,其中的每一个元素都包含三部分内容(文件描述符、监视的事件集合、就绪的事件集合)nfds
:fds 数组的长度timeout
:poll 函数的超时时间(单位 ms)
2. 参数 timeout 的取值
-1
:调用 poll 后进行阻塞等待,直到被监视的文件描述符上有事件就绪为止。0
:调用 poll 后进行非阻塞等待,无论被监视的文件描述符上的事件是否就绪,poll 都会立即返回。特定的时间值
:调用 poll 后在指定的时间内进行阻塞等待,如果被监视的文件描述符上一直没有事件就绪,则在指定时间后 poll 进行超时返回。
3. 返回值说明
- 调用 poll 函数成功:返回已经有事件就绪的文件描述符的个数。
- 调用 poll 函数失败:返回 -1,同时设置错误码,错误码的可能取值如下:
EFAULT
:fds数组不包含在调用程序的地址空间中。EINTR
:此调用被信号所中断。EINVAL
:nfds值超过RLIMIT_NOFILE值。ENOMEM
:核心内存不足。
- timeout 时间耗尽:返回 0
⭐ 2. struct pollfd 结构体介绍
1. struct pollfd 的成员
- 其中,fd 和 events 由用户填写,revents 由操作系统填写。
struct pollfd
{
int fd; // 监视的文件描述符,若设置为负数则忽略 events 字段且设置 revents 字段为 0
short int events; // 需要监视 fd 这个文件描述符上的哪些事件
short int revents; // poll 函数返回时,告知用户 fd 这个文件描述符上的哪些事件已经就绪
};
2. events 和 revents 的取值
事件 | 描述 | 是否可作为输入 | 是否可作为输出 |
---|---|---|---|
POLLIN | 数据(包括普通数据和优先数据)可读 | 是 | 是 |
POLLRDNORM | 普通数据可读 | 是 | 是 |
POLLRDBAND | 优先级带数据可读(Linux 不支持) | 是 | 是 |
POLLPRI | 高优先级数据可读,比如 TCP 带外数据 | 是 | 是 |
POLLOUT | 数据(包括普通数据和优先数据)可写 | 是 | 是 |
POLLWRNORM | 普通数据可写 | 是 | 是 |
POLLWRBAND | 优先级带数据可写 | 是 | 是 |
POLLRDHUP | TCP连接被对方关闭,或者对方关闭了写操作,它由 GNU 引入 | 是 | 是 |
POLLERR | 错误 | 否 | 是 |
POLLHUP | 挂起。比如管道的写端被关闭后,读端描述符上将收到 POLLHUP 事件 | 否 | 是 |
POLLNVAL | 文件描述符没有打开 | 否 | 是 |
🌈 三、poll 的优缺点
⭐ 1. poll 的优点
struct pollfd
结构体中包含了events
和revents
两个字段,相当于将 select 的输入输出型参数进行分离。因此在每次调用poll之前,不需要像 select 一样重新对参数进行设置。- 由于 fds 数组的大小没有上限,就导致了 poll 函数能够监视的文件描述符的数量没有上限
- poll 可以同时等待多个文件描述符,能够提高 IO 的效率。
⭐ 2. poll 的缺点
- 和 select 函数一样,当poll返回后,需要遍历fds数组来获取就绪的文件描述符。
- 每次调用 poll 时,都需要把大量的
struct pollfd
结构从用户态拷贝到内核态,这个开销也会随着 poll 监视的文件描述符数目的增多而增大。 - 同时每次调用 poll 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时也很大。
🌈 sipoll 使用示例
#pragma once
#include <iostream>
#include <algorithm>
#include <sys/poll.h>
#include "socket.h"
using std::cerr;
using std::make_unique;
using std::max;
using std::unique_ptr;
using namespace socket_ns;
class poll_server
{
const static int fd_max_num = 1024;
const static int defauld_fd = -1;
private:
uint16_t _port;
unique_ptr<Socket> _listen_socket;
bool _isrunning;
struct pollfd* _rfds = nullptr;
int _num;
public:
poll_server(uint16_t port)
: _port(port)
, _listen_socket(make_unique<tcp_socket>())
, _isrunning(false)
, _num(fd_max_num)
{}
// 初始化服务器
void init_server()
{
_listen_socket->build_listen_socket(_port);
_rfds = new struct pollfd[_num];
for (size_t i = 0; i < _num; i++)
{
_rfds[i].fd = defauld_fd;
_rfds[i].events = 0;
_rfds[i].revents = 0;
}
// 最初只有一个文件描述符
_rfds[0].fd = _listen_socket->sockfd();
_rfds[0].events |= POLLIN;
}
// 获取连接(处理 listen fd 的就绪事件)
void accept_connection()
{
// 有新连接到来 - 连接事件就绪
// 处理 listen 套接字
Inet_addr addr;
int sockfd = _listen_socket->accepter(&addr); // 由于 select 的原因,一定不会阻塞
if (sockfd > 0)
{
LOG(DEBUG, "get a new link success, client info: %s:%d", addr.ip().c_str(), addr.port());
// 将新的 fd 交给 poll 托管
int pos = 0;
for (; pos < _num; pos++)
{
if (_rfds[pos].fd == defauld_fd)
{
_rfds[pos].fd = sockfd;
_rfds[pos].events |= POLLIN;
break;
}
}
if (pos == _num)
{
close(sockfd);
}
}
}
// 处理 IO(处理普通 fd 的就绪事件)
void handle_io(int i)
{
// 处理普通 sockfd,正常读写
char buffer[1024];
// 这里的读取不会阻塞,因为 select 已经判断出 fd 的读事件就绪
ssize_t n = ::recv(_rfds[i].fd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = '\0';
cout << "client say: " << buffer << endl;
// 服务器给客户端回复
string content = "<html><body><h1>hello world</h1></body></html>";
string echo_str = "HTTP/1.0 200 OK\r\n";
echo_str += "Content-Type: text/html\r\n";
echo_str += "Content-Length: " + to_string(content.size()) + "\r\n\r\n";
echo_str += content;
// 发送数据
::send(_rfds[i].fd, echo_str.c_str(), echo_str.size(), 0);
}
else if (0 == n)
{
// 对端关闭连接
LOG(INFO, "client quit, sockfd: %d", _rfds[i].fd);
close(_rfds[i].fd);
_rfds[i].fd = defauld_fd; // 让 select 不要再关心这个 fd 了
}
else
{
// 读取失败
LOG(ERROR, "recv error, sockfd: %d", _rfds[i].fd);
close(_rfds[i].fd);
_rfds[i].fd = defauld_fd;
}
}
// 处理已经就绪的事件 (一定会同时存在大量就绪的 fd,可能是普通 sockfd,也可能是 listensockfd)
void handle_event()
{
for (size_t i = 0; i < _num; i++)
{
if (_rfds[i].fd == defauld_fd)
continue;
int fd = _rfds[i].fd;
int revents = _rfds[i].revents;
if (revents & POLLIN)
{
if (fd == _listen_socket->sockfd())
accept_connection(); // 处理连接事件
else
handle_io(i); // 处理 IO 事件
}
}
}
// 展示所有合法的 fd
void print_all_legal_fd()
{
cout << "legal fd list: ";
for (size_t i = 0; i < fd_max_num; i++)
if (_rfds[i].fd != defauld_fd)
cout << _rfds[i].fd << " ";
cout << endl;
}
// 运行服务器
void loop()
{
_isrunning = true;
while (true)
{
int timeout = -1;
int n = ::poll(_rfds, _num, timeout);
switch (n)
{
case 0:
LOG(DEBUG, "timeout, %d", timeout);
break;
case -1:
LOG(ERROR, "poll error");
break;
default:
LOG(INFO, "haved event ready, n: %d", n); // 如果事件就绪,但时不处理,select 会一直通知我,直到事件被处理为止
handle_event();
print_all_legal_fd();
sleep(1);
break;
}
}
}
~poll_server()
{
if (_listen_socket)
close(_listen_socket->sockfd());
}
};