多路转接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一样每次调用前都清空,简化了编码

相关推荐
我是小bā吖6 分钟前
阿里云服务网格ASM实践
网络·阿里云·云计算·服务发现
小刘同学32122 分钟前
C++11 特性
c++·c11新特性
真的想上岸啊36 分钟前
学习C++、QT---18(C++ 记事本项目的stylesheet)
开发语言·c++·学习
m0_5522008240 分钟前
《UE5_C++多人TPS完整教程》学习笔记40 ——《P41 装备(武器)姿势(Equipped Pose)》
c++·游戏·ue5
吴free1 小时前
mac电脑wireshark快速实现http接口抓包
网络·测试工具·http·wireshark
CodeWithMe1 小时前
【Note】《深入理解Linux内核》 Chapter 15 :深入理解 Linux 页缓存
linux·spring·缓存
0wioiw01 小时前
Ubuntu基础(监控重启和查找程序)
linux·服务器·ubuntu
Tipriest_1 小时前
Ubuntu常用的软件格式deb, rpm, dmg, AppImage等打包及使用方法
linux·运维·ubuntu
艾希逐月1 小时前
TCP数据的发送和接收
服务器·网络·tcp/ip
丁劲犇1 小时前
用 Turbo Vision 2 为 Qt 6 控制台应用创建 TUI 字符 MainFrame
开发语言·c++·qt·tui·字符界面·curse