Linux多路转接poll
Linux多路转接poll
1. poll()
poll()
结构包含了要监视的 event 和发生的 event ,接口使用比 select()
更方便。且 poll 并没有最大数量限制(但是数量过大后性能也是会下降)。
2. poll() 的工作原理
poll()
不再需要像 select()
那样自行设置文件描述符集合,它只需要用户在 pollfd
结构体中设置文件描述符及其关心的事件(events
),在输出时,结构体内的 revents
作为函数调用后事件就绪的结果, 就 select()
与poll()
的不同而言, poll()
支持用户自定义关心某些事件,同时将事件就绪的结构用不同的变量保存起来。
poll()
会返回就绪文件描述符的数量,用户需要自行设置判断条件,遍历pollfd
结构体中的events
,将其与自己关心的事件按位与(&
),然后执行 recv()
或其他函数读取或进行其他操作。
3. 函数声明
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
struct pollfd *fds
:*fds
表示”数组“的起始地址。下面给出struct pollfd
的具体内容。
nfds_t nfds
:表示数组有效元素的个数。nfds_t
是unsigned long
typedef 出来的类型。
timeout
:表示超时时间,以毫秒为单位,等于0
表示非阻塞等待,-1
表示阻塞等待。
reval
:返回值大于0
,返回事件就绪文件描述符的数量;返回值小于0
表示出错;返回值等于0
表示超时。
4. struct pollfd
struct pollfd
{
int fd; /* 文件描述符 */
short events; /* 等待的事件集 */
short revents; /* 实际发生的事件集 */
};
events
:表示调用 poll()
时希望检测的事件类型。
revents
:表示在 poll()
调用之后,实际发生的事件集。poll()
会在返回时填充该字段,以指示文件描述符上发生了哪些事件。
常见的事件类型有:
事件 | 描述 | 是否可作为输入 | 是否可作为输出 |
---|---|---|---|
POLLIN | 数据(包括普通数据和优先数据)可读 | 是 | 是 |
POLLRDNRM | 普通数据可读 | 是 | 是 |
POLLRDBAND | 优先级带数据可读(Linux不支持) | 是 | 是 |
POLLPRI | 高级优先数据可读,比如 TCP 带外数据 | 是 | 是 |
POLLOUT | 数据(包括普通数据和优先数据)可写 | 是 | 是 |
POLLWRNRM | 普通数据可写 | 是 | 是 |
POLLWRBAND | 优先级带数据可写 | 是 | 是 |
POLLRDHUP | TCP 连接被对方关闭,或者对方关闭了写操作,它由 GNU 引入 | 是 | 是 |
POLLERR | 错误发生 | 否 | 是 |
POLLHUP | 挂起。比如管道的写端被关闭后,读端描述符上将收到 POLLHUP 事件 | 否 | 是 |
POLLNVAL | 文件描述符没有打开 | 否 | 是 |
5. poll() 的使用
#pragma once
#include <iostream>
#include <poll.h>
#include "Socket.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace socket_ns;
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_unique<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;
}
void Accepter()
{
InetAddr addr;
int sockfd = _listensock->Accepter(&addr);
if (sockfd > 0)
{
LOG(DEBUG, "get a new link, client info %s:%d\n", addr.Ip().c_str(), addr.Port());
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;
LOG(INFO, "add %d to fd_array success!\n", sockfd);
break;
}
}
if (!flag)
{
LOG(WARNING, "Server Is Full!\n");
::close(sockfd);
// 扩容
// 添加
}
}
}
// 处理普通的fd就绪的
void HandlerIO(int i)
{
char buffer[1024];
ssize_t n = ::recv(fd_events[i].fd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
std::cout << "client say# " << buffer << std::endl;
std::string content = "<html><body><h1>hello bite</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(INFO, "client quit...\n");
::close(fd_events[i].fd);
fd_events[i].fd = gdefaultfd;
fd_events[i].events = 0;
fd_events[i].revents = 0;
}
else
{
LOG(ERROR, "recv error\n");
::close(fd_events[i].fd);
fd_events[i].fd = gdefaultfd;
fd_events[i].events = 0;
fd_events[i].revents = 0;
}
}
// 一定会存在大量的fd就绪,可能是普通sockfd,也可能是listensockfd
void HandlerEvent()
{
// 事件派发
for (int i = 0; i < gnum; i++)
{
if (fd_events[i].fd == gdefaultfd)
continue;
// fd一定是合法的fd
// 合法的fd不一定就绪, 判断fd是否就绪
if (fd_events[i].revents & POLLIN)
{
// 读事件就绪
// 1. listensockfd 2. normal sockfd就绪
if (_listensock->Sockfd() == fd_events[i].fd)
{
Accepter();
}
else
{
HandlerIO(i);
}
}
}
}
void Loop()
{
int timeout = -1;
while (true)
{
int n = ::poll(fd_events, gnum, timeout);
switch (n)
{
case 0:
LOG(DEBUG, "time out\n");
break;
case -1:
LOG(ERROR, "poll error\n");
break;
default:
LOG(INFO, "haved event ready, n : %d\n", n);
HandlerEvent();
PrintDebug();
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;
std::unique_ptr<Socket> _listensock;
struct pollfd fd_events[gnum];
};
6. poll() 的缺点
poll()
返回后,需要轮询pollfd 来获取就绪的文件描述符。
每次调用 poll()
都要把大量的 pollfd 结构从用户态拷贝到内核态。
poll()
的底层,也需要操作系统遍历所有的文件描述符,来获取就绪的文件描述符和它的事件,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。