【Linux】poll多路转接

能够用来唯一标定系统资源的标识符都可以称之为句柄

前言

select多路转接具有2大缺陷:

  • 监测的文件描述符是有限的,取决于fd_set位图的大小;如果想改,需要修改对应底层结构后重新编译内核
  • 由于fd_set位图是输入输出型参数,在内核态返回用户态时同时承担着传参的任务,所以我们需要一个第三方数组来持久化保存所需要检测的文件描述符。在我们每次遍历这个第三方数组在写代码时过于繁琐

poll多路转接克服了以上的两个缺点,它可以监测自定义数量的文件描述符,并且无需第三方数组,而是采用2个short参数分别保存输入与输出的变量的信息

poll的函数接口

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • 第一个参数:

    struct pollfd {
        int   fd;         /* file descriptor 所要检测的文件描述符*/
        short events;     /* requested events 要内核关心的事件*/
        short revents;    /* returned events 已经就绪的事件*/
    };
    
  • 第二个参数:关心的文件描述符的最大数量

  • 第三个参数:超时时间(微秒)设置为-1表示阻塞等直到等到有文件描述符上的事件就绪

  • 返回值:大于0表示已经就绪的文件描述符的数量;0表示超时;-1表示出错

基于poll的echo服务器代码实现

sock.hpp

#pragma once
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

namespace ns_sock {
  static const int BACKLOG = 5;

  enum Error {
    CREATE = 1,
    BIND,
    LISTEN
  };

  class Socket {
    public:
      Socket() {}
      ~Socket() {}
      static int CreateSocket() {
        int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
        if(listen_sock < 0) {
          std::cerr << "create sock error" << std::endl;
          exit(CREATE);
        }
        else {
          return listen_sock;
        }
      }

      static void Bind(int listen_sock, uint16_t port) {
        sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;
        if(bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0) {
          std::cerr << "bind error" << std::endl;
          exit(BIND);
        }
      }

      static void Listen(int sock) {
        if(listen(sock, BACKLOG) < 0) {
          std::cerr << "listen error" << std::endl;
          exit(LISTEN);
        }
      }
  };
}

poll_server.hpp

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

namespace ns_poll_server {

  static const uint16_t DEFAULT_PORT = 8080;
  static const int SOCK_NUM = 1024;
  static const int TIMEOUT = -1;
  static const size_t BUFFER_SIZE = 4096;

  enum ERROR {
    MALLOC = 10
  };
  class PollServer {
    public:
      PollServer(uint16_t port = DEFAULT_PORT): _port(port) {
        _listen_sock = ns_sock::Socket::CreateSocket();
        ns_sock::Socket::Bind(_listen_sock, _port);
        ns_sock::Socket::Listen(_listen_sock);
        // 初始化pollfds
        // 开辟空间
        _pollfds = (struct pollfd*) malloc(sizeof(struct pollfd) * SOCK_NUM);
        if(_pollfds == nullptr) {
          std::cerr << "malloc pollfds error" << std::endl;
          exit(MALLOC);
        }
        // 置空
        for(size_t i = 0; i < SOCK_NUM; i++) {
          _pollfds[i].fd = -1;
          _pollfds[i].events = 0;
          _pollfds[i].revents = 0;
        }
        // 绑定listen_sock 到_pollfds中,并监听读事件
        _pollfds[0].fd = _listen_sock;
        _pollfds[0].events = POLLIN;
      }

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

      void Handler() {
        for(size_t i = 0; i < SOCK_NUM; i++) {
          // 当前下标的元素不是被关心的文件描述符
          if(_pollfds[i].fd == -1) {
            continue;
          }
          // 当前文件描述符被关心
          else {
            // 当前文件描述符读事件已就绪
            if(_pollfds[i].revents & POLLIN) {
              // 读就绪的文件描述符是监听套接字
              if(_pollfds[i].fd == _listen_sock) {
                // accept建立链接
                sockaddr_in peer;
                bzero(&peer, 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;
                  return;
                }
                else {
                  // 建立新链接成功
                  std::cout << "get a new link: " << sock << std::endl;
                  // 将新的文件描述符添加到poll中进行读事件的等待检测
                  size_t index = 0;
                  for(; index < SOCK_NUM; index++) {
                    if(_pollfds[index].fd == -1) {
                      break;
                    }
                  }
                  // 打开的文件描述符过多
                  if(index == SOCK_NUM) {
                    std::cerr << "poll fd too much error" << std::endl;
                    return;
                  }
                  // 成功添加新的文件描述符
                  else {
                    _pollfds[index].fd = sock;
                    _pollfds[index].events = POLLIN;
                    // 把监听套接字的读就绪清空
                    _pollfds[i].revents = 0;
                    _pollfds[i].events = POLLIN;
                  }
                }
              }
              // 读就绪的文件描述符是其他套接字
              else {
                //  打印数据
                char buffer[BUFFER_SIZE] = {0};
                ssize_t size = recv(_pollfds[i].fd, buffer, sizeof(buffer) - 1, 0);
                // recv失败
                if(size < 0) {
                  std::cerr << "recv error" << std::endl;
                  _pollfds[i].fd = -1;
                  _pollfds[i].events = 0;
                  _pollfds[i].revents = 0;
                  return;
                }
                // 对端链接关闭
                else if(size == 0) {
                  std::cout << "close: " << _pollfds[i].fd << std::endl;
                  // 关闭文件描述符
                  close(_pollfds[i].fd);
                  _pollfds[i].fd = -1;
                  _pollfds[i].events = 0;
                  _pollfds[i].revents = 0;
                  return;
                }
                // 成功读取数据
                else {
                  buffer[size] = '\0';
                  std::cout << "sock " << _pollfds[i].fd << ": " << buffer;
                  _pollfds[i].revents = 0;
                  _pollfds[i].events = POLLIN;
                }
              }
            }
            // 当前文件描述符未就绪读
            else {
              continue;
            }
          }
        }
      }

      void Loop() {
        while(true) {
          int n = poll(_pollfds, SOCK_NUM, TIMEOUT);
          switch(n) {
            // poll出错
            case -1:
              std::cerr << "poll error" << std::endl;
              continue;
              break;
            // 没有就绪的文件描述符
            case 0:
              std::cout << "timeout..." << std::endl;
              break;
            // 有文件描述符就绪
            default:
              Handler();
              break;
          }
        }
      }

    private:
      uint16_t _port;
      int _listen_sock;
      struct pollfd *_pollfds;
  };
}//namespace

入口程序

#include "poll_server.hpp"
int main()
{
  ns_poll_server::PollServer *srv = new ns_poll_server::PollServer(8081);
  srv->Loop();
  return 0;
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

DanteIoVeYou

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

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

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

打赏作者

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

抵扣说明:

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

余额充值