【Linux】多路转接 -- poll函数

news2024/11/26 12:42:06

文章目录

  • 1. poll函数原型
  • 2. poll服务器
  • 3. poll的优点和确定

1. poll函数原型

poll函数和与我上一篇文章介绍的select函数一样,都是系统提供的多路转接接口,允许进程或线程在同一时间监听多个文件描述符。

本篇文章的一部分内容与上一篇介绍select函数的文章练习很大。

poll函数原型如下:

在这里插入图片描述
参数说明:

  • fds:一个poll函数监视的结构列表,每一个元素包含三部分内容:文件描述符、监视的事件集合、就绪的事件集合。
  • nfds:表示fds数组的长度。
  • timeout:表示poll函数的超时时间,单位是毫秒(ms)。

参数timeout的取值:

  • -1:poll调用后进行阻塞等待,直到被监视的某个文件描述符上的某个事件就绪。
  • 0:poll调用之后进行非阻塞等待,无论被监视的文件描述符上的事件是否就绪,poll检测之后都会立即返回。
  • 特定的时间值:poll调用后在指定的时间内进行阻塞等待,如果被监视的文件描述符上一直没有事件就绪,则在该时间后poll进行超时返回。

返回值说明:

  • 如果函数调用成功,则返回所有事件就绪的文件描述符个数。
  • 如果timeout时间耗尽,返回0。
  • 如果函数调用失败,返回-1,同时错误码会被设置。

poll调用失败时,错误码可能被设置为:

  • EFAULT:fds数组不包含在调用程序的地址空间内。
  • EINTR:此调用被信号所中断。
  • EINVAL:nfds值超过RLIMIT_NOFILE值
  • ENOMEM:核心内存不足

struct pollfd结构

struct pollfd结构当中包含三个成员:

  • fd:特定的文件描述符,若设置为负值则忽略events字段并且revents字段返回0。
  • events:需要监视文件描述符上的哪些事件。
  • revents:poll函数返回时告知用户该文件描述符上的哪些事件已经就绪。

在这里插入图片描述
events和revents的取值如下:
在这里插入图片描述

events和revents的取值都是以宏的方式进行定义的,它们的二进制序列当中有且只有一个比特位是1,且为1的比特位是各不相同的。

在这里插入图片描述

  • 在调用poll函数之前,可以通过“或”运算符将要检测的事件添加到events成员当中。
  • 在poll函数返回后,可以通过“与”运算符检测revents成员中是否包含特定事件,以得知对应文件描述符的特定事件是否就绪。

2. poll服务器

poll的工作流程和select是类似的,这里我们实现一个简单的poll服务器,该服务器也是读取客户端发来的数据并进行打印。

  • PollServer类

PollServer类当中也只需要包含监听套接字和端口号两个成员变量,在poll服务器绑定时直接将IP地址设置为INADDR_ANY即可。

  • 在构造PollServer对象时,需要指明服务器的端口号,当然也可以在初始化poll服务器的时候指明。
  • 在初始化poll服务器的时候调用Socket类中的函数,依此进行套接字的创建、绑定和监听即可,这里的Socket类和之前实现的一模一样。
  • 在析构函数中可以选择调用close函数将监听套接字进行关闭,但实际也可以不进行该动作,因为服务器运行后一般是不退出的。
#pragma once

#include "Socket.hpp"
#include <poll.h>

#define BACK_LOG 5

class PollServer
{
public:
    PollServer(int port)
        : _port(port)
    {}

    void InitPollServer()
    {
        _listen_sock = Socket::SocketCreate();
        Socket::SocketBind(_listen_sock, _port);
        Socket::SocketListen(_listen_sock, BACK_LOG);
    }

    ~PollServer()
    {
        if (_listen_sock >= 0) close(_listen_sock);
    }

private:
    int _listen_sock; // 监听套接字
    int _port; // 端口号
};

运行服务器

服务器初始化完毕之后就可以开始运行了,而poll服务器要做的就是不断调用poll函数,当事件就绪时对应执行某种动作即可。

  • 首先,在poll服务器开始死循环调用poll函数之前,需要定义一个fds数组,该数组当中的每个位置都是一个struct pollfd结构,后续调用poll函数时会作为参数进行传入。先将fds数组中每个位置初始化为无效,并将监听套接字添加到fds数组当中,表示服务器刚开始运行时只需要监视监听套接字的读事件。
  • 此后,poll服务器就不断调用poll函数监视读事件是否就绪。如果poll函数的返回值大于0,则说明poll函数调用成功,此时已经有文件描述符的读事件就绪,接下来就应该对就绪事件进行处理。如果poll函数的返回值等于0,则说明timeout时间耗尽,此时直接准备下一个调用即可。如果poll函数的返回值为-1,则说明poll调用失败,此时也让服务器准备进行下一次poll调用,但实际应该进一步判断错误码,根据错误码来判断是否应该继续调用。
