【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 "sock.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 "PollServer.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结构从用户态拷贝到内核中
  • 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaomage1213888

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值