本章代码Gitee地址:PollServer
1. poll
poll
的作用和select
一模一样,只负责等待
poll
在select
的基础之上解决了select
的两个硬伤:
select
等待的fd
有上限select
输入输出参数较多
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
-
struct pollfd *fds
:struct pollfd { int fd; //关心的文件描述符 short events; //关心的事件 用户->内核 (位图) short revents; //返回关心的事件 内核->用户 (位图) };
事件(位图值) 描述 POLLIN 数据可读 POLLOUT 数据可写 POLLERR 发生错误 POLLHUP 挂起 POLLNVAL 文件描述符无效 POLLPRI 高优先级数据(例如:TCP紧急指针) -
nfds_t nfds
:等待多个文件描述符当中值最大的+1,即maxfd+1
-
int timeout
:等待时间,单位是毫秒;如果设为-1,表述阻塞等待 -
返回值:
> 0
:有n个fd已经就绪
== 0
:等待超时,没有错误,没有文件描述符就绪
< 0
:等待出错
2. poll_server
#pragma once
#include<iostream>
#include<string>
#include<sys/time.h>
#include<poll.h>
#include"Socket.hpp"
#include"Log.hpp"
static const uint16_t defaultport = 8089;
static const int fd_max = 64;
const int defaultfd = -1;
const int non_events = 0;
class PollServer
{
public:
PollServer(uint16_t port = defaultport)
:_port(port)
{
for(int i = 0; i < fd_max; i++)
{
_events_fd[i].fd = defaultfd;
_events_fd[i].events = non_events;
_events_fd[i].revents = non_events;
}
}
bool Init()
{
_listensock.Socket();
_listensock.Bind(_port);
_listensock.Listen();
return true;
}
void Accepter()
{
std::string clientip;
uint16_t clientport;
int sock = _listensock.Accept(&clientip, &clientport); // 此时并不会阻塞, 因为已经上层通知事件已经就绪
if (sock < 0)
return;
log(Info, "accept success, %s:%d, sockfd:%d", clientip.c_str(), clientport, sock);
int pos = 1;
for (; pos < fd_max; pos++)
{
if (_events_fd[pos].fd != defaultfd)
continue;
else
break;
}
if (pos == fd_max) // 文件描述符满了(位图满了)
{
log(Warning, "server is full, close %d", sock);
//可以扩容
close(sock);
}
else
{
_events_fd[pos].fd = sock;
_events_fd[pos].events = POLLIN;
_events_fd[pos].revents = non_events;
PrintFd(); // Debug
}
}
void Recver(int fd, int pos)
{
// 读事件就绪
char buffer[1024];
ssize_t n = read(fd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = 0;
std::cout << "get a message: " << buffer << std::endl;
}
else if (n == 0)
{
log(Info, "client quit, me too, close fd:%d", fd);
close(fd);
_events_fd[pos].fd = defaultfd; // 从select中移除
}
else
{
log(Warning, " read error, close fd:%d", fd);
close(fd);
_events_fd[pos].fd = defaultfd;
}
}
void Dispatcher()
{
for (int i = 0; i < fd_max; i++)
{
int fd = _events_fd[i].fd;
if(fd == defaultfd) continue;
if (_events_fd[i].revents & POLLIN)
{
if(fd == _listensock.Getfd()) //是监听套接字且已经就绪 获取新链接
{
Accepter();
}
else
{
Recver(fd, i);
}
//其他的事件...
}
}
}
void Start()
{
int listensock = _listensock.Getfd();
_events_fd[0].fd = listensock;
_events_fd[0].events = POLLIN;
int timeOut = 1500; //1.5s
for( ; ; )
{
//不可直接accept, accept本质是检测并获取listensock上面的事件
struct timeval timeout = {2, 0}; //输入输出型参数, 需要周期性重复设置
//int s = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout);
int p = poll(_events_fd, fd_max, timeOut);
switch (p)
{
case 0:
//等待超时
std::cout << "time out..." << std::endl;
break;
case -1:
//等待出错
std::cerr << "poll error" << std::endl;
break;
default:
//有事件就绪
std::cout << "get a link" << std::endl; //如果上层一直不处理,底层则一直触发
Dispatcher();
break;
}
}
}
//Debug
void PrintFd()
{
std::cout << "online fd list: ";
for(int i = 0; i < fd_max; i++)
{
if(_events_fd[i].fd != defaultfd)
std::cout << _events_fd[i].fd << " ";
}
std::cout << std::endl;
}
~PollServer()
{}
private:
MySocket _listensock;
uint16_t _port;
struct pollfd _events_fd[fd_max];
// int _rfd_array[fd_max];
};
虽然
poll
解决了select
文件描述符有上限和每次都要对文件描述符进行重置的问题,但是这些都交给了操作系统,虽然poll
不设上限,但是操作系统有上限,而且操作系统是要在底层遍历的