#pragma once

#include "Socket.hpp"
#include <poll.h>

#define BACK_LOG 5
#define NUM 1024
#define DFL_FD -1

class PollServer
{
public:
    PollServer(int port)
        : _port(port)
    {}

    void InitPollServer()
    {
        _listen_sock = Socket::SocketCreate();
        Socket::SocketBind(_listen_sock, _port);
        Socket::SocketListen(_listen_sock, BACK_LOG);
    }

    ~PollServer()
    {
        if (_listen_sock >= 0) close(_listen_sock);
    }

    void Run()
    {
        struct pollfd fds[NUM];
        ClearPollfds(fds, NUM, DFL_FD);
        SetPollfds(fds, NUM, _listen_sock);
        while (1)
        {
            switch (poll(fds, NUM, -1))
            {
                case 0:
                    std::cout << "timeout..." << std::endl;
                    break;
                case -1:
                    std::cerr << "poll error" << std::endl;
                    break;
                default:
                    HandleEvent(fds, NUM);
                    break;
            }
        }
    }

private:
    void ClearPollfds(struct pollfd fds[], int num, int default_fd)
    {
        for (int i = 0; i < num; ++i)
        {
            fds[i].fd = default_fd;
            fds[i].events = 0;
            fds[i].revents = 0;
        }
    }

    bool SetPollfds(struct pollfd fds[], int num, int fd)
    {
        for (int i = 0; i < num; ++i)
        {
            if (fds[i].fd == DFL_FD)
            {
                fds[i].fd = fd;
                fds[i].events |= POLLIN; // 添加事件到events中
                return true;
            }
            return false;
        }
    }

    int _listen_sock; // 监听套接字
    int _port; // 端口号
};

事件处理

当poll检测到有文件描述符的读事件就绪,就会在其对应的struct pollfd结构中的revents成员中添加读事件并返回,接下来poll服务器就应该对就绪事件进行处理了,事件处理结果如下:

  • 首先遍历fds数组的每个struct pollfd结构,如果该结构当中的fd有效,且revents当中包含读事件,则说明该文件描述符的读事件就绪,接下来就需要进一步判断该文件描述符是监听套接字还是与客户端建立的套接字。
  • 如果是监听套接字的读事件就绪,则调用accept函数将底层建立好的连接获取上来,并将获取到的套接字添加到fds数组当中,表示下一次调用poll函数时需要监视该套接字的读事件。
  • 如果是客户端建立的连接对应的读事件就绪,则调用read函数读取客户端发来的数据,并将读取到的数据在服务端进行打印。
  • 如果在调用read函数时发现客户端将连接关闭或者read函数调用失败,则poll函数会直接关闭对应的连接,并将该连接对应的文件描述符从fds数组当中清除,表示下一次调用poll函数时无需再监视该套接字的读事件。
    void HandleEvent(struct pollfd fds[], int num)
    {
        for (int i = 0; i < num; ++i)
        {
            if (fds[i].fd == DFL_FD) continue; // 跳过无效位置

            if (fds[i].fd == _listen_sock && fds[i].revents & POLLIN)
            {
                // 连接事件就绪
                struct sockaddr_in peer;
                memset(&peer, 0, sizeof(peer));
                socklen_t len = sizeof(peer);
                int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);
                if (sock < 0)
                {
                    std::cerr << "accept error" << std::endl;
                    continue;
                }
                std::string peer_ip = inet_ntoa(peer.sin_addr);
                int peer_port = ntohs(peer.sin_port);
                std::cout << "get a new link[" << peer_ip << ":" << peer_port << "]" << std::endl;
                if (!SetPollfds(fds, NUM, sock))
                {
                    close(sock);
                    std::cout << "poll server is full, close fd: " << sock << std::endl;
                }
            }
            else if (fds[i].revents & POLLIN)
            {
                // 读事件就绪
                char buffer[1024];
                ssize_t size = read(fds[i].fd, buffer, sizeof(buffer) - 1);
                if (size > 0) // 读取成功
                {
                    buffer[size-1] = 0;
                    std::cout << "echo# " << buffer << std::endl;
                }
                else if (size == 0) // 对端连接关闭
                {
                    std::cout << "client quit!" << std::endl;
                    close(fds[i].fd);
                    UnSetPollfds(fds, i); // 将该文件从fds数组中清除
                }
                else
                {
                    std::cerr << "read error" << std::endl;
                    close(fds[i].fd);
                    UnSetPollfds(fds, i); // 将该文件从fds数组中清除
                }
            }
        }
    }

