【Linux网络】poll{初识poll / poll接口 / poll vs select / poll开辟多客 ...

打印 上一主题 下一主题

主题 578|帖子 578|积分 1734

1.初识poll



poll是Linux体系中的一个体系调用,它用于监控多个文件形貌符(file descriptors,如套接字、管道、文件等)的状态变革。通过poll,步伐可以同时等待多个文件形貌符上发生的特定事件(如可读、可写、错误等),而无需为每个文件形貌符创建单独的线程或进程。这使得poll成为处理多个并发连接和I/O操作的有效手段。
  1. #include <poll.h>
  2. int poll(struct pollfd *fds, nfds_t nfds, int timeout);
复制代码
fds:是一个指向struct pollfd结构体数组的指针,每个元素指定了一个要检查的文件形貌符及其感爱好的事件。
nfds:指定了fds数组中元素的数量。
timeout:指定了poll调用阻塞的时间,以毫秒为单位。如果设置为-1,poll将无穷期地等待;如果设置为0,poll将立即返回;如果设置为正数,poll将在指定的毫秒数后超时。
poll与select的主要联系与区别


  • 联系
    多路复用:poll和select都是I/O多路复用机制,允许步伐同时监控多个文件形貌符,提高了步伐处理并发I/O的效率。
    事件通知:两者都提供了一种机制,通过事件通知步伐哪些文件形貌符已经准备好进行读取、写入或有异常环境。
    非阻塞I/O:它们都可以与非阻塞I/O联合使用,使得步伐可以在没有数据可读或写入时继续实行其他任务。
  • 区别


  • 文件形貌符数量限制:
    select:通常受到体系文件形貌符数量限制的限制,一般在1024个左右。
    poll:没有文件形貌符数量的硬限制,理论上可以监控更多的文件形貌符(但实际上受限于体系资源和内存限制)。
  • 数据结构:
    select:使用三个独立的位图(bitmap)来跟踪文件形貌符的状态,这些位图在内核空间维护。
    poll:使用一个struct pollfd结构体数组来存储要监控的文件形貌符及其事件,这个数组在用户空间维护。
  • 事件信息丰富度:
    select:需要步伐遍历位图来查找事件,对于每个文件形貌符的状态变革,select提供的信息相对较少。
    poll:struct pollfd结构体中的revents字段在事件发生时会被内核设置,提供了更丰富的信息关于每个文件形貌符的状态变革。
  • 性能:
    select:在文件形貌符数量较多时,性能会下降,由于需要遍历整个位图来查找停当的文件形貌符。
    poll:虽然也需要在用户空间和内核空间之间复制文件形貌符集合,但由于其数据结构的计划,在处理大量文件形貌符时大概具有更好的性能。然而,如果监控的文件形貌符数量非常大,仍然大概碰到性能瓶颈。
  • 超时精度:
    select:超时参数是一个整数值,表现调用应该等待的秒数,精度较低。
    poll:超时参数是一个毫秒值,提供了更高的时间精度。
  • 移植性:
    两者都具有良好的可移植性,可以在差别的Unix-like体系中使用。但需要注意的是,在某些特定体系或环境下,poll的支持大概不如select广泛。
综上所述,poll和select在Linux体系中都扮演着重要的角色,但在具体使用时需要根据应用场景、文件形貌符数量、性能要求等因素进行选择。
poll的原理

poll函数是Linux体系中的一个重要体系调用,用于监控多个文件形貌符(file descriptors)的状态变革。下面从参数和底层原理两个方面对poll函数进行简要叙述。
  1. #include <poll.h>  
  2. int poll(struct pollfd fds[], nfds_t nfds, int timeout);
复制代码
fds:这是一个指向struct pollfd结构体数组的指针。每个pollfd结构体代表了一个要监控的文件形貌符及其感爱好的事件。pollfd结构体的界说通常如下:
  1. c
  2. struct pollfd {  
  3.     int fd;          // 文件描述符  
  4.     short events;    // 等待的事件  
  5.     short revents;   // 实际发生了的事件  
  6. };
复制代码
fd:要监控的文件形貌符。
events:请求监控的事件,可以是读、写、异常等多种事件的组合。
revents:函数返回时,由内核设置,表现实际发生的事件。
nfds:指定了fds数组中元素的数量,即要监控的文件形貌符的总数。
timeout:指定了poll调用阻塞的时间,以毫秒为单位。
如果timeout为正数,poll将等待指定的毫秒数。
如果timeout为0,poll将立即返回,不阻塞。
如果timeout为-1,poll将无穷期地等待,直到有事件发生。
   poll函数的底层原理
  poll函数的底层实现原理主要基于等待队列。当调用poll函数时,它会遍历传入的pollfd结构体数组,为每个感爱好的文件形貌符注册一个等待事件。这些等待事件会被挂接到内核中相应的等待队列上。
等待队列:内核中的每个文件形貌符都大概关联有一个或多个等待队列,用于存放等待该文件形貌符上特定事件发生的进程或线程。
轮询与阻塞:poll函数会轮询检查每个文件形貌符的等待队列,检察是否有事件发生。
如果有文件形貌符上发生了感爱好的事件,poll函数会立即返回,并将这些事件记录在对应pollfd结构体的revents字段中。
如果没有任何文件形貌符上发生事件,而且timeout参数指定了非零值,poll函数会进入阻塞状态,直到超时时间到达或至少有一个文件形貌符上发生了事件。
   返回与错误处理:
  当有文件形貌符上发生事件或超时时间到达时,poll函数会返回。返回值表现发生了事件的文件形貌符数量(如果大于0),大概在超时时返回0,大概在发生错误时返回-1并设置errno以指示错误缘故原由。
效率与限制:虽然poll函数没有像select那样有文件形貌符数量的硬限制,但在处理大量文件形貌符时,仍然需要将整个pollfd数组在用户空间和内核空间之间复制,这大概会导致性能下降。此外,poll函数在内部仍然需要遍历全部要监控的文件形貌符,因此在大规模并发场景下大概不是最高效的办理方案。
综上所述,poll函数通过轮询和等待队列机制实现了对多个文件形貌符状态的监控,是Linux体系中处理I/O多路复用的重要手段之一。然而,在处理大量文件形貌符时,大概需要考虑其性能限制并探索更高效的办理方案(如epoll)。
   源码
  1. #include <poll.h>
  2. int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  3. // pollfd结构struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */};
复制代码
多路转接包括:用户告诉内核你需要关心什么 && 内核告诉用户你让我关心的fd有哪些停当了。select 用位图,poll 用结构体数组。poll 在用户传给内核的时间,告诉内核需要关心 struct pollfd 结构体中的 fd 中的 events 事件;返回时,内核设置struct pollfd 结构体中的 revents 事件,表现该fd的该事件停当。poll 最大的特点:将输入和输出事件进行分离!
内核怎么知道是关心读事件还是写事件还是其他事件呢?当内核返回用户也一样。 events 和 revents 都是 short 类型,都是 16 个比特位,在 Linux 中,使用比特位传参!把事件设置成位图的情势。

poll 的本质是将读写事件分离,传入用户定的数组元素的大小,通过 events 和 revents 以位图的方式来传递停当和关心标记位的办理方案!
poll的优点

poll 也是多路转接方案的一种,它主要办理的就是 select 中的等待 fd 有上限的问题,以及每次都要对关心的 fd 进行事件重置的问题。
差别与select使用三个位图来表现三个fdset的方式,poll使用一个pollfd的指针实现.
pollfd结构包罗了要监视的event和发生的event,不再使用select“参数-值”传递的方式. 接口使用比select更方便。
poll并没有最大数量限制 (但是数量过大后性能也是会下降).
poll的缺点

poll中监听的文件形貌符数目增多时和select函数一样,poll返回后,需要轮询pollfd来获取停当的形貌符.
每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中.
同时连接的大量客户端在一时候大概只有很少的处于停当状态, 因此随着监视的形貌符数量的增长, 其效率也会线性下降.
poll vs select

poll 本质上是通过一个结构体数组来等待 fd 的,它办理了 select 等待 fd 有上限的问题,怎样办理?_event_fds 这个数组的大小是自己定的,可以定的非常大,大到内存扛不住,此时就是操作体系/内存 软件或硬件的问题了,不是 poll 接口自己的问题。select 等待 fd 有上限的问题,本质上是接口自己的问题,poll 本质上是办理了 select 等待 fd 有上限的问题。
poll 与 select 都需要遍历检测有哪些文件形貌符停当,poll 在内核中需要遍历检测有哪些文件形貌符停当;在用户层需要遍历检测有哪些事件已经停当。
poll 和 select 都避免不开遍历的问题,当fd过多,效率提升不明显。
2.poll开辟多客户端echo服务器

封装套接字接口

  1. #pragma once
  2. #include <iostream>
  3. #include <string>
  4. #include <unistd.h>
  5. #include <cstring>
  6. #include <sys/types.h>
  7. #include <sys/stat.h>
  8. #include <sys/socket.h>
  9. #include <arpa/inet.h>
  10. #include <netinet/in.h>
  11. #include "Log.hpp"
  12. enum
  13. {
  14.     SocketErr = 2,
  15.     BindErr,
  16.     ListenErr,
  17. };
  18. const int g_backlog = 10;
  19. class Sock
  20. {
  21. private:
  22.     int _sockfd;
  23. public:
  24.     Sock()
  25.     {
  26.     }
  27.     ~Sock()
  28.     {
  29.     }
  30. public:
  31.     void Socket()
  32.     {
  33.         _sockfd = socket(AF_INET, SOCK_STREAM, 0);
  34.         if (_sockfd < 0)
  35.         {
  36.             lg(Fatal, "socker error, %s: %d", strerror(errno), errno);
  37.             exit(SocketErr);
  38.         }
  39.     }
  40.     void Bind(uint16_t port)
  41.     {
  42.         struct sockaddr_in local;
  43.         memset(&local, 0, sizeof(local));
  44.         local.sin_family = AF_INET;
  45.         local.sin_port = htons(port);
  46.         local.sin_addr.s_addr = INADDR_ANY;
  47.         if (bind(_sockfd, (struct sockaddr *)&local, sizeof(local)) < 0)
  48.         {
  49.             lg(Fatal, "bind error, %s: %d", strerror(errno), errno);
  50.             exit(BindErr);
  51.         }
  52.     }
  53.     void Listen()
  54.     {
  55.         if (listen(_sockfd, g_backlog) < 0)
  56.         {
  57.             lg(Fatal, "listen error, %s: %d", strerror(errno), errno);
  58.             exit(ListenErr);
  59.         }
  60.     }
  61.     int Accept(std::string *clientip, uint16_t *clientport)
  62.     {
  63.         struct sockaddr_in peer;
  64.         socklen_t len = sizeof(peer);
  65.         int newfd = accept(_sockfd, (struct sockaddr *)&peer, &len);
  66.         if (newfd < 0)
  67.         {
  68.             lg(Warning, "accept error, %s: %d", strerror(errno), errno);
  69.             return -1;
  70.         }
  71.         char ipstr[64];
  72.         inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
  73.         *clientip = ipstr;
  74.         *clientport = ntohs(peer.sin_port);
  75.         return newfd;
  76.     }
  77.     bool Connect(const std::string &ip, const uint16_t &port)
  78.     {
  79.         struct sockaddr_in peer;
  80.         memset(&peer, 0, sizeof(peer));
  81.         peer.sin_family = AF_INET;
  82.         peer.sin_port = htons(port);
  83.         inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));
  84.         int n = connect(_sockfd, (struct sockaddr *)&peer, sizeof(peer));
  85.         if (n == -1)
  86.         {
  87.             std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
  88.             return false;
  89.         }
  90.         return true;
  91.     }
  92.     void CloseFd()
  93.     {
  94.         close(_sockfd);
  95.     }
  96.     int getSocketFd()
  97.     {
  98.         return _sockfd;
  99.     }
  100. };
