【Linux】从零开始使用多路转接IO --- poll

在这里插入图片描述

碌碌无为,则余生太长;
欲有所为,则人生苦短。
--- 中岛敦 《山月记》---

从零开始使用多路转接IO

  • 1 前言
  • 1 poll接口介绍
  • 3 代码编写
  • 4 总结

1 前言

上一篇文章我们学习了多路转接中的Select,其操作很简单,但有一些缺陷:

  1. 每次调用 select,都需要手动设置 fd 集合, 从接口使用角度来说也非常不便。
  2. 每次调用 select, 都需要把 fd 集合从用户态拷贝到内核态, 这个开销在 fd 很多时会很大。这个是多路转接IO无法避免的问题!
  3. 同时每次调用 select 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时很大。
  4. select 支持的文件描述符数量太小!虽然操作系统中文件描述符也有限制,但是这是操作系统的缺陷。同样select也是缺点

而poll方案可以解决其中的两个缺点:

  • select支持的文件描述符少,poll理论上可以支持无限个文件描述符。
  • select每次调用接口都需要手动设置fd集合,poll不需要!

那么接下来我们就来看poll是怎样实现的。

1 poll接口介绍

首先poll的作用与select一模一样:等待多个文件描述符!只负责等待!

我们来看看poll接口:

OLL(2)                                                                   Linux Programmer's Manual                                                                   POLL(2)

NAME
       poll, ppoll - wait for some event on a file descriptor

SYNOPSIS
       #include <poll.h>

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

       #define _GNU_SOURCE         /* See feature_test_macros(7) */
       #include <signal.h>
       #include <poll.h>

       int ppoll(struct pollfd *fds, nfds_t nfds,
               const struct timespec *tmo_p, const sigset_t *sigmask);

poll接口中只有三个参数:

  1. struct pollfd *fds:这时一个文件描述符数组,其中每个元素是一个结构体,其中包含文件描述符,需要处理的事件类型。
  2. nfds_t nfds:表示文件描述符的数量!
  3. timeout:输入性参数,这里直接采用的是毫秒,不使用结构体!等于0时是非阻塞IO,等于-1时是阻塞IO!
  4. 返回值表示是否成功:大于0 即有n个就绪了;等于0表示超时了;小于0就是poll出错了!

我们来看看struct pollfd内部是怎么样的 :

struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };

我们对比一下select,select需要传入三个事件集,输入输出性参数,每次都会发生改变!所以才需要每次调用都要进行初始化。而poll使用一个结构体,对于这个文件描述符有两种事件:requested events 与 returned events!输入输出并不互相干扰!那么就解决了select需要不断初始化的问题。

那么事件类型有哪些呢?

宏定义描述
POLLIN普通或优先级带数据可读
POLLRDNORM同POLLIN
POLLRDBAND数据可读(优先级带数据)
POLLPRI高优先级数据可读
POLLOUT普通数据可写
POLLWRNORM同POLLOUT
POLLWRBAND数据可写(优先级带数据)
POLLERR发生错误
POLLHUP挂起,对方关闭连接
POLLNVAL描述字不是一个打开的文件

这些都是宏定义,short events;是一个16位位图,可以通过宏定义进行匹配设置!我们想要查看哪些事件,或者有哪些事件就绪了,就都可以通过位运算进行判断就可以了!

通过结构体的两个位图:

  1. 用户就可以告诉内核需要帮我们对fd的哪些事件进行等待了
  2. 内核也可以通过位图告诉用户fd的哪些事件就绪了!

3 代码编写

我们仅仅需要对select的代码做出一些修改即可:

