云计算百科
云计算领域专业知识百科平台

【linux】高级IO,I/O多路转接之poll,接口和原理讲解,poll版本的TCP服务器

小编个人主页详情<—请点击 小编个人gitee代码仓库<—请点击 linux系统编程专栏<—请点击 linux网络编程专栏<—请点击 倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己! 在这里插入图片描述


目录

    • 前言
    • 一、前置知识
    • 二、接口和原理讲解
    • 三、poll版本的TCP服务器
    • 四、poll的缺点
    • 五、源代码
      • Main.cc
      • makefile
      • PollServer.hpp
      • Log.hpp
      • Socket.hpp
    • 总结

前言

【linux】高级IO,I/O多路转接之select,接口和原理讲解,select版本的TCP服务器逐步完善讲解——书接上文 详情请点击<——,本文会在上文的基础上进行讲解,所以对上文不了解的读者友友请点击前方的蓝字链接进行学习 本文由小编为大家介绍——【linux】高级IO,I/O多路转接之poll,接口和原理讲解,poll版本的TCP服务器


一、前置知识

  • 要提起poll,不得不提起selet,关于select的缺点,小编在上一篇文章中已经进行讲解详情请点击<——,select的缺点如下 在这里插入图片描述
  • 第一点:select等待的fd是有限的,这个select的接口本身有问题,fd_set位图中只能容纳有限的1024个fd,不支持扩容或者传递更多的fd
  • 第二点:输入输出型参数比较多(select的后四个参数),数据拷贝的频率比较高,每次调用select的时候,从用户到内核,需要对用户关心的fd进行拷贝修改,从内核到用户,需要对已经就绪的fd进行拷贝修改,所以如果频繁的调用select,那么也就会进行频繁的数据拷贝
  • 第三点:输入输出型参数比较多,每次都要对关心的fd进行事件重置
  • 第四点:在用户层,要使用第三方数组fd_array管理用户关心的fd,并且用户层需要进行多次的遍历,例如在调用select之前用户重置rfds中用户关心的fd需要遍历数组,在Dispatcher判断fd是否在rfds中也需要进行遍历数组,在连接管理器Accepter中寻找合适的位置放入连接对应的文件描述符sock的时候同样需要遍历数组,同样的在内核检测fd就绪也需要遍历底层的文件描述符表进而判断对应的fd上的事件是否就绪
  • 所以虽然select是一个多路转接的方案,可以同时等待多个文件描述符,理想情况下,select等待的文件描述符越多,那么单位时间内等待的时间就越少,所以IO效率越高
  • 但是在实际中,随着select等待的文件描述符越来越多,上面的四点被触发的概率就越多,即带来的是数据拷贝较多,并且重置增多,遍历增多,所以在实际使用select的时候,随着select一次等待的文件描述符越来越多,select进行IO的效率可能到达某个峰值之后,可能会增长缓慢
  • 所以即使select在其它IO模型的对比中,IO效率较高,但是对于我们来讲还不够,并且对于上面的第一点和第三点是select的硬伤,于是我们有了第二种多路转接方案poll
  • 二、接口和原理讲解

  • poll也是一种多路转接方案,poll同样的只负责等待,poll同样可以一次等待多个文件描述符,多路转接方案poll的系统调用接口是poll,使用poll要包头文件#include <poll.h>,所以此时如下,我们来认识一下poll 在这里插入图片描述
  • poll的返回值n是int类型的,如果大于0那么代表此时有n个文件描述符上的事件就绪了,如果n等于0,那么代表此时poll等待超时了,如果n小于0,那么代表等待出错了
  • 首先poll有三个参数,那么我们此时先来看poll的第三个参数,即int类型的timeout超时时间,timeout的单位是微秒us,即如果我们想要设置超时时间为3秒,那么此时就要将秒换算成微秒进行传参,1秒等于1000微秒,所以如果想要设置超时时间为3秒,那么timeout要被设置为3000
  • poll的第一个参数是一个指针,指向一个struct pollfd的类型,这个类型的成员变量有一个文件描述符fd,一个short类型的events,还有一个short类型的revents,对于第一个的文件描述符fd我们可以知道是什么作用,即用户告诉内核帮我关心这个文件描述符的上的某某事件,也可以用作内核告诉用户这个文件描述符上的某某事件已经就绪了,赶快来读取吧
  • 所以用户在给内核传入的时候,如何才能告知内核帮我关心这个文件描述符上的具体事件呢?我们知道其实内核喜欢使用比特位来传递信息,所以第二个成员变量short类型的events就是用户给内核传入的时候要使用的,即short类型的events也势必使用了比特位传递信息 在这里插入图片描述
  • 那么event通过设置上面的标志位,然后就可以告诉内核要关心文件描述符的什么事件,例如关心读事件可以设置POLLIN,关心写事件可以设置POLLOUT等,如果要关心多个事件,那么进行按位与一起设置即可 在这里插入图片描述
  • 那么struct pollfd的第三个参数revents是当内核检测到了用户让关心的文件描述符上的就绪了之后,那么内核就设置revents,例如读事件就绪了那么就设置POLLIN,写事件就绪那那么就设置POLLOUT等,如果一个文件描述符fd上的多个事件同时就绪,那么进行按位与一起设置即可,所以poll实现了将fd的输入事件和输出事件进行分离
  • 我们说poll可以解决select的fd有上限的问题,如何解决?那么就是通过第一个参数和第二个参数解决,poll的第一个参数是一个指针,确切的说是一个数组,数组中被设置的每一个对象是用户需要内核关心的,那么poll的第二个参数是个数,即这个数组中的对象的个数,所以poll也和select一样,需要用户在用户层维护一个数组来管理fd,那么此时问题来了,不是说poll可以解决select的fd有上限的问题吗,poll还是使用了一个数组
  • 但是poll与select不一样的是,poll的这个数组很有特点,poll的这个数组是直接用于传参的,并且poll的这个数组的大小由用户来定,poll给用户权利,让用户决定需要关心多少fd,好,既然你说poll可以解决select的fd有上限的问题,那么我用户关心10亿个fd,即此时数组中的对象需要开10亿个,但是事实的情景是,开不出来这么多的空间,那么我们如何理解呢?
  • 很好理解,我poll提供给用户,允许让用户关心10亿个fd,但是实际情况下数组中放的对象开不出10亿个,那么此时是谁的问题?肯定不是我poll的问题呀,实际情况是内存不足了,所以说是硬件的问题,我poll就要开10亿个,硬件不支持,是硬件的问题,不是我poll的问题,所以poll解决了select的fd有上限的问题 在这里插入图片描述
  • 所以此时我们来看,poll的接口相对于select是更简单了,即接口的进步是趋向于越来越简单进行设计,poll相对于select没有那么多的输入输出参数,诸如select中,对于fd_set位图来讲如果不知道比特位的位置是什么含义,比特位的内容是什么含义,根本就无法使用selct,并且对于超时时间,poll也采用了更简单的方式,直接传一个int类型,舍弃了select中还要传参一个结构体来表示超时时间
  • 不是说poll可以解决select的硬伤,其中select的硬伤有两个,一个是fd有上限,第二个是每次调用select,用户都要对关心的fd进行重置,所以说你的意思是poll不需要每次调用poll的时候,对用户关心的fd进行重置?
  • 是的,struct pollfd这个结构体中的成员变量有fd,events,revents,由于有struct pollfd这个结构的支撑,poll实现了对fd的输入事件和输出事件进行分离,所以当内核检测到文件描述符上的事件就绪的时候,对于一个struct pollfd这个结构体中的成员变量fd,events不会进行修改,内核要进行修改的是revents
  • 如果用户调用poll的时候,用户下次还需要对这个文件描述符上的事件进行关心,那么不用对这个fd进行重置,直接进行传入即可,因为fd可以告知内核用户要关心文件描述符,events可以告知内核,用户要关心的是fd上的具体事件,所以poll可以完美的解决select的2个硬伤
  • 所以此时我们感觉poll和select很像,在用户层还有内核都需要进行很多的遍历才能确认fd上的事件是否继续,只不过poll和select不一样的是poll更简单,更容易让我们上手,所以也就意味着只要我们掌握了select版本的TCP服务器的编写,那么poll版本的TCP服务器的编写也就很简单了
  • 所以下面我们不废话,直接基于上一篇文章中的select版本的TCP服务器的代码 第二点讲解的select版本的TCP服务器,详情请点击<——,进行代码的一些简单的修改,快速实现一下poll版本的TCP服务器
  • 三、poll版本的TCP服务器

  • 首先我们的目的就是要基于select版本的TCP服务器的代码进行修改实现poll版本的TCP服务器,所以如下是select版本的TCP服务器的代码
  • #include <iostream>
    #include <sys/select.h>
    #include <unistd.h>
    #include "Log.hpp"
    #include "Socket.hpp"

    using namespace std;

    static const uint16_t defaultport = 8080;
    static const int fd_num_max = sizeof(fd_set) * 8;
    int defaultfd = 1;

    class SelectServer
    {
    public:
    SelectServer(uint16_t port = defaultport)
    :_port(port)
    {
    for(int i = 0; i < fd_num_max; i++)
    {
    fd_array[i] = defaultfd;
    }
    }

    bool Init()
    {
    _listensock.Socket();
    _listensock.Bind(_port);
    _listensock.Listen();

    return true;
    }

    void Accepter()
    {
    string clientip;
    uint16_t clientport;
    int sock = _listensock.Accept(&clientip, &clientport);
    if(sock < 0)
    return;

    lg(Info, "accept success, %s:%d, sock fd: %d", clientip.c_str(), clientport, sock);

    int pos = 1;
    for(; pos < fd_num_max; pos++)
    {
    if(fd_array[pos] == defaultfd)
    break;
    }

    if(pos == fd_num_max)
    {
    lg(Warning, "server is full, close %d now", sock);
    close(sock);
    }
    else
    {
    fd_array[pos] = sock;
    PrintFd();
    }
    }

    void Recver(int fd, int pos)
    {
    char buffer[1024];
    ssize_t n = read(fd, buffer, sizeof(buffer) 1);
    if(n > 0)
    {
    buffer[n 1] = 0;
    cout << "get a message: " << buffer << endl;
    }
    else if(n == 0)
    {
    lg(Info, "client quit, me too, close fd: %d", fd);
    close(fd);
    fd_array[pos] = defaultfd;
    }
    else
    {
    lg(Warning, "recv error, fd: %d", fd);
    close(fd);
    fd_array[pos] = defaultfd;
    }
    }

    void Dispatcher(fd_set& rfds)
    {
    for(int i = 0; i < fd_num_max; i++)
    {
    int fd = fd_array[i];
    if(fd == defaultfd)
    continue;

    if(FD_ISSET(fd, &rfds))
    {
    if(fd == _listensock.Fd())
    {
    Accepter(); // 连接管理器
    }
    else
    {
    Recver(fd, i); // 数据读取器
    }
    }
    }
    }

    void Start()
    {
    int listensock = _listensock.Fd();
    fd_array[0] = listensock;
    for(;;)
    {
    fd_set rfds;
    FD_ZERO(&rfds);

    int maxfd = fd_array[0];
    for(int i = 0; i < fd_num_max; i++)
    {
    if(fd_array[i] != defaultfd)
    {
    FD_SET(fd_array[i], &rfds);
    if(maxfd < fd_array[i])
    {
    maxfd = fd_array[i];
    lg(Info, "max fd update, max fd is: %d", maxfd);
    }
    }
    }

    // struct timeval timeout = {2, 0};
    struct timeval timeout = {0, 0};
    // int n = select(listensock + 1, &rfds, nullptr, nullptr, &timeout);
    // int n = select(listensock + 1, &rfds, nullptr, nullptr, nullptr);
    int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);
    switch(n)
    {
    case 0:
    cout << "time out, timeout: " << timeout.tv_sec << "." << timeout.tv_usec << endl;
    break;
    case 1:
    cout << "select error" << endl;
    break;
    default:
    cout << "get a new link" << endl;
    Dispatcher(rfds);
    break;
    }
    }
    }

    void PrintFd()
    {
    cout << "online fd list: ";
    for(int i = 0; i < fd_num_max; i++)
    {
    if(fd_array[i] != defaultfd)
    {
    cout << fd_array[i] << ' ';
    }
    }
    cout << endl;
    }

    ~SelectServer()
    {
    _listensock.Close();
    }
    private:
    Sock _listensock;
    uint16_t _port;
    int fd_array[fd_num_max];
    };

    在这里插入图片描述

  • 那么对于代码整体的框架我们不需要进行很大的改动,首先改变如上的文件名为PollServer.hpp以及Main.cc中的main函数中调用智能指针管理的类为PollServer
  • #include "PollServer.hpp"
    #include <memory>

    int main()
    {
    unique_ptr<PollServer> svr(new PollServer());

    svr->Init();
    svr->Start();

    return 0;
    }

  • 接下来在PollServer.hpp中将封装的类的名称修改为PollServer,并且同步修改构造函数和析构函数的名称为PollServer以及~PollServer,接下来将用户层维护管理fd的数组修改为struct pollfd _event_fds[fd_num_max];,其中fd_num_max我们设置为64,即此时我们仅关心最多64个文件描述符,如果需要关心更多,那么进行设置即可,那么对于析构函数以及Init初始化服务器我们不需要改动
  • 接下来定义一个全局的int non_event = 0表示没有要关心的或者就绪的事件,那么在构造函数中遍历_event_fds数组,使用non_event初始化数组中的每一个成员对象struct pollfd中的events以及revents,因为events以及revents是通过设置的比特位传递信息的,所以如果non_event为0,即此时比特位上全部为0表示不关心任何事件或者没有文件描述上的任何事件就绪
  • 并且使用defaultfd = -1去初始化struct pollfd中的fd即可,文件描述符被初始化为defaultfd表示这个位置的文件描述符无效,即使内核收到了这个文件描述符也会进行忽略,因为文件描述符是文件描述符表数组的下标,下标都是大于等于0的,所以defaultfd = 1表示文件描述符无效
  • #include <iostream>
    #include <sys/select.h>
    #include <unistd.h>
    #include <poll.h>
    #include "Log.hpp"
    #include "Socket.hpp"

    using namespace std;

    static const uint16_t defaultport = 8080;
    static const int fd_num_max = 64;
    int defaultfd = 1;
    int non_event = 0;

    class PollServer
    {
    public:
    PollServer(uint16_t port = defaultport)
    :_port(port)
    {
    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;
    }
    }

    bool Init()
    {
    _listensock.Socket();
    _listensock.Bind(_port);
    _listensock.Listen();

    return true;
    }

    void Accepter();
    void Recver(int fd, int pos);
    void Dispatcher(fd_set& rfds);
    void Start();
    void PrintFd();

    ~PollServer()
    {
    _listensock.Close();
    }
    private:
    Sock _listensock;
    uint16_t _port;
    struct pollfd _event_fds[fd_num_max];
    };

  • 那么接下来在Start函数中,作为一款服务器我们一定要关心的是用于监听连接到来的文件描述符listensock,所以在Start的for循环开始前我们就将listensock设置进_event_fds数组的0号位置中,即将0号位置的对象struct pollfd的成员变量fd设置为listensock,然后将events设置为POLLIN,即关心listensock的读事件,同样的超时时间timeout我们设置为3000表示3000微秒,即对应3秒 在这里插入图片描述
  • 那么在for循环中,就可以开始执行服务器的逻辑了,poll多路转接方案相对select,poll在每次调用前不需要对关心的fd进行重置以及求出maxfd的值,所以我们将select的这些工作直接删除掉,然后直接开始调用poll进行依次传参即可_event_fds, fd_num_max, timeout
  • 接下来将n = 0的情况中对应打印的超时时间的打印删除,因为此时poll的timeout超时时间不再是和select一样是输入输出型参数了,然后Dispatcher分派器不需要传入任何参数,因为已经就绪的文件描述符的事假,内核已经进行了设置进了类的成员函数_event_fds中的对象struct pollfd中的成员变量revents中
  • 所以在派发器中遍历_event_fds数组,然后找出fd不为defaultfd的位置,然后使用按位与看revents中的比特位的读事件是否被内核设置为POLLIN了,如果设置了,那么按位与的结果为true表示内核告诉我们,你用户让我关心的文件描述符上的事件已经就绪了,所以此时我们再判断如果这个fd是listensock我们就使用连接管理器Accepter就获取连接,否则我们就使用数据读取器Recver读取文件描述符中底层接收缓冲区的数据
  • void Dispatcher()
    {
    for(int i = 0; i < fd_num_max; i++)
    {
    int fd = _event_fds[i].fd;
    if(fd == defaultfd)
    continue;

    if(_event_fds[i].revents & POLLIN)
    {
    if(fd == _listensock.Fd())
    {
    Accepter(); // 连接管理器
    }
    else
    {
    Recver(fd, i); // 数据读取器
    }
    }
    }
    }

    void Start()
    {
    _event_fds[0].fd = _listensock.Fd();
    _event_fds[0].events = POLLIN;
    int timeout = 3000;
    for(;;)
    {
    int n = poll(_event_fds, fd_num_max, timeout);
    switch(n)
    {
    case 0:
    cout << "time out…" << endl;
    break;
    case 1:
    cout << "poll error" << endl;
    break;
    default:
    cout << "get a new link" << endl;
    Dispatcher();
    break;
    }
    }
    }

  • 那么此时我们来看连接管理器Accepter,此时listensock上的读事件就绪了,即底层的连接到来了,我们应该使用Accept将连接获取上来然后放到_event_fds数组中,那么遍历_event_fds数组寻找一个位置放入sock
  • 如果遍历_event_fds数组之后发现没有位置了,所以此时我们有两种策略 (一)第一种是仅仅维护fd_num_max = 64,即我们只关心64个fd,如果数组满了,那么后续我们在获取上来的连接就直接关掉 (二)第二种策略是进行扩容,那么这里我们使用vector进行管理struct pollfd,即管理fd,那么也就意味着管理fd的数组_event_fds我们可以进行resize扩容,如果不想使用vecotr,而是使用原生数组,那么原生数组的扩容逻辑其实很简单,这里小编就不再编写了,感兴趣的读者友友可以自行编写
  • 根据不同的场景可以选择不同的策略,那么这里小编就简单一点,采用第一种方法,数组维护固定的fd,关闭连接,其实我们也可以硬编码直接将fd_num_max设置大一点比如初始值为65535这个样子,当然这里小编由于测试环境中来自客户端的连接并没有那么多,所以fd_num_max小编就设置为64了
  • 接下来如果遍历_event_fds找到了位置pos,那么我们就将新获取上来的连接对应的文件描述符sock设置到fd中,并且关心读事件将POLLIN设置到events中,并且顺带也将revents也初始化为non_event表示没有任何事件就绪,其实我们这里也可以不初始化revents,为什么?
  • 因为将来内核发现用户让我内核关心的文件描述符的事件如果就绪了,那么就使用对应的宏事件覆盖revents的值,由于内核是覆盖式的写入revents,所以这里我们就算不初始化revents也没有问题
  • 接下来调用PrintFd,将在线的fd打印出来,改动同样很小,poll对应的fd数组变更了,所以获取fd的方式修改为_event_fds[i].fd即可,接下来PrintFd返回后,也可以继续执行其它的事情,比如打印日志,获取状态等任务,这里小编就不进行举例编写了
  • void Accepter()
    {
    string clientip;
    uint16_t clientport;
    int sock = _listensock.Accept(&clientip, &clientport);
    if(sock < 0)
    return;

    lg(Info, "accept success, %s:%d, sock fd: %d", clientip.c_str(), clientport, sock);

    int pos = 1;
    for(; pos < fd_num_max; pos++)
    {
    if(_event_fds[pos].fd == defaultfd)
    break;
    }

    if(pos == fd_num_max)
    {
    lg(Warning, "server is full, close %d now", sock);
    close(sock);
    // 扩容
    }
    else
    {
    _event_fds[pos].fd = sock;
    _event_fds[pos].events = POLLIN;
    _event_fds[pos].revents = non_event;
    PrintFd();
    }
    }

    void PrintFd()
    {
    cout << "online fd list: ";
    for(int i = 0; i < fd_num_max; i++)
    {
    if(_event_fds[i].fd != defaultfd)
    {
    cout << _event_fds[i].fd << ' ';
    }
    }
    cout << endl;
    }

  • 那么在数据读取器Recver中,逻辑是将数据读取上来,如果read的返回值n大于0打印数据字符串这些不需改动,需要改动的是当n等于0的时候,此时客户端已经将连接关闭了,所以此时我服务器也把连接关闭,那么就调用close关闭文件描述符fd即可,然后将该位置上的fd值设置为defaultfd表示用户不再关心该文件描述符
  • 由于poll管理的fd数组改动了,所以当n等于0的时候,需要改动是该位置的读取方式为_event_fds[pos].fd,同理n小于0的情况也需要用户不再关心该文件描述符,即也是需要改动读取该位置的读取方式为_event_fds[pos].fd
  • void Recver(int fd, int pos)
    {
    char buffer[1024];
    ssize_t n = read(fd, buffer, sizeof(buffer) 1);
    if(n > 0)
    {
    buffer[n 1] = 0;
    cout << "get a message: " << buffer << endl;
    }
    else if(n == 0)
    {
    lg(Info, "client quit, me too, close fd: %d", fd);
    close(fd);
    _event_fds[pos].fd = defaultfd;
    }
    else
    {
    lg(Warning, "recv error, fd: %d", fd);
    close(fd);
    _event_fds[pos].fd = defaultfd;
    }
    }

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

  • 所以此时运行结果如上,那么我们左侧的poll版本的TCP服务器运行之后,开始没有客户端连接,所以每隔3秒,那么就自动等待超时一次,那么当右侧第一个会话使用telnet作为客户端连接的时候,左侧服务器就会收到连接,然后添加到_event_fds,关心它的读事件
  • 那么此时小编在右侧输入字符串11111,左侧服务器就可以使用poll顺利等待到连接对应的文件描述符sock对应的读事件就绪,然后让Recver数据读取器读取文件描述符sock上的tcp接收缓冲区的数据然后打印,同样的道理第二个会话使用telnet作为客户端的连接以及输入22222也是同样的道理
  • 紧接着在第二个会话中,小编断开连接,那么此时服务器也能将连接对应的文件描述符在_event_fds中移除,同样第一个会话断开连接也可以顺利移除,所以我们的服务器对于客户端的连接,发送数据,断开连接都可以把握得住
  • 四、poll的缺点

  • 那么虽然poll解决了select的两个硬伤,即fd有上限,以及在调用的时候需要对关心的fd上的事件进行重置,但是poll还有效率的问题
  • 即poll的效率还是不够极致,因为poll中需要用户维护关心fd的数组_event_fds,要进行大量的遍历,例如在分派器Dispatcher判断_event_fds中已经就绪的fd需要进行遍历数组,在连接管理器Accepter中寻找合适的位置放入连接对应的文件描述符sock的时候同样需要遍历数组,同样的在内核检测fd就绪也需要遍历底层的文件描述符表进而判断对应的fd上的事件是否就绪
  • 但是小编,这里我有一个疑问,遍历数组的事件复杂度并不高呀,仅仅是O(N),感觉并没有影响效率,那么小编在这里说如果今天要关心的fd有1亿个,乃至10亿个,你poll的接口可以设置的fd没有上限,那么理想情况下,在硬件内存支持的情况下,poll是可以允许关心高达10亿多个fd上的事件的,那么此时进行多次的遍历就很影响效率
  • 所以poll也是有硬伤的,那么poll的硬伤如何优化呢?那么就要使用到小编下一篇讲解的epoll这个多路转接方案了,请期待小编的下一篇讲解多路转接方案之epoll
  • 五、源代码

    Main.cc

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

    int main()
    {
    unique_ptr<PollServer> svr(new PollServer());

    svr->Init();
    svr->Start();

    return 0;
    }

    makefile

    pollserver:Main.cc
    g++ -o $@ $^ -std=c++11
    .PHONY:clean
    clean:
    rm -f pollserver

    PollServer.hpp

    #include <iostream>
    #include <sys/select.h>
    #include <unistd.h>
    #include <poll.h>
    #include "Log.hpp"
    #include "Socket.hpp"

    using namespace std;

    static const uint16_t defaultport = 8080;
    static const int fd_num_max = 64;
    int defaultfd = 1;
    int non_event = 0;

    class PollServer
    {
    public:
    PollServer(uint16_t port = defaultport)
    :_port(port)
    {
    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;
    }
    }

    bool Init()
    {
    _listensock.Socket();
    _listensock.Bind(_port);
    _listensock.Listen();

    return true;
    }

    void Accepter()
    {
    string clientip;
    uint16_t clientport;
    int sock = _listensock.Accept(&clientip, &clientport);
    if(sock < 0)
    return;

    lg(Info, "accept success, %s:%d, sock fd: %d", clientip.c_str(), clientport, sock);

    int pos = 1;
    for(; pos < fd_num_max; pos++)
    {
    if(_event_fds[pos].fd == defaultfd)
    break;
    }

    if(pos == fd_num_max)
    {
    lg(Warning, "server is full, close %d now", sock);
    close(sock);
    // 扩容
    }
    else
    {
    _event_fds[pos].fd = sock;
    _event_fds[pos].events = POLLIN;
    _event_fds[pos].revents = non_event;
    PrintFd();
    }
    }

    void Recver(int fd, int pos)
    {
    char buffer[1024];
    ssize_t n = read(fd, buffer, sizeof(buffer) 1);
    if(n > 0)
    {
    buffer[n 1] = 0;
    cout << "get a message: " << buffer << endl;
    }
    else if(n == 0)
    {
    lg(Info, "client quit, me too, close fd: %d", fd);
    close(fd);
    _event_fds[pos].fd = defaultfd;
    }
    else
    {
    lg(Warning, "recv error, fd: %d", fd);
    close(fd);
    _event_fds[pos].fd = defaultfd;
    }
    }

    void Dispatcher()
    {
    for(int i = 0; i < fd_num_max; i++)
    {
    int fd = _event_fds[i].fd;
    if(fd == defaultfd)
    continue;

    if(_event_fds[i].revents & POLLIN)
    {
    if(fd == _listensock.Fd())
    {
    Accepter(); // 连接管理器
    }
    else
    {
    Recver(fd, i); // 数据读取器
    }
    }
    }
    }

    void Start()
    {
    _event_fds[0].fd = _listensock.Fd();
    _event_fds[0].events = POLLIN;
    int timeout = 3000;
    for(;;)
    {
    int n = poll(_event_fds, fd_num_max, timeout);
    switch(n)
    {
    case 0:
    cout << "time out…" << endl;
    break;
    case 1:
    cout << "poll error" << endl;
    break;
    default:
    cout << "get a new link" << endl;
    Dispatcher();
    break;
    }
    }
    }

    void PrintFd()
    {
    cout << "online fd list: ";
    for(int i = 0; i < fd_num_max; i++)
    {
    if(_event_fds[i].fd != defaultfd)
    {
    cout << _event_fds[i].fd << ' ';
    }
    }
    cout << endl;
    }

    ~PollServer()
    {
    _listensock.Close();
    }
    private:
    Sock _listensock;
    uint16_t _port;
    struct pollfd _event_fds[fd_num_max];
    };

    Log.hpp

    #pragma once

    #include <iostream>
    #include <string>
    #include <ctime>
    #include <cstdio>
    #include <cstdarg>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>

    #define SIZE 1024

    #define Info 0
    #define Debug 1
    #define Warning 2
    #define Error 3
    #define Fatal 4

    #define Screen 1 //输出到屏幕上
    #define Onefile 2 //输出到一个文件中
    #define Classfile 3 //根据事件等级输出到不同的文件中

    #define LogFile "log.txt" //日志名称

    class Log
    {
    public:
    Log()
    {
    printMethod = Screen;
    path = "./log/";
    }

    void Enable(int method) //改变日志打印方式
    {
    printMethod = method;
    }

    ~Log()
    {}

    std::string levelToString(int level)
    {
    switch(level)
    {
    case Info:
    return "Info";
    case Debug:
    return "Debug";
    case Warning:
    return "Warning";
    case Error:
    return "Error";
    case Fatal:
    return "Fatal";
    default:
    return "";
    }
    }

    void operator()(int level, const char* format, ...)
    {
    //默认部分 = 日志等级 + 日志时间
    time_t t = time(nullptr);
    struct tm* ctime = localtime(&t);
    char leftbuffer[SIZE];
    snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
    ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
    ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

    va_list s;
    va_start(s, format);
    char rightbuffer[SIZE];
    vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
    va_end(s);

    char logtxt[2 * SIZE];
    snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

    printLog(level, logtxt);
    }

    void printLog(int level, const std::string& logtxt)
    {
    switch(printMethod)
    {
    case Screen:
    std::cout << logtxt << std::endl;
    break;
    case Onefile:
    printOneFile(LogFile, logtxt);
    break;
    case Classfile:
    printClassFile(level, logtxt);
    break;
    default:
    break;
    }
    }

    void printOneFile(const std::string& logname, const std::string& logtxt)
    {
    std::string _logname = path + logname;
    int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
    if(fd < 0)
    return;
    write(fd, logtxt.c_str(), logtxt.size());
    close(fd);
    }

    void printClassFile(int level, const std::string& logtxt)
    {
    std::string filename = LogFile;
    filename += ".";
    filename += levelToString(level);

    printOneFile(filename, logtxt);
    }

    private:
    int printMethod;
    std::string path;
    };

    Log lg;

    Socket.hpp

    #pragma once

    #include <iostream>
    #include <string>
    #include <cstring>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include "Log.hpp"

    const int backlog = 10;

    enum{
    SocketErr = 1,
    BindErr,
    ListenErr,
    };

    class Sock
    {
    public:
    Sock()
    {}

    void Socket()
    {
    sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd_ < 0)
    {
    lg(Fatal, "socket error, %s : %d", strerror(errno), errno);
    exit(SocketErr);
    }

    int opt = 1;
    setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
    }

    void Bind(uint16_t port)
    {
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = INADDR_ANY;
    socklen_t len = sizeof(local);

    if(bind(sockfd_, (struct sockaddr*)&local, len) < 0)
    {
    lg(Fatal, "bind error, %s : %d", strerror(errno), errno);
    exit(BindErr);
    }
    }

    void Listen()
    {
    if(listen(sockfd_, backlog) < 0)
    {
    lg(Fatal, "listen error, %s : %d", strerror(errno), errno);
    exit(ListenErr);
    }
    }

    int Accept(std::string* clientip, uint16_t* clientport)
    {
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);

    int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);
    if(newfd < 0)
    {
    lg(Warning, "accept error, %s : %d", strerror(errno), errno);
    return 1;
    }

    char ipstr[128];
    inet_ntop(AF_INET, &(peer.sin_addr), ipstr, sizeof(ipstr));
    *clientip = ipstr;
    *clientport = ntohs(peer.sin_port);

    return newfd;
    }

    bool Connect(const std::string& serverip, uint16_t serverport)
    {
    struct sockaddr_in peer;
    memset(&peer, 0, sizeof(peer));
    peer.sin_family = AF_INET;
    peer.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(peer.sin_addr));
    socklen_t len = sizeof(peer);

    int n = connect(sockfd_, (struct sockaddr*)&peer, len);
    if(n == 1)
    {
    std::cerr << "connect to " << serverip << ':' << serverport << "error" << std::endl;
    return false;
    }

    return true;
    }

    void Close()
    {
    if(sockfd_ > 0)
    {
    close(sockfd_);
    }
    }

    int Fd()
    {
    return sockfd_;
    }

    ~Sock()
    {}
    private:
    int sockfd_;
    };


    总结

    以上就是今天的博客内容啦,希望对读者朋友们有帮助 水滴石穿,坚持就是胜利,读者朋友们可以点个关注 点赞收藏加关注,找到小编不迷路!

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【linux】高级IO,I/O多路转接之poll,接口和原理讲解,poll版本的TCP服务器
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!