private:
    void UnSetPollfds(struct pollfd fds[], int pos)
    {
        fds[pos].fd = DFL_FD;
        fds[pos].events = 0;
        fds[pos].revents = 0;
    }

因为这里fds数组的大小是固定设置的,因此将新获取连接对应的文件描述符添加到fds数组时,可能会因为fds数组已满而添加失败,这时poll服务器只能将刚刚获取上来的连接对应的套接字进行关闭。

运行poll服务器

#include "PollServer.hpp"
#include <string>

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cout << "Usage: " << "./PollServer port" << std::endl;
        exit(1);
    }

    int port = atoi(argv[1]);
    PollServer* svr = new PollServer(port);
    svr->InitPollServer();
    svr->Run();

    return 0;
}

运行结果如下:
在这里插入图片描述

3. poll的优点和确定

poll的优点

  • struct pollfd结构当中包含了events和revents,相当于select的输入输出参数进行分离,因此在每次调用poll之前,不需要像select一样重新对参数进行设置。
  • poll可监控的文件描述符数量没有限制。
  • poll和select一样,可以同时等待多个文件描述符,提高IO效率。

虽然代码中将fds数组的元素定义为1024,但是fds数组的大小是可以继续增大的,poll函数能够帮你监视多少文件描述符是由传入poll函数的第二个参数决定的。

而fd_set类型只有1024个比特位,因此select函数最大只能监视1024个文件描述符。

poll的缺点

  • 和select函数一样,当poll返回后,需要遍历fds数组来获取就绪的文件描述符。
  • 每次调用poll,都需要把大量的struct pollfd结构用用户态拷贝到内核态,这个开销也会随着poll监视的文件描述符数目的增多而增多。
  • 同时每次调用poll都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Report Sharp-Shooter Lite Edition Crack

Report Sharp-Shooter Lite Edition Crack 报告Sharp Shooter™ 是为.NET Framework设计的&#xff0c;使用C#编写&#xff0c;并且只包含100%的托管代码。Report Sharp Shooter能够从多个数据源生成任何复杂的报告&#xff0c;并将生成的报告导出为大多数格式&#xff0c;包括…

UNIX 入门

与 UNIX 建立连接启动会话登录命令提示符修改口令退出系统 简单的 UNIX 命令命令格式ls 命令who 命令虚拟终端 tty伪终端 ptywho am i 命令 cal 命令help 命令man 命令 shell 概述shell 命令更换 shell临时更改 shell永久更改 shell 登录过程 与 UNIX 建立连接 启动会话 要启…

Java Set集合:HashSet和TreeSet类

Set 集合类似于一个罐子&#xff0c;程序可以依次把多个对象“丢进”Set 集合&#xff0c;而 Set 集合通常不能记住元素的添加顺序。也就是说 Set 集合中的对象不按特定的方式排序&#xff0c;只是简单地把对象加入集合。Set 集合中不能包含重复的对象&#xff0c;并且最多只允…

【无标题】发大水

发大声道TOC 欢迎使用Markdown编辑器 你好&#xff01; 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章&#xff0c;了解一下Markdown的基本语法知识。 新的改变 我们对Markdown编辑器进行了一些功能拓展与语…

K8s集群安全机制

1.访问K8s集群的时候&#xff0c;需要经过三个步骤完成具体操作 &#xff08;1&#xff09;认证&#xff08;2&#xff09;鉴权&#xff08;授权&#xff09;&#xff08;3&#xff09;准入控制 进行访问的时候&#xff0c;过程中都要经过apiserver&#xff0c;apiserver做统…

Qt小项目贪吃蛇实线,主要掌握定时器、信号与槽、按键事件、绘制事件、坐标运算、随机数生成等

Qt小项目贪吃蛇实线&#xff0c;主要掌握定时器、信号与槽、按键事件、绘制事件、坐标运算、随机数生成等 Qt 贪吃蛇演示QWidget 绘制界面项目源文件 注释清晰widget.hwidget.cpp 拓展QTimerQKeyEventQRectFQPointFQPainterQIcon Qt 贪吃蛇演示 QWidget 绘制界面 项目源文件 注…

java版直播商城平台规划及常见的营销模式 电商源码/小程序/三级分销+商城免费搭建 bbcbbc

​ Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务…

Unity Sort Group(排序组)