首先,poll需要一个struct pollfd数组,这里储存需要处理的fd。初始化事遍历进行将对应fd设置为-1,事件设置为0,将listen套接字加入就可以:

	void Initserver()
    {
        // 对数组进行初始化
        for (int i = 0; i < gnum; i++)
        {
            fd_array[i].fd = gdefault;
            fd_array[i].events = 0;
            fd_array[i].revents = 0;
        }
        // 加入监听套接字
        fd_array[0].fd = _listensock->GetSockfd();
        fd_array[0].events = POLLIN;
    }
    //...
    // poll
    struct pollfd fd_array[gnum];

然后对Loop函数进行修改,我们不在需要对数据遍历更新rfds了,这样代码看起来就整洁了许多!

void Loop()
    {
        // 进入服务
        while (true)
        {
            // 创建timeout
            int timeout = 1000;
            // 进行select
            int n = ::poll(fd_array, gnum, timeout);
            switch (n)
            {
            case 0:
                // 超时
                LOG(DEBUG, "timeout \n");
                break;
            case -1:
                // 出错了
                LOG(ERROR, "select error\n");
                break;
            default:
                // 正常
                LOG(INFO, "have event ready: n = %d\n", n);
                // 处理事件
                HandlerEvent();
                PrintDebug();
                break;
            }
        }
    }

接下来就是HandlerEvent函数,进行判断的策略依然是遍历,这里只关心读事件:

void HandlerEvent()
    {
        // 遍历fd_array判断是否有就绪的新事件
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i].fd == gdefault)
                continue;
            // 如果有新事件
            if (fd_array[i].revents & POLLIN)
            {
                // 进行判断是scokfd 还是普通fd
                if (fd_array[i].fd == _listensock->GetSockfd())
                {
                    Accepter();
                }
                // 普通fd 进行正常读写
                else
                {
                    HandlerIO(fd_array[i]);
                }
            }
        }
    }

然后就是对于普通套接字和监听套接字的处理,针对数组进行稍微修改即可:

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());
            // 将新获取的fd加入到数组中
            LOG(INFO, "get new fd :%d\n", sockfd);
            bool flag = false;
            for (int i = 0; i < gnum; i++)
            {
                if (fd_array[i].fd == gdefault)
                {
                    flag = true;
                    fd_array[i].fd = sockfd;
                    fd_array[i].events = POLLIN;
                    break;
                }
                else
                    continue;
            }
            if (flag == false)
            {
                LOG(WARNING, "fd_array have fill!\n");
                return;
                // 可以进行扩容
            }
        }
    }
    void HandlerIO(struct pollfd &sp)
    {
        char buffer[1024];
        int n = ::recv(sp.fd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            // 读取到了数据
            buffer[n] = 0;
            std::string echo_str = "[client say]#";
            echo_str += buffer;
            std::cout << echo_str << std::endl;
            // 返回一个报文
            std::string content = "<html><body><h1>hello bite</h1></body></html>";
            std::string ret_str = "HTTP/1.0 200 OK\r\n";
            ret_str += "Content-Type: text/html\r\n";
            ret_str += "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";
            ret_str += content;
            // echo_str += buffer;
            ::send(sp.fd, ret_str.c_str(), ret_str.size(), 0); // 临时方案
        }
        else if (n == 0)
        {
            // 此时fd退出了
            LOG(INFO, "fd:%d quit!\n", sp.fd);
            ::close(sp.fd);
            sp.fd = gdefault;
            sp.events = 0;
            sp.revents = 0;
        }
        else
        {
            LOG(ERROR, "recv error! errno:%d\n", errno);
            ::close(sp.fd);
            sp.fd = gdefault;
        }
    }

来看效果:
在这里插入图片描述
很好的实现了我们的需求!代码也比select更加的简单了!

4 总结

Poll的底层其实也是遍历,对我们传入的数据进行遍历,这样的效率其实比select并不能高出太多!也就是说poll依然有这样的缺点:

  1. 每次调用 select, 都需要把 fd 集合从用户态拷贝到内核态, 这个开销在 fd 很多时会很大。这个是多路转接IO无法避免的问题!
  2. 同时每次调用 select 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时很大。

