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 优先级带数据可写 是       
POLLRDHUP TCP连接被对方关闭, 或者对方关闭了写操作。它由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;
        }

    }

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

猜你喜欢

转载自blog.csdn.net/strive_mianyang/article/details/145656540
今日推荐