多路转接之poll(接口介绍,struct pollfd介绍,实现原理,实现非阻塞网络通信代码)

8 篇文章 0 订阅

目录

poll

引入

介绍

函数原型

fds

struct pollfd

特点

nfds

timeout

取值

返回值 

原理

如何实现关注多个fd?

如何确定哪个fd上有事件就绪?

如何区分事件类型?

判断某事件是否就绪的方法

代码

示例

总结

为什么说它解决了fd上限问题?

缺点


poll

引入

我们前面介绍了select -- 多路转接之select(fd_set介绍,参数详细介绍,优缺点),实现非阻塞式网络通信(代码+思路)-CSDN博客

  • 随着文件数量增多(有新客户端来连接),文件上事件就绪的概率也就增大了
  • 而等待就绪->通知用户层有事件就绪的过程涉及到多次遍历和拷贝,就绪次数多了,遍历和拷贝就多了
  • 所以,会慢慢让效率变低

为了解决这些问题,出现了新的多路转接的方案 -- poll

 

介绍

是一种用于多路复用 I/O 事件的系统调用,它允许程序监视多个文件描述符,并等待其中的某些事件发生

  • 它和select一样,只负责等待
  • 它主要解决了select的两个弊端 -- fd有上限 和 参数重置问题

 

函数原型

fds

和select中三个位图的作用相同 -- 用于用户和内核之间的信息交流,但原理不同

struct pollfd

  • 用户给内核传入要关注文件的fd和要关注的事件类型 -- 使用了pollfd中的fd,events字段
  • 内核给用户返回该文件上的哪个事件已就绪 -- 使用fd,revents字段
特点

将输入和输出事件分离

  • 而不是像select一样,用户和内核使用的是同一个结构(位图)
  • 这里使用了两个变量给两方分别使用

nfds

fds中的元素个数

  • 相当于需要关注的fd个数

timeout

等待事件的超时时间

  • 毫秒为单位,1000ms=1s
取值
  • >0 -- 超时时间
  • =0 -- 非阻塞,函数会立即返回
  • -1

返回值 

和select作用相同

  • >0 -- 就绪的fd个数
  • =0 -- 超时,没有事件就绪
  • <0 -- 等待的文件中有已经关闭的文件

原理

如何实现关注多个fd?

这里的fds参数是一个结构体类型的指针

  • 所以可以传入结构体数组 / 指向堆上空间的指针,后续可以动态扩容

所以,我们可以添加多个pollfd结构到数组中

如何确定哪个fd上有事件就绪?

遍历数组,检查每个结构的revents字段状态

如何区分事件类型?

将events/revents字段看作16个bit位,1个bit位可以对应一个事件类型

  • 和标志位一样
判断某事件是否就绪的方法
  • 读事件为例:
  • (某个指定pollfd结构中的revents & POLLIN) 是否等于1,等于1说明该事件已经就绪

代码

我们可以直接改select代码为poll版本,很简单:

  • 不需要在循环内重复设置参数
  • 把那些删掉后,修改调用select为poll即可,最多就创建一个pollfd结构体
#include "Log.hpp"
#include "socket.hpp"
#include <poll.h>

static const int def_port = 8080;
static const int def_max_num = 1024;
static const int def_data = -1;
static const int no_data = 0;

class poll_server
{
public:
    poll_server()
    {
        for (int i = 0; i < def_max_num; ++i)
        {
            fds_[i].fd = def_data;
            fds_[i].events = no_data;
            fds_[i].revents = no_data;
        }
    }
    ~poll_server()
    {
        listen_socket_.Close();
    }
    void start()
    {
        listen_socket_.Socket();
        listen_socket_.Bind(def_port);
        listen_socket_.Listen();
        int timeout = 1;

        // 固定数组第一项是监听套接字
        struct pollfd tl = {listen_socket_.get_fd(), POLLIN, no_data};
        fds_[0] = tl;

        while (true)
        {
            int ret = poll(fds_, def_max_num, timeout);
            if (ret > 0) // 有事件就绪
            {
                handle();
            }
            else if (ret == 0) // 超时
            {
                continue;
            }
            else
            {
                perror("poll");
                break;
            }
        }
    }

private:
    void receiver(int fd, int i)
    {
        char in_buff[1024];
        int n = read(fd, in_buff, sizeof(in_buff) - 1);
        if (n > 0)
        {
            in_buff[n - 1] = 0;
            std::cout << "get message: " << in_buff << std::endl;
        }
        else if (n == 0) // 客户端关闭连接
        {
            close(fd);
            lg(DEBUG, "%d quit", fd);
            fds_[i].fd = -1; // 重置该位置
            fds_[i].events = no_data;
            fds_[i].revents = no_data;
        }
        else
        {
            lg(ERROR, "fd: %d ,read error");
        }
    }
    void accepter()
    {
        std::string clientip;
        uint16_t clientport;
        int sock = listen_socket_.Accept(clientip, clientport);
        if (sock == -1)
        {
            return;
        }
        else // 把新fd加入数组
        {
            struct pollfd t;
            int pos = 1000; //1s
            for (; pos < def_max_num; ++pos)
            {
                if (fds_[pos].fd == def_data) // 找到空位,但不能直接添加
                {
                    break;
                }
            }
            if (pos != def_max_num)
            {
                t.fd = sock;
            }
            else // 满了
            {
                //这里可以扩容
                lg(WARNING, "server is full,close %d now", sock);
                close(sock);
            }
            t.events = POLLIN;
            t.revents = no_data;
            fds_[pos] = t;
        }
    }
    void handle()
    {
        for (int i = 0; i < def_max_num; ++i) // 遍历数组
        {
            int fd = fds_[i].fd;
            if (fd != def_data) // 有效fd
            {
                if (fds_[i].revents & POLLIN) // 有事件就绪
                {
                    if (fd == listen_socket_.get_fd()) // 获取新连接
                    {
                        accepter();
                    }
                    else // 读事件
                    {
                        receiver(fd, i);
                    }
                }
            }
        }
    }

private:
    MY_SOCKET listen_socket_;
    struct pollfd fds_[def_max_num];
};

示例

总结

为什么说它解决了fd上限问题?

因为poll里的数组大小由用户决定,而fd_set的大小已经被系统定死了,无法改变

缺点

但是,poll依然没有解决多次遍历的问题

  • 用户层需要查看数组中每个结构的revents,看哪些文件的哪些事件就绪了
  • 内核也需要遍历数组,查看需要关注哪些文件的哪些事件,查看是否有文件就绪

遍历成本由文件个数决定

  • 虽然poll没有限制,但一旦数量过多,会影响遍历效率

所以,为了解决这个问题,提出了新的方案 -- epoll

它是目前效率最高的多路转接方案

  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值