** Unity 中的Sort Group组组件允许让Sprite Renderer(精灵渲染器)重新决定渲染顺序. ** 作为组件存在 组件内容&#xff1a; Unity 使用Sort Group 组件的Sort layer 和Order in layer的值来确定排序组在渲染队列内相对与场景内其他排序组和游戏对象的优先级。 属性功能So…

解密时尚RFID服装电商仓储系统

大家好&#xff0c;今天我们要聊一个时尚圈的新宠——RFID服装电商仓储系统&#xff01;是不是听起来就很高端大气上档次&#xff1f;别急&#xff0c;我会来给你扒一扒这个神秘的系统。 首先&#xff0c;咱们得搞清楚什么是RFID。别想太复杂&#xff0c;RFID就是一种让衣服变得…

大数据课程H2——TELECOM的电信流量项目实现

文章作者邮箱&#xff1a;yugongshiyesina.cn 地址&#xff1a;广东惠州 ▲ 本章节目的 ⚪ 了解TELECOM项目的数据收集&#xff1b; ⚪ 了解TELECOM项目的数据清洗&#xff1b; ⚪ 了解TELECOM项目的数据导出&#xff1b; ⚪ 了解TELECOM项目的数据可视化&…

抖音小店规则解读:开设个人店铺,合规经营

抖音小店是抖音平台上的一项功能&#xff0c;允许用户在抖音上开设个人店铺&#xff0c;进行商品销售。下面不若与众将介绍关于抖音小店的一些规则&#xff1a; 1. 店铺资质要求&#xff1a;开设抖音小店需要满足一定的资质要求&#xff0c;包括拥有有效身份证件、年满18周岁、…

Oracle以逗号分隔的字符串拆分为多行数据实例详解

前言 近期在工作中遇到某表某字段是可扩展数据内容&#xff0c;信息以逗号分隔生成的&#xff0c;现需求要根据此字段数据在其它表查询相关的内容展现出来&#xff0c;第一想法是切割数据&#xff0c;以逗号作为切割符&#xff0c;以下为总结的实现方法&#xff0c;以供大家参…

设计中存在的误区

1、子组件&#xff0c;如果想要出现宽度和高度&#xff0c;要在子组件中的最大的盒子定义宽度和高度&#xff0c;才能出现 1.1 你在common.js定义是不管用的&#xff0c;要在自己的盒子中定义长度和高度

Spring 基础

目录 一、什么是 Spring 框架?二、Spring 包含的模块有哪些&#xff1f;2.1 版本2.2 Spring各模块依赖 三、Spring,Spring MVC,Spring Boot 之间什么关系? 一、什么是 Spring 框架? Spring 是一款开源的轻量级 Java 开发框架&#xff0c;旨在提高开发人员的开发效率以及系统…

重要信号换成加地过孔的重要性【从仿真看本质】

文章目录 前言 前言 为什么需要在差分或者重要信号换层时在它们旁边加上地孔呢&#xff1f; 大家可能如果对画 PCB 没有经验的话&#xff0c;可能不太理解为什么差分线在换层时需要在 差分孔旁边打上地孔&#xff0c;这个问题有很多人都不太明白&#xff0c;为什么要这么做&…

K8s集群安全机制(RBAC)

RBAC 基于角色的访问控制

虹科展会丨诚邀您参与8月9-11日上海汽车测试及开发博览会,虹科在【3002】展位等您来!

汽车测试及质量监控博览会&#xff08;中国&#xff09;(Testing Expo China – Automotive)是引领世界的国际博览会&#xff0c;展示汽车测试、开发和验证技术的各个方面&#xff0c;每年在上海举行&#xff0c;并在底特律和斯图加特举办年度姊妹展会。 2023年的汽车测试及质…

python人工智能可以干什么,python人工智能能干什么

大家好&#xff0c;给大家分享一下python做人工智能需要什么水平&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 人工智能包含常用机器学习和深度学习两个很重要的模块&#xff0c;而python拥有matplotlib、Numpy、sklearn、keras等大量的…

Self-Attention、transformer代码、word2vec理论(skip-gram、CNOW)、近似训练 (第十三次组会)

@[TOC](Self-Attention、transformer代码、word2vec理论(skip-gram、CNOW)、近似训练 (第十三次组会)) Self-Attention相关 Transformer代码

成功的公文写作,需要注重结构和表达的准确性

首先&#xff0c;公文需要遵循一定的结构和布局&#xff0c;例如开头、正文和结尾的组成&#xff0c;每个部分的内容和格式都需要符合规范和标准。在结构方面&#xff0c;公文需要具备清晰的逻辑性和条理性&#xff0c;通过段落和标题的使用&#xff0c;使文章结构更加清晰明了…