当前位置: 首页 > article >正文

Linux多路转接poll

Linux多路转接poll

1. poll()

poll() 结构包含了要监视的 event 和发生的 event ,接口使用比 select() 更方便。且 poll 并没有最大数量限制(但是数量过大后性能也是会下降)。

2. poll() 的工作原理

poll() 不再需要像 select() 那样自行设置文件描述符集合,它只需要用户在 pollfd 结构体中设置文件描述符及其关心的事件(events,在输出时,结构体内的 revents 作为函数调用后事件就绪的结果, 就 select()poll() 的不同而言, poll() 支持用户自定义关心某些事件,同时将事件就绪的结构用不同的变量保存起来。

poll() 会返回就绪文件描述符的数量,用户需要自行设置判断条件,遍历pollfd 结构体中的events,将其与自己关心的事件按位与&),然后执行 recv() 或其他函数读取或进行其他操作。

Poll1

3. 函数声明

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout)

struct pollfd *fds*fds 表示”数组“的起始地址。下面给出 struct pollfd 的具体内容。

nfds_t nfds:表示数组有效元素的个数。 nfds_t unsigned long typedef 出来的类型。

timeout:表示超时时间,以毫秒为单位,等于 0 表示非阻塞等待, -1 表示阻塞等待。

reval:返回值大于 0 ,返回事件就绪文件描述符的数量;返回值小于 0 表示出错;返回值等于 0 表示超时。

4. struct pollfd

struct pollfd
{
	int   fd;         /* 文件描述符 */
	short events;     /* 等待的事件集 */
	short revents;    /* 实际发生的事件集 */
};

events:表示调用 poll()希望检测的事件类型

revents :表示在 poll() 调用之后,实际发生的事件集poll() 会在返回时填充该字段,以指示文件描述符上发生了哪些事件。

常见的事件类型有:

事件描述是否可作为输入是否可作为输出
POLLIN数据(包括普通数据和优先数据)可读
POLLRDNRM普通数据可读
POLLRDBAND优先级带数据可读(Linux不支持)
POLLPRI高级优先数据可读,比如 TCP 带外数据
POLLOUT数据(包括普通数据和优先数据)可写
POLLWRNRM普通数据可写
POLLWRBAND优先级带数据可写
POLLRDHUPTCP 连接被对方关闭,或者对方关闭了写操作,它由 GNU 引入
POLLERR错误发生
POLLHUP挂起。比如管道的写端被关闭后,读端描述符上将收到 POLLHUP 事件
POLLNVAL文件描述符没有打开

5. poll() 的使用

#pragma once

#include <iostream>
#include <poll.h>
#include "Socket.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace socket_ns;

