IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 【Linux】多路转接之poll -> 正文阅读

[系统运维]【Linux】多路转接之poll


poll也是 一种多路转接的方案,poll 对select的缺点进行了改进:

  • 解决了select可以检测的文件描述符有上限的问题

  • 用户告知OS要检测的文件描述符OS告知哪些文件描述符上有事件就绪输入输出型参数进行了分离

    也就是说,保存要检测文件描述符的数据结构,不会在OS返回时被覆盖了,在轮询调用poll时,不再需要对检测文件描述符的数据结构再进行重新设置了,也就意味着用户不再需要自己创建数据结构来维护要检测文件描述符了

poll接口

#include <poll.h>

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

参数

  • fds:一个poll函数检测的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合
  • nfds:表示fds数组的长度
  • timeout:poll函数的超时时间, 单位是毫秒(ms)

返回值

  • 返回0:表示超出设置的timeout时间后还没有文件描述符就绪
  • 返回-1:发生错误
  • 返回大于0的值:表示poll由于监听的文件描述符就绪而返回

pollfd结构

在这里插入图片描述

  • fd:要检测的文件描述符
  • events:需要OS检测的事件的就绪类型
  • revents:OS返回的事件就绪类型

events和revents的取值:

在这里插入图片描述

在这里插入图片描述


timeout决定poll的等待策略

  • timeout设为0:表示非阻塞轮询
  • timeout设为大于0的值:表示阻塞多少ms后返回,轮询检测
  • timeout设为-1:表示阻塞等待

poll TCP服务器执行流程

  • 首先创建TCP套接字绑定端口号,对TCP套接字进行Listen,这时就会得到一个监听套接字listen_sock
  • 然后创建pollfd结构体数组rfds[],并对rfds进行清空,把对listen_sock读事件监听添加到数组的第一个元素
  • 接着进行阻塞轮询,根据不同的poll返回值,分为三种程序走向:
    • 返回值为0:timeout超时,继续轮询检测
    • 返回值为1:出错返回,继续轮询
    • 返回值大于0的值:说明这时有事件就绪或者有新连接到来
  • 如果poll返回值大于0,就把rfds[]中元素的revents&POLLIN筛选读事件就绪的文件描述符
    • 如果是listen_sock就绪,则说明有新连接到来,这时就可以对连接进行accept,此时进行accept不会被阻塞,accept之后,把新获取的文件描述符添加到rfds[]中的未使用位置,并检测读事件
    • 如果是别的文件描述符就绪,就是读事件就绪了,也就是有客户端发来了数据,这时就能对数据进行read,这时的read也不会阻塞

PollServer实现

  • 封装套接字,这里的封装套接字和select一样

  • 按照上面的执行流程编写PollServer

PollServer.hpp

#pragma once
#include "socket.hpp"
#include <poll.h>

#define BACKLOG 5
#define DFL_FD -1
#define NUM 64

namespace ns_poll {
    class PollServer {
    private:
        int port;
        int listen_sock;
    public:
        PollServer(int _port) :port(_port) {}

        void InitServer() {
            listen_sock = ns_sock::Sock::Socket();
            ns_sock::Sock::Bind(listen_sock, port);
            ns_sock::Sock::Listen(listen_sock, BACKLOG);
        }

        void Run() {
            std::cout << "服务器启动" << std::endl;
            struct pollfd rfds[64];
            for (int i = 0; i < 64; ++i) {
                rfds[i].fd = DFL_FD;
                rfds[i].events = 0;
                rfds[i].revents = 0;
            }
            //把监听套接字添加到rfds
            rfds[0].fd = listen_sock;
            //evevts |= 需要检测的事件 ,就是把事件注册进了events
            //本质是位操作把event的一位置为了1
            rfds[0].events |= POLLIN;
            rfds[0].revents = 0;
            while (true) {
                switch (poll(rfds, 64, -1)) {
                case 0:
                    std::cout << "timeout!!!" << std::endl;
                    break;
                case -1:
                    std::cerr << "poll error!" << std::endl;
                    break;
                default:
                    //处理事件
                    HanderEnent(rfds, NUM);
                    break;
                }
            }
        }
			~PollServer() {}
    private:
        void HanderEnent(struct pollfd rfds[], int num) {
            //判定fd是否在rfds中
            for (int i = 0; i < num; ++i) {
                //过滤无效fd
                if (rfds[i].fd == DFL_FD) {
                    continue;
                }
                //筛选已经就绪的事件
                //revents & 事件类型 ,位操作检测revents的特定位是否被OS置为1
                if (rfds[i].revents & POLLIN) {
                    if (rfds[i].fd == listen_sock) {
                        //连接事件就绪
                        //获取连接
                        struct sockaddr_in peer;
                        socklen_t len = sizeof(peer);

                        //这里进行accept时,由于连接事件已经就就绪,所以accept不会阻塞
                        int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
                        if (sock < 0) {
                            std::cerr << "获取连接错误" << std::endl;
                            continue;
                        }
                        //获取到一个新连接
                        //打印客户端信息
                        uint16_t peer_port = htons(peer.sin_port);
                        std::string peer_ip = inet_ntoa(peer.sin_addr);

                        std::cout << "有一个新连接 " << "ip :[" << peer_ip << "]  port: [" << peer_port << "]" << std::endl;

                        if (!AddFdToPollFd(sock, rfds, NUM)) {
                            std::cerr << "添加失败" << std::endl;
                            close(sock);
                        }//end if
                    }//end if
                    else {
                        //读事件就绪
                        char buffer[1024];
                        ssize_t size = recv(rfds[i].fd, buffer, sizeof(buffer) - 1, 0);
                        if (size > 0) {
                            buffer[size] = 0;
                            std::cout << "echo# " << buffer;
                        }
                        else if (size == 0) {
                            std::cout << "客户端断开连接" << std::endl;
                            close(rfds[i].fd);
                            //清除rfds中的文件描述符
                            rfds[i].fd = DFL_FD;
                        }
                        else {
                            std::cerr << "读取出错!!!" << std::endl;
                            close(rfds[i].fd);
                            //清除rfds中的文件描述符
                            rfds[i].fd = DFL_FD;
                        }
                    }
                }//end if
            }//end for
        }
        bool AddFdToPollFd(int sock, struct pollfd rfds[], int num) {
            for (int i = 0; i < num; ++i) {
                if (rfds[i].fd == DFL_FD) {
                    rfds[i].fd = sock;
                    rfds[i].events |= POLLIN;
                    rfds[i].revents = 0;
                    return true;
                }
            }
            return false;
        }
    };
}
  • server.cc
#include "poll_server.hpp"
#include <string>
static void Usage(std::string proc){
    std::cerr << "Usage :" << "\n\t" << proc <<" port "<<std::endl;
}

int main(int argc, char* argv[]){
    if(argc != 2){
        Usage(argv[0]);
        exit(4);
    }

    ns_poll::PollServer* pollsvr = new ns_poll::PollServer(atoi(argv[1]));   
    
    pollsvr->InitServer();
    pollsvr->Run();
}

poll的优点

  • pollfd结构包含了要监视的event和发生的revent,不再使用select“参数-值”传递的方式. 接口使用比select更方便
  • poll并没有检测文件描述符最大数量限制 (但是数量过大后性能也是会下降)

poll缺点

poll中监听的文件描述符数目增多时

  • 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符,遍历有开销
  • 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中
  • 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-05-09 13:09:20  更:2022-05-09 13:12:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 18:45:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码