I/O 多路转接之poll
初识 poll
- 系统提供 poll函数来实现多路复用输入/输出模型.
IO = 等 + 拷贝
。poll只负责对fd进行等待,有事件就绪,就进行事件的派发。可以同时对多个文件描述符进行等待。
poll解决了select
的一些问题:
- 同时等待的fd的个数有上限的问题。
- select输入输出参数混合,每次需要重新设定的问题。
poll函数原型
- 主要作用是对多个文件描述符进行状态监测,属于多路复用I/O的一种实现方式。
参数:
- fds:是一个指向struct pollfd结构数组的指针,每个struct pollfd结构体包含了一个文件描述符和该描述符上感兴趣的事件。
- nfds:指定了fds数组中元素的数量。
- timeout:指定了poll函数等待事件发生的超时时间(
毫秒
)。如果设置为-1
,poll将无限期等待
;如果设置为0
,poll将立即返回
,不等待任何事件发生。
返回值
- 大于0:表示有文件描述符的状态发生了变化,返回值是状态发生变化的文件描述符的数量。
- 0:表示在指定的超时时间内,没有文件描述符的状态发生变化,poll函数超时返回。
- -1:表示poll函数调用失败,此时会设置全局变量errno以指示错误原因。
struct pollfd结构体是poll函数的核心
,其定义如下:
- fd:需要监测的文件描述符。
- events:指定对fd上哪些事件感兴趣,可以是一个或多个事件的位或(bitwise OR)组合。
- revents:在调用poll函数返回时,由
系统填写
,表示在fd上实际发生了哪些事件。
验证timeout三种情况:
示例代码:
PollServer.hpp
#pragma once
#include <iostream>
#include <poll.h>
#include <unistd.h>
#include "Socket.hpp"
const static int defaltval = -1;
const static int gdefaultnum = 1024;
class PollServer
{
public:
PollServer(uint16_t port) : _port(port),
_listensock(std::make_unique<TcpSocket>(_port))
{
InetAddr addr("0", _port);
_listensock->CreateTcpSocket(addr);
for (int i = 0; i < gdefaultnum; i++)
{
_events[i].fd = defaltval;
_events[i].events = POLLIN;
_events[i].revents = 0;
}
_events[0].fd = _listensock->Getsockfd();
_events[0].events = POLLIN;
_events[0].revents = 0;
}
void AcceptListen()
{
InetAddr client = InetAddr();
int sockfd = _listensock->Accepter(&client);
if (sockfd < 0)
{
LOG(WARNING, "accept error...");
}
int i = 0;
for (; i < gdefaultnum; i++)
{
if (_events[i].fd == defaltval) // 找到一个空位置
{
_events[i].fd = sockfd; // 保存新的文件描述符
_events[i].events = POLLIN;
_events[i].revents = 0;
break;
}
}
if (i == gdefaultnum) // 可以扩容
{
std::cout << "_events is full..." << std::endl;
}
}
void ServiceIO(int pos)
{
char buffer[1024];
ssize_t n = ::recv(_events[pos].fd, buffer, sizeof(buffer), 0);
if (n < 0)
{
LOG(WARNING, "recv error...");
::close(_events[pos].fd);
_events[pos].fd = defaltval;
_events[pos].events = -1;
_events[pos].revents = 0;
}
else if (n == 0)
{
LOG(INFO, "client quit...");
::close(_events[pos].fd);
_events[pos].fd = defaltval;
_events[pos].events = -1;
_events[pos].revents = 0;
}
else
{
buffer[n] = 0;
std::cout << "client say#:" << buffer << std::endl;
std::string echo = "Server echo#";
echo += buffer;
n = ::send(_events[pos].fd, echo.c_str(), echo.size(), 0);
if (n < 0)
{
LOG(WARNING, "send error...");
::close(_events[pos].fd);
_events[pos].fd = defaltval;
_events[pos].events = -1;
_events[pos].revents = 0;
}
}
}
void HanderEvent()
{
for (int i = 0; i < gdefaultnum; i++) // 遍历数组,判断哪个文件描述符就绪了
{
if (_events[i].fd == defaltval)
continue;
if (_events[i].revents & POLLIN) // 说明这个文件描述符 读条件就绪了
{
uint16_t port = _events[i].fd;
short revents = _events[i].revents;
// 有新连接到来
if (port == _listensock->Getsockfd()) // 说明是listen的文件描述符就绪了
{
AcceptListen();
}
else // 说明是普通的文件描述符就绪了
{
ServiceIO(i);
}
}
}
}
void Start()
{
while (true)
{
int n = poll(_events, gdefaultnum, -1);
if (n > 0)
{
LOG(INFO, "have Event Happen...");
HanderEvent();
}
else if (n == 0) // 超时
{
LOG(INFO, "time out...");
continue;
}
else // 出错了
{
LOG(WARNING, "poll error...");
continue;
}
}
}
~PollServer()
{
}
private:
uint16_t _port;
std::unique_ptr<Socket> _listensock;
struct pollfd _events[gdefaultnum];
};