这样poll 的处境就很尴尬,没有select资历早,适配性不如select。性能又比不过epoll!
下一篇文章我们来学习epoll!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/907635.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux网络编程自定义协议和多进程多线程并发

1.三次握手及后面过程 计算机A是客户端, B是服务端 1.1三次握手&#xff1a; 1客户端给服务端SYN报文 2服务端返回SYNACK报文 3客户端返回ACK报文 客户端发完ACK后加入到服务端的维护队列中&#xff0c;accept()调用后就能和客户端建立连接&#xff0c;然后建立通讯 1.2关闭…

[CARLA系列--01]CARLA 0.9.15 在Windows下的安装教程(一)

Carla是一款开源的自动驾驶仿真器&#xff0c;它基本可以用来帮助训练自动驾驶的所有模块&#xff0c;包括感知系统&#xff0c;Localization, 规划系统等等.Carla这个产品目前已经更新到了最新的0.9.15版本,目前遇到好多人在windows系统上如何安装可编辑版的Carla遇到了好多问…

【Qt聊天室客户端】用户信息界面设置功能实现

1. 按钮禁用关系梳理 基本逻辑梳理 用户界面-申请好友按钮 只有当前用户不是你的好友时&#xff0c;该按钮才可以使用&#xff0c;否则是禁用状态 用户界面-发送消息与删除好友 当前用户是你的好友时&#xff0c;按钮才可以使用&#xff0c;否则这两个按钮禁用区分是否是你好…

一张图简单讲述Mamba的演进过程

这张图表提供了 RNN&#xff08;1986&#xff09;、LSTM&#xff08;1997&#xff09;、Transformer&#xff08;2017&#xff09;和 Mamba&#xff08;2024&#xff09;四种不同的神经网络架构在训练阶段、测试阶段和额外问题方面的对比。可以看出&#xff0c;Mamba 作为一种最…

redis v6.0.16 安装 基于Ubuntu 22.04

redis安装 基于Ubuntu 22.04 本文演示如何在ubuntu22.04下&#xff0c;安装redis v6.0.16&#xff0c;并配置测试远程访问。 Step1 更新环境 sudo apt updateStep2 安装redis sudo apt install redis-server -yStep3 启动 sudo systemctl restart redissudo systemctl sta…

Postman:高效的API测试工具

在现代软件开发中&#xff0c;前后端分离的架构越来越普遍。前端开发者与后端开发者之间的协作需要一种高效的方式来测试和验证API接口。在这个背景下&#xff0c;Postman作为一款强大的API测试工具&#xff0c;受到了广泛的关注和使用。 今天将介绍什么是Postman、为什么要使用…

Vue指令:v-else、v-else-if

目录 1.语法&#xff1a; 2. 题目 3.页面展示 4.结构 1.语法&#xff1a; 1.作用&#xff1a;辅助v-if进行判断渲染 2.语法&#xff1a;v-else 、v-esle-if"表达式" 2. 题目 <!DOCTYPE html> <html lang"en"> <head><meta chars…

RANSAC(随机抽样一致性算法)

RANSAC&#xff08;随机抽样一致性算法&#xff09;是一种用于估计数学模型参数的迭代方法&#xff0c;尤其适用于包含大量异常值的数据。使用 RANSAC&#xff0c;我们可以找到一个最优的线性拟合&#xff0c;同时最大限度地减少对异常值的影响。接下来&#xff0c;我将给出一个…

群控系统服务端开发模式-应用开发-业务架构逻辑开发第一轮测试

整个系统的第一个层次已经开发完毕&#xff0c;已经有简单的中控&#xff0c;登录、退出、延迟登录时长、黑名单、数据层封装、验证层封装、RSA加解密、Redis等功能&#xff0c;还缺获取个人、角色按钮权限、角色菜单权限功能。角色按钮权限以及角色菜单权限等明后天开发&#…

react基础之reactHooks

