本章代码Gitee地址:PollServer
文章目录
- 1. poll
- 2. poll_server
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;elsebreak;}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.5sfor( ; ; ){//不可直接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;}}}//Debugvoid 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
不设上限,但是操作系统有上限,而且操作系统是要在底层遍历的