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

Linux网络 | 多路转接Poll

        前言:本节内容主要讲述多路转接的Poll, 这是我们讲解的多路转接中的第二个解决方案。 Poll主要由select修改而来, 没有select和后面的Epoll重要, 但是友友们还是要认真学习哦!现在废话不多说,开始我们的学习吧!

        ps:本节内容之前最好看一下select的知识点哦!

目录

POLL 

Poll相较于Select的优点

Pollfd

POLL代码实现

准备文件

Poll_server.hpp

初始化

Start


POLL 

Poll相较于Select的优点

        其实, select是有一些缺点的。 这些缺点导致当select等待的就绪的文件描述符越来越多时, 其实效率不会一直提高下去, 是一定会有一个临界点的。 下面就是select的一些缺点:

  •         1.等待的fd是有上限的。(因为它是使用位图的方式将要关心的文件描述符交给内核。并且位图对应的类型也是固定的。所以select就势必等待的fd是有上限的)
  •         2.输入输出型参数比较多,数据拷贝的频率比较高.
  •         3. 输入输出型参数比较多,每一次都要对关心的fd事件进行重置.
  •         4、使用第三方数组管理用户的fd,用户层需要很多次遍历。内核中检测fd事件就绪,也要遍历.

         为了修正select的问题,就有了下一种解决方案: poll。

        poll只负责等待,在select的基础上,解决select的两个硬伤:等待fd有上限的问题和输入输出型参数比较多,每次都要对关心的fd进行事件重置的问题。首先返回值,大于零代表有几个文件描述符就绪,等于零代表超时了,小于零代表失败。

        第三个参数使用的timeout整形,单位是毫秒。设为1000,就是1秒timeout一次。

        重要的是第一个参数,是结构体。第二个参数是nfds_t。pollfd我们可以理解为一个数组,第二个参数可以理解为这个数组中一共有多少个元素。

Pollfd

        这个结构体的意思就是,当我们用户向内核交付的时候,就是关心fd的events这个事件。 返回的时候,内核就告诉用户,fd的revents这个事件就绪了。

        所以, pollfd将输入和输出事件进行了分离。所以,未来要关心多个文件描述符,就传过去多个包含文件描述符的pollfd对象的数组就可以了。以后只需要利用nfds遍历pollfd,检测哪些就绪。

        这里的short类型如何表示事件,其实就是位图的原理,利用比特位进行标记。对应比特位代表不同的事件,下面是对应事件的宏:

事件

描述可作为输入?可作为输出?
POLLIN数据(包括普通数据和优先数据)可读
POLLRDNORN普通数据可读

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

POLL代码实现

准备文件

Poll的文件类似于Select:

由于上一节已经介绍了相关文件, 所以本节不再赘述。 

Poll_server.hpp

先看整体类定义:

#pragma once
#include <iostream>
using namespace std;
#include "Log.hpp"
#include "Socket.hpp"
#include<poll.h>
#include <sys/time.h>
//以上,需要用到的头文件


int defaultport = 8080;  //设置默认端口号
static const int fd_num_max = 64; // 设置最大fd的个数默认值
int defaultfd = -1                // 辅助数组默认初始值
int non_event = 0;                //一开始没有事件的时候默认设置成零

class PollServer
{
public:
    PollServer
    {


    }
    ~PollServer()
    {
   
    }

    bool Init()
    {

    }

    //
    void Accepter()
    {

    }

    void Recver(int fd, int i)
    {

    }


    //
    void Dispatcher()
    {

    }
    //
    void Start()
    {

    }

    void PrintFd()
    {

    }

private:
    Socket listensock_;
    uint16_t port_;

    pollfd _event_fds[fd_num_max];   //这里可以使用vector

    // int fd_array[fd_num_max];
};

        一共有三个成员变量, 一个listensock_用来监听新连接。 一个port_设置端口号。 一个_event_fds就是我们关心的fd。是pollfd类型的。

初始化

        初始化, 先将pollfd数组里面都设置成默认值, 利用定义的全局变量defaultfd和non_event设置。然后启动服务器开始监听。

    PollServer(uint16_t port = defaultport)
    : port_(port)
    {
        // 初始化pollfd数组
        for (int i = 0; i < fd_num_max; i++)
        {
            _event_fds[i].fd = defaultfd;
            _event_fds[i].events = non_event;
            _event_fds[i].revents = non_event;

            // cout << "fd_array[" << i << "]" << " : " << fd_array[i] << endl;
        }
    }
    ~PollServer()
    {
        listensock_.Close();
    }

    bool Init()
    {
        listensock_.InitSocket();
        listensock_.Bind(port_);
        listensock_.Listen();
        return true;
    }

