Linux高级IO——多路转接之poll

本文详细介绍了C++中的PollServer类,它基于poll函数实现了一个非阻塞的多路复用I/O服务器,解决了select函数的文件描述符上限和重置问题。PollServer通过`poll()`函数监控多个文件描述符的状态,提高网络编程效率。
摘要由CSDN通过智能技术生成

本章代码Gitee地址:PollServer

文章目录

1. poll

poll的作用和select一模一样,只负责等待

pollselect的基础之上解决了select的两个硬伤:

  1. select等待的fd有上限
  2. 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不设上限,但是操作系统有上限,而且操作系统是要在底层遍历的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加法器+

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值