多路转接poll服务器

目录

函数原型

poll服务器

对比select的优点


关于select的详解,可查看多路转接select服务器-CSDN博客

函数原型

cpp 复制代码
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

poll作为多路转接的实现方案,与select要解决的问题是一样的,同时它还要做到规避select的一些缺点

cpp 复制代码
struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events */
    short revents;    /* returned events */
};

这个数据结构中fd告诉了操作系统要关心fd上的事件,events是用户想关心的事件,有POLLIN,

POLLPRI,POLLOUT等等,这些都是宏

cpp 复制代码
#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相差不大,看代码可以理解

cpp 复制代码
#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;
};

主函数

cpp 复制代码
#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一样每次调用前都清空,简化了编码

相关推荐
我爱拉臭臭4 分钟前
分布式之CAP原则:理解分布式系统的核心设计哲学
linux·运维·服务器·分布式
小比卡丘7 分钟前
【C++初阶】第15课—模版进阶
android·java·c++
GOTXX1 小时前
掌握MySQL:基本查询指令与技巧
数据库·c++·mysql·全文检索·多线程·热榜
"_rainbow_"6 小时前
C++常用函数合集
开发语言·c++·算法
Wendy_robot7 小时前
力扣经典位运算
c++·算法·leetcode
蒲公英的孩子8 小时前
Linux下 REEF3D及DIVEMesh 源码编译安装及使用
linux·c++·分布式·开源软件
小跌—8 小时前
Linux:简单自定义shell
linux
半青年8 小时前
数据结构之哈希表的原理和应用:从理论到实践的全面解析
java·c语言·数据结构·c++·python·哈希算法
码农不惑8 小时前
sqlite3的API以及命令行
c++·sqlite