Start

        服务器运行的时候, 就是循环式的检查_event_fds数组里面有没有需要关心的fd事件就绪了。 如果有, 那么就进入Dispatcher进行事件派发。如下为Start:

    void Start()
    {
        _event_fds[0].fd = listensock_.Fd();
        _event_fds[0].events = POLLIN;//新连接到来,事件类型等于读事件就绪。 所以这里设置成POLLIN, 表示读事件就绪。
        int timeout = 3000;


        for (;;)
        {
            int n = poll(_event_fds, fd_num_max, timeout);  //第一个数组元素, 第二个数组元素个数
            switch (n)
            {
            case 0:
                cout << "time out, timeout: " << endl;
                break;
            case -1:
                cerr << "poll error" << endl;
                break;
            default:
                // 有时间就绪了,TOOD
                cout << "get a link!!!" << endl;
                Dispatcher(); //事件派发
                break;
            }
        }
    }

        事件派发就是因为有事件就绪, 但是我们并不能直接定位数组中哪个元素的fd就绪了, 所以就要循环检测。 如果检测的时候检测到fd的revents是POLLIN, 就是就绪了。 那么就可以根据不同情况分发不同事件了。 有两种情况:一种是fd和监听fd相同,说明来了新连接。 就要进入连接管理器。 另一种就是来信息了, 读取就行了。 

    void Dispatcher()
    {
        for (int i = 0; i < fd_num_max; i++)
        {   
            int fd = _event_fds[i].fd;  //得到合法文件描述符
            if (fd == defaultfd) continue;  //这个文件描述符不关心

            //然后根据rfds判断是否fd就绪
            if (_event_fds[i].revents & POLLIN)   //判断就绪
            {
                if (fd == listensock_.Fd())   //如果fd就是当前listensock_的fd, 说明有新连接岛链, 就链接。
                {
                    Accepter();    //链接管理器
                }
                else
                {
                    Recver(fd, i);  //读取管理器
                }
            }
        }
    }

        连接管理器进行的操作分两步。 第一步:连接。第二步:将fd封装成pollfd类型对象加入到_event_fds数组中。 下面就是这个流程:

    void Accepter()
    {
        string clientip;
        uint16_t clientport = 0;
        int sock = listensock_.Accept(&clientip, &clientport);   //此时不会阻塞在这里, 因为新连接到来了我们才accept, 而不是先accept等待新连接到来。
        if (sock < 0) return;
        lg(Info, "accept success, %s: %d, sock fd : %d", clientip.c_str(), clientport, sock);   //链接成功
        //链接成功之后, 就是添加新需要关心的fd进入数组中。
        int pos = 1;
        for (;pos < fd_num_max; pos++)
        {
            if (_event_fds[pos].fd != defaultfd) continue;
            else break;
        }
        if (pos == fd_num_max)   //如果这个条件成立, 就说明pos加到了fd_num_max,而不是遇到了defaultfd。 就说明满了, 就把监听sock关掉。 
        {
            lg(Waring, "server is full, close %d now!", sock);
            close(sock);        

            //这里可以扩容
        }
        else   //遇到了defaultfd, 没满
        {
            _event_fds[pos].fd = sock;    //将defaultfd的位置设置为新连接的fd。
            _event_fds[pos].events = POLLIN;
            _event_fds[pos].revents = non_event;

            PrintFd();
        }
    }

        读取管理器就是读取接收缓冲区里面的内容即可。

    void Recver(int fd, int i)
    {
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); 
        if (n > 0)
        {
            buffer[n] = 0;

            cout << "get a message: " << buffer << endl;
        }
        else if (n == 0)  //等待超时,
        {
            lg(Info, "client quit, me too, close fd is : %d", fd);
            close(fd);
            _event_fds[i].fd = defaultfd;
        }
        else
        {
            lg(Waring, "recv error: fd is : %d", fd);   
            close(fd);
            _event_fds[i].fd = defaultfd;
        }

    }

——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!


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

相关文章:

  • NO.18十六届蓝桥杯备战|循环嵌套|乘法表|斐波那契|质数|水仙花数|(C++)
  • 深度学习-114-大语言模型应用之提示词指南实例DeepSeek使用手册(三)
  • docker搭建redis-cluster
  • FPGA简介|结构、组成和应用
  • MYSQL批量UPDATE的两种方式
  • AI自动驾驶:2025有戏,Uber受益先于特斯拉
  • 在 Kubernetes (K8s) 环境中,备份 PostgreSQL 数据库
  • AI在网络安全中的应用:构建智能防护体系
  • 《Python高性能计算实战:从NumPy并行化到分布式Dask集群》
  • 案例-02.部门管理-查询
  • 2024-arXiv-Alpha2:使用深度强化学习发现逻辑公式化Alpha
  • 计算机网络原理试题二
  • 时间序列分析(四)——差分运算、延迟算子、AR(p)模型
  • 盲注技术获取数据库的表、列和具体数据
  • DeepSeek 助力 Vue 开发:打造丝滑的无限滚动(Infinite Scroll)
  • 基于Swift实现仿IOS闹钟
  • 3.3 企业级AI Agent工程实践:从API设计到高可用架构的全栈开发指南
  • ES 命令行查询
  • LeetCodehot100 力扣热题100 二叉树展开为链表
  • CMakeLists使用