复制代码
Makefile

  1. poll_server:Main.cc
  2.         g++ -o $@ $^ -std=c++11
  3. .PHONY:clean
  4. clean:
  5.         rm -f poll_server
复制代码
主函数

  1. #include "PollServer.hpp"
  2. #include <memory>
  3. int main()
  4. {
  5.     std::unique_ptr<PollServer> svr(new PollServer());
  6.     svr->Init();
  7.     svr->Start();
  8.     return 0;
  9. }
复制代码
日志服务

  1. #pragma once
  2. #include <iostream>
  3. #include <time.h>
  4. #include <stdarg.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <fcntl.h>
  8. #include <unistd.h>
  9. #include <stdlib.h>
  10. #define SIZE 1024
  11. #define Info 0
  12. #define Debug 1
  13. #define Warning 2
  14. #define Error 3
  15. #define Fatal 4
  16. #define Screen 1
  17. #define Onefile 2
  18. #define Classfile 3
  19. #define LogFile "log.txt"
  20. class Log
  21. {
  22. private:
  23.     int printMethod;
  24.     std::string path;
  25. public:
  26.     Log()
  27.     {
  28.         printMethod = Screen;
  29.         path = "./";
  30.     }
  31.     void Enable(int method)
  32.     {
  33.         printMethod = method;
  34.     }
  35.     std::string levelToString(int level)
  36.     {
  37.         switch (level)
  38.         {
  39.         case Info:
  40.             return "Info";
  41.         case Debug:
  42.             return "Debug";
  43.         case Warning:
  44.             return "Warning";
  45.         case Error:
  46.             return "Error";
  47.         case Fatal:
  48.             return "Fatal";
  49.         default:
  50.             return "None";
  51.         }
  52.     }
  53.     /*
  54.      void logmessage(int level, const char *format, ...)
  55.         {
  56.             time_t t = time(nullptr);
  57.             struct tm *ctime = localtime(&t);
  58.             char leftbuffer[SIZE];
  59.             snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
  60.                      ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
  61.                      ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
  62.             va_list s;
  63.             va_start(s, format);
  64.             char rightbuffer[SIZE];
  65.             vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
  66.             va_end(s);
  67.             // 格式:默认部分+自定义部分
  68.             char logtxt[SIZE * 2];
  69.             snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
  70.             // printf("%s", logtxt);
  71.             printLog(level, logtxt);
  72.         }
  73.     */
  74.    // lg(Warning, "accept error, %s: %d", strerror(errno), errno);
  75.     void operator()(int level, const char *msg_format, ...)
  76.     {
  77.         time_t timestamp = time(nullptr);
  78.         struct tm *ctime = localtime(&timestamp);
  79.         //level 年月日
  80.         char leftbuffer[SIZE];
  81.         snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
  82.                  ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
  83.                  ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
  84.         //自定义msg
  85.         va_list arg_list;//存储可变参数列表信息
  86.         va_start(arg_list, msg_format);//初始化 使其指向函数参数列表中format参数之后的第一个可变参数
  87.         char rightbuffer[SIZE];
  88.         vsnprintf(rightbuffer, sizeof(rightbuffer), msg_format, arg_list);
  89.         va_end(arg_list);//清理va_list变量
  90.         // 格式:默认部分+自定义部分
  91.         char log_content[SIZE * 2];
  92.         snprintf(log_content, sizeof(log_content), "%s %s", leftbuffer, rightbuffer);
  93.         // printf("%s", logtxt); // 暂时打印
  94.         printLog(level, log_content);
  95.     }
  96.     void printLog(int level, const std::string &log_content)
  97.     {
  98.         switch (printMethod)
  99.         {
  100.         case Screen:
  101.             std::cout << log_content << std::endl;
  102.             break;
  103.         case Onefile:
  104.             printOneFile(LogFile, log_content);
  105.             break;
  106.         case Classfile:
  107.             printClassFile(level, log_content);
  108.             break;
  109.         default:
  110.             break;
  111.         }
  112.     }
  113.     void printOneFile(const std::string &log_filename, const std::string &log_content)
  114.     {
  115.         //path = "./"; #define LogFile "log.txt"
  116.         std::string _logFilename = path + log_filename;
  117.         int fd = open(_logFilename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
  118.         if (fd < 0)
  119.             return;
  120.         write(fd, log_content.c_str(), log_content.size());
  121.         close(fd);
  122.     }
  123.     void printClassFile(int level, const std::string &log_content)
  124.     {
  125.         //#define LogFile "log.txt"
  126.         std::string filename = LogFile;
  127.         filename += ".";
  128.         filename += levelToString(level); // "log.txt.Debug"
  129.         printOneFile(filename, log_content);
  130.     }
  131.     ~Log()
  132.     {
  133.     }
  134. };
  135. Log lg;
  136. /*
  137. int sum(int n, ...)
  138. {
  139.     va_list s; // char*
  140.     va_start(s, n);
  141.     int sum = 0;
  142.     while(n)
  143.     {
  144.         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
  145.         n--;
  146.     }
  147.     va_end(s); //s = NULL
  148.     return sum;
  149. }
  150. */
复制代码
聊天服务器

  1. #pragma once
  2. #include <iostream>
  3. #include <poll.h>
  4. #include <sys/time.h>
  5. #include "Socket.hpp"
  6. using namespace std;
  7. static const uint16_t defaultport = 8888;
  8. static const int fd_num_max = 64;
  9. int defaultFd = -1;
  10. int non_event = 0;
  11. class PollServer
  12. {
  13. private:
  14.     Sock _listensock;
  15.     uint16_t _port;
  16.     struct pollfd _event_fds[fd_num_max]; // 结构体数组
  17.     // struct pollfd *_event_fds; 动态数组 可扩容
  18.     // int fd_array[fd_num_max];
  19.     // int wfd_array[fd_num_max];
  20. public:
  21.     PollServer(uint16_t port = defaultport)
  22.         : _port(port)
  23.     {
  24.         for (int i = 0; i < fd_num_max; i++)
  25.         {
  26.             _event_fds[i].fd = defaultFd;
  27.             _event_fds[i].events = non_event;
  28.             _event_fds[i].revents = non_event;
  29.         }
  30.     }
  31.     bool Init()
  32.     {
  33.         _listensock.Socket();
  34.         _listensock.Bind(_port);
  35.         _listensock.Listen();
  36.         return true;
  37.     }
  38.     void Accepter()
  39.     {
  40.         // 连接事件就绪
  41.         std::string clientip;
  42.         uint16_t clientport = 0;
  43.         int sock = _listensock.Accept(&clientip, &clientport); // 会不会阻塞在这里?不会
  44.         if (sock < 0)
  45.             return;
  46.         lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);
  47.         // sock -> _event_fds[]
  48.         int pos = 1;
  49.         for (; pos < fd_num_max; pos++) // 第二个循环
  50.         {
  51.             if (_event_fds[pos].fd != defaultFd)
  52.                 continue;
  53.             else
  54.                 break;
  55.         }
  56.         if (pos == fd_num_max)
  57.         {
  58.             lg(Warning, "server is full, close %d now!", sock);
  59.             close(sock);
  60.             // 也可以不关闭fd 扩容存fd
  61.         }
  62.         else
  63.         {
  64.              _event_fds[pos].fd = sock;
  65.             _event_fds[pos].events = POLLIN;
  66.             _event_fds[pos].revents = non_event;
  67.             PrintFd();
  68.         }
  69.     }
  70.     void Recver(int fd, int pos)
  71.     {
  72.         char buffer[1024];
  73.         ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // 需要考虑是否是完整数据包问题(此处忽略)
  74.         if (n > 0)
  75.         {
  76.             buffer[n] = 0;
  77.             cout << "get a messge: " << buffer << endl;
  78.         }
  79.         else if (n == 0)
  80.         {
  81.             lg(Info, "client quit, me too, close fd is : %d", fd);
  82.             close(fd);
  83.             _event_fds[pos].fd = defaultFd;
  84.         }
  85.         else
  86.         {
  87.             lg(Warning, "recv error: fd is : %d", fd);
  88.             close(fd);
  89.             _event_fds[pos].fd = defaultFd;
  90.         }
  91.     }
  92.     void Dispatcher()
  93.     {
  94.         for (int i = 0; i < fd_num_max; i++) // 这是第三个循环
  95.         {
  96.             int fd = _event_fds[i].fd;
  97.             if (fd == defaultFd)
  98.                 continue;
  99.             if (_event_fds[i].revents & POLLIN)
  100.             {
  101.                 if (fd == _listensock.getSocketFd())
  102.                     Accepter(); // 连接管理器
  103.                 else
  104.                     Recver(fd, i); // non listenfd
  105.             }
  106.         }
  107.     }
  108.     void Start()
  109.     {
  110.         _event_fds[0].fd = _listensock.getSocketFd();
  111.         _event_fds[0].events = POLLIN;
  112.         int timeout = 3000; // 3s
  113.         for (;;)
  114.         {
  115.             int n = poll(_event_fds, fd_num_max, timeout);
  116.             switch (n)
  117.             {
  118.             case 0:
  119.                 cout << "time out... " << endl;
  120.                 break;
  121.             case -1:
  122.                 cerr << "poll error" << endl;
  123.                 break;
  124.             default:
  125.                 // 有事件就绪了
  126.                 cout << "get a new link!!!!!" << endl;
  127.                 Dispatcher();
  128.                 break;
  129.             }
  130.         }
  131.     }
  132.     void PrintFd()
  133.     {
  134.         cout << "online fd list: ";
  135.         for (int i = 0; i < fd_num_max; i++)
  136.         {
  137.             if (_event_fds[i].fd == defaultFd)
  138.                 continue;
  139.             cout << _event_fds[i].fd << " ";
  140.         }
  141.         cout << endl;
  142.     }
  143.     ~PollServer()
  144.     {
  145.         _listensock.CloseFd();
  146.     }
  147. };
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

梦应逍遥

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表