class PollServer
{
    const static int gnum = sizeof(fd_set) * 8;
    const static int gdefaultfd = -1;

public:
    PollServer(uint16_t port) : _port(port), _listensock(std::make_unique<TcpSocket>())
    {
        _listensock->BuildListenSocket(_port);
    }
    void InitServer()
    {
        for (int i = 0; i < gnum; i++)
        {
            fd_events[i].fd = gdefaultfd;
            fd_events[i].events = 0;
            fd_events[i].revents = 0;

        }
        fd_events[0].fd = _listensock->Sockfd(); 
        fd_events[0].events = POLLIN;
    }
    void Accepter()
    {
        InetAddr addr;
        int sockfd = _listensock->Accepter(&addr);
        if (sockfd > 0)
        {
            LOG(DEBUG, "get a new link, client info %s:%d\n", addr.Ip().c_str(), addr.Port());
       
            bool flag = false;
            for (int pos = 1; pos < gnum; pos++)
            {
                if (fd_events[pos].fd == gdefaultfd)
                {
                    flag = true;
                    fd_events[pos].fd = sockfd;
                    fd_events[pos].events = POLLIN;
                    LOG(INFO, "add %d to fd_array success!\n", sockfd);
                    break;
                }
            }
            if (!flag)
            {
                LOG(WARNING, "Server Is Full!\n");
                ::close(sockfd);
                // 扩容
                // 添加
            }
        }
    }
    // 处理普通的fd就绪的
    void HandlerIO(int i)
    {
        char buffer[1024];
        ssize_t n = ::recv(fd_events[i].fd, buffer, sizeof(buffer) - 1, 0); 
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client say# " << buffer << std::endl;
            std::string content = "<html><body><h1>hello bite</h1></body></html>";
            std::string echo_str = "HTTP/1.0 200 OK\r\n";
            echo_str += "Content-Type: text/html\r\n";
            echo_str += "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";
            echo_str += content;
            ::send(fd_events[i].fd, echo_str.c_str(), echo_str.size(), 0); 
        }
        else if (n == 0)
        {
            LOG(INFO, "client quit...\n");
            ::close(fd_events[i].fd);
            fd_events[i].fd = gdefaultfd;
            fd_events[i].events = 0;
            fd_events[i].revents = 0;
        }
        else
        {
            LOG(ERROR, "recv error\n");
            ::close(fd_events[i].fd);
            fd_events[i].fd = gdefaultfd;
            fd_events[i].events = 0;
            fd_events[i].revents = 0;
        }
    }
    // 一定会存在大量的fd就绪,可能是普通sockfd,也可能是listensockfd
    void HandlerEvent()
    {
        // 事件派发
        for (int i = 0; i < gnum; i++)
        {
            if (fd_events[i].fd == gdefaultfd)
                continue;
            // fd一定是合法的fd
            // 合法的fd不一定就绪, 判断fd是否就绪
            if (fd_events[i].revents & POLLIN)
            {
                // 读事件就绪
                // 1. listensockfd 2. normal sockfd就绪
                if (_listensock->Sockfd() == fd_events[i].fd)
                {
                    Accepter();
                }
                else
                {
                    HandlerIO(i);
                }
            }
        }
    }
    void Loop()
    {
        int timeout = -1;
        while (true)
        {
            int n = ::poll(fd_events, gnum, timeout); 
            switch (n)
            {
            case 0:
                LOG(DEBUG, "time out\n");
                break;
            case -1:
                LOG(ERROR, "poll error\n");
                break;
            default:
                LOG(INFO, "haved event ready, n : %d\n", n); 
                HandlerEvent();
                PrintDebug();
                break;
            }
        }
    }

    void PrintDebug()
    {
        std::cout << "fd list: ";
        for (int i = 0; i < gnum; i++)
        {
            if (fd_events[i].fd == gdefaultfd)
                continue;
            std::cout << fd_events[i].fd << " ";
        }
        std::cout << "\n";
    }
    ~PollServer() {}

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensock;

    struct pollfd fd_events[gnum];
};

6. poll() 的缺点

poll() 返回后,需要轮询pollfd 来获取就绪的文件描述符。

每次调用 poll() 都要把大量的 pollfd 结构从用户态拷贝到内核态。

poll()的底层,也需要操作系统遍历所有的文件描述符,来获取就绪的文件描述符和它的事件,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。


http://www.kler.cn/a/525677.html

相关文章:

  • 最新-CentOS 7 基于1 Panel面板安装 JumpServer 堡垒机
  • Linux文件原生操作
  • 16.Word:石油化工设备技术❗【28】
  • Node.js 的底层原理
  • HTML<kbd>标签
  • Cursor 帮你写一个小程序
  • 解读Linux 6.x版本内核的sys目录作用
  • SQL注入漏洞之错误类型注入 爆破表 字段 列名称 以及mysql版本 以及Limit使用方式解释 以及靶场相关联系
  • 「全网最细 + 实战源码案例」设计模式——桥接模式
  • 7.抽象工厂(Abstract Factory)
  • P1002 [NOIP2002 普及组] 过河卒
  • Leetcode 131 分割回文串(纯DFS)
  • EtherCAT主站IGH-- 23 -- IGH之fsm_slave.h/c文件解析
  • 在Ubuntu下编译VLC
  • 【AI非常道】二零二五年一月(二),AI非常道
  • 正态分布与柯西分布的线性组合与副本随机变量同分布
  • Spring Boot + Facade Pattern : 通过统一接口简化多模块业务
  • 【C语言】函数递归
  • 【LeetCode: 958. 二叉树的完全性检验 + bfs + 二叉树】
  • 【自学笔记】MySQL的重点知识点-持续更新
  • 《LLM大语言模型+RAG实战+Langchain+ChatGLM-4+Transformer》
  • 【C++动态规划 离散化】1626. 无矛盾的最佳球队|2027
  • 受击反馈HitReact、死亡效果Death Dissolve、Floating伤害值Text(末尾附 客户端RPC )
  • Git 版本控制:基础介绍与常用操作
  • 当代搜索引擎技术介绍性能优化
  • MySQL UNION 操作详解