目录
函数原型
poll服务器
对比select的优点
关于select的详解,可查看多路转接select服务器-CSDN博客
函数原型
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll作为多路转接的实现方案,与select要解决的问题是一样的,同时它还要做到规避select的一些缺点
struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */
};
这个数据结构中fd告诉了操作系统要关心fd上的事件,events是用户想关心的事件,有POLLIN,
POLLPRI,POLLOUT等等,这些都是宏
#define POLLIN 0x001 /* There is data to read. */
#define POLLPRI 0x002 /* There is urgent data to read. */
#define POLLOUT 0x004 /* Writing now will not block. */
使用时,将events置为0,然后与POLLIN进行算数或运算,就表示告诉操作系统要关心fd的读事件,也可以关心同一个fd的多个事件,比如将events与POLLIN和POLLOUT都或一遍
看到这些宏定义,可以看出events也当成了位图来使用,每个比特位表示某种事件,值为1表示要关心这种事件,为0表示不关心
revents是操作系统返回的事件,表示fd上就绪了的事件,通过将revents与POLLIN进行算数与操作,就可知道写事件是否就绪
可以看出fds参数是一个指针,而后面的nfds参数实际上就是一个整型,这两个参数确定了一个pollfd的数组,pollfd包含了用户要关心哪些文件描述符的哪些事件的信息,还包含了操作系统告诉用户哪些事件就绪的信息。
timeout类似select,表示超时时间,以毫秒为单位
返回值与select一样,返回值为0表示超时返回;为-1表示有错误发生,并设置错误码errno;为正数表示在timeout时间内事件就绪的文件描述符个数
poll服务器
这里只给出poll_server的代码,其他文件的代码对理解poll不重要,只需要了解套接字的使用便可轻松看懂,若要查看其他文件的代码,详见rokobo/wsl_code - Gitee.com
poll服务器与select服务器有很强的相似性,有一个pollfd数组记录需要关心的文件描述符,此外大体思路与select相差不大,看代码可以理解
#include "socket.hpp"
#include "Log.hpp"
#include <poll.h>
#include <memory>
#include <cstring>
#include <cerrno>
#include <vector>
using namespace SocketModule;
using namespace LogModule;
class poll_server
{
public:poll_server():_listen_sock(std::make_shared<TcpSocket>()),_is_running(false),fds(NUM, {-1, 0, 0}){}void init(int port){_listen_sock->BuildTcpSocketMethod(port);fds[0].fd = _listen_sock->Fd();fds[0].events |= POLLIN;}void loop(){_is_running = true;int listenfd = _listen_sock->Fd();int timeout = 2000;while(_is_running){int ret = poll(fds.data(), fds.size(), timeout);if(ret == -1){LOG(LogLevel::ERROR) << "Error message: " << strerror(ret);continue;}else if(ret == 0){LOG(LogLevel::INFO) << "Time out\n";continue;}else{LOG(LogLevel::INFO) << "Dispatch begin\n";dispatcher();}} }void accepter(int fd){InetAddr client;auto client_sock = _listen_sock->Accepter(&client);if(client_sock == nullptr){LOG(LogLevel::ERROR) << "Accept error";return;}int client_fd = client_sock->Fd();if(client_fd < 0){LOG(LogLevel::ERROR) << "Client fd error";return;}//将client_fd加入到fds中int i=0;for(i=0;i<NUM;++i){if(fds[i].fd == -1){fds[i].fd = client_fd;fds[i].events |= POLLIN;LOG(LogLevel::INFO) << "Accept success: " << client_sock->Fd() << " " << client.Addr();break;}}if(i == NUM){//扩容LOG(LogLevel::ERROR) << "fds is full";fds.resize(NUM * 2, {-1, 0, 0}); return;}}void recver(int who){int fd = fds[who].fd;std::string buffer;auto client_sock = std::make_shared<TcpSocket>(fd);ssize_t ret = client_sock->Recv(&buffer);if(ret == -1){LOG(LogLevel::ERROR) << "Recv error" << strerror(errno);client_sock->Close();//将fd从fds中删除fds[who].fd = -1;fds[who].events = 0;fds[who].revents = 0;return;}else if(ret == 0){LOG(LogLevel::INFO) << "Client closed: " << client_sock->Fd();client_sock->Close();//将fd从fds中删除fds[who].fd = -1;fds[who].events = 0;fds[who].revents = 0;return;}else{LOG(LogLevel::INFO) << "Recv success: " << buffer;return;}}void dispatcher(){//找到所有合法的fd,分发for(int i=0;i<NUM;++i){if(fds[i].fd == -1)continue;if(fds[i].revents & POLLIN){//分发给处理连接的函数if(fds[i].fd == _listen_sock->Fd()){accepter(fds[i].fd);}//分发给处理IO的函数else{recver(i);}}}}void stop(){}
private:std::shared_ptr<TcpSocket> _listen_sock;std::vector<pollfd> fds;bool _is_running;
};
主函数
#include "poll_server.hpp"
#include <string>
int main(int argc, char* argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;return -1;}int port = std::stoi(argv[1]);if(port <= 0 || port > 65535){std::cerr << "Invalid port number" << std::endl;return -1;}poll_server s_svr;s_svr.init(port);s_svr.loop();return 0;
}
对比select的优点
poll解决了select关心的文件描述符数量有限的问题,可以向pollfd数组里添加随意添加元素,而且poll将输入参数与输出参数分离,events是输入参数,revents是输出参数,这样不需要像select一样每次调用前都清空,简化了编码