文章目录 React Hooks 使用指南常用 Hooks使用规则 小结 React Hooks 使用指南 React Hooks 是 React 16.8 引入的一种新特性&#xff0c;允许在函数组件中使用状态和其他 React 特性&#xff0c;而无需编写类组件。以下是一些基础的 Hooks 及其使用规则。 常用 Hooks useSta…

桑基图在医学数据分析中的更复杂应用示例

桑基图&#xff08;Sankey Diagram&#xff09;能够有效地展示复杂的流动关系&#xff0c;特别适合用于医学数据分析中的多种转归和治疗路径的可视化。接下来&#xff0c;我们将构建一个稍微复杂的示例&#xff0c;展示不同疾病患者在治疗过程中的流动&#xff0c;以及他们的治…

Android15音频进阶之Cuttlefish搭建音频开发环境(九十二)

简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+…

koa + sequelize做距离计算(MySql篇)

1.核心思路 1.利用sequelize的fn方法调用MySql原生函数&#xff08;ST_Distance_Sphere 、POINT&#xff09; 2.通MOD过函数将查询到的距离除以1000&#xff0c;这样km就变成了米 &#xff0c;利用FOMAT函数将查询到的结果精确到两位小数 3.这里利用到了MySql的原生函数&…

【Oracle APEX开发小技巧10】CSS样式控制交互式报表列宽和自动换行效果

在实际开发中使用交互式报表可能会出现某些字段的列宽过长&#xff0c;某些字段的列宽只有缩到一角的情况&#xff0c;那么如何解决这种情况呢&#xff1f;有没有方法可以控制交互式报表的列宽呢&#xff1f;下面就来介绍一下解决方法&#xff1a; 页设置-页-CSS-内嵌 输入如下…

IO详解(BIO、NIO、实战案例、底层原理刨析)

文章目录 IO详解&#xff08;BIO、NIO、实战案例、底层原理刨析&#xff09;&#x1f30e; IO&#x1fa90; 同步、异步、阻塞、非阻塞⚡ BIO&#x1f47d; 简介&#x1f60e; 案例 &#x1f680; NIO✈️ 介绍&#x1f697; Buffer&#xff08;缓冲&#xff09;&#x1f6f8; …

#渗透测试#SRC漏洞挖掘# 信息收集-Shodan之搜索语法进阶

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

Python复习1:

一、数据类型 1.数字&#xff1a;int、float、bool 2.字符串&#xff1a;string 3.列表&#xff1a;list 4.集合&#xff1a;set 5.字典&#xff1a;dictionary 二、Test 1.print输出固定格式 num110 str1"hello world" #输出的固定格式 print("num1%d&…

【MyBatis源码】BoundSql分析

基础 BoundSql是对SQL语句及参数信息的封装&#xff0c;它是SqlSource解析后的结果。Executor组件并不是直接通过StaticSqlSource对象完成数据库操作的&#xff0c;而是与BoundSql交互。BoundSql是对Executor组件执行SQL信息的封装&#xff0c;具体实现代码如下&#xff1a; …

Python爬虫抓取三个网站上的英语每日一句

一、引言 大学英语学习需要巩固高中语法&#xff0c;补充四六级词汇&#xff0c;增加英语语感&#xff0c;提升英语的运用能力。学好英语有很多种方法&#xff0c;采用句子来突破英语语法、词汇、口语和听力的方法简单有效&#xff0c;值得提倡。李阳就是采用这种方法来教授英…

三相LCL并网逆变器—为什么采用LCL滤波器

1.为什么需要滤波器 当前并网逆变器大多采用脉冲宽度调制(PWM)技术&#xff0c;导致桥臂输出的电压中存在开关频率及倍数频率次的谐波电压&#xff0c;进而使得输出到电网的电流中含有谐波。从电网的角度来看&#xff0c;是不希望系统内含有高次谐波的&#xff0c;因为这会影响…