? ? ? ? 在之前我們講過select是最古老的多路轉接方案,古老就意味著他不是很方便使用,他需要用戶手動保存fd_set這個位圖結構,來表示讀寫事件的關注與否或者就緒性。
? ? ? ? 而且由于fd_set的大小是固定的,這就意味著他能管理的套接字文件描述符是有限的。針對這兩個問題,人們研究出來了poll,他一方面不需要用戶手動保存位圖了,還能讓poll管理的文件描述符是無限多的,只要你的內存足夠大。
一、Poll的函數原型
參數說明:
fds
- 類型:
struct pollfd *
- 作用:指向?
pollfd
?結構體的數組,每個元素描述一個要監控的文件描述符及其關注的事件。正是由于這傳的參數是一個結構體數組指針,所以理論上只要你的數組足夠大,那么我poll能管理的文件描述符也就越多。nfds
- 類型:
nfds_t
(通常是無符號整數)- 作用:數組?
fds
?中的結構體個數,即要監控的文件描述符數量。timeout
- 類型:
int
- 作用:超時時間(毫秒)。
- 這是一個純粹的輸入型參數,和之前的select不同。
timeout > 0
:最多等待?timeout
?毫秒。timeout == 0
:非阻塞模式,立即返回。timeout < 0
:無限等待,直到有事件發生。
返回值:
> 0
:表示有事件發生,返回值為就緒的文件描述符數量。== 0
:超時,無任何事件發生。< 0
:發生錯誤,返回?-1
?并設置?errno
。
二、Poll的第一個參數是如何做到不用手動保存的呢?
????????可以看到pollfd結構體的成員是這樣的,其中第一個成員表示的是文件描述符的值,而第二個第三個成員則是用戶需要讓內核監控哪些事件、內核返還給用戶哪些事件已經就緒了。
? ? ? ? 之前我們在使用select的時候,fd_set分為讀、寫、和異常,他們三個位圖就管理了所有的文件描述符,所以造就了其復雜性。但是這里把文件描述符的值和事件分開了,一個pollfd就對應一個文件描述符及其關注的事件。從而讓不同文件描述符不用相同的資源,而做到解耦。
三、使用poll的示例
? ? ? ? 這個示例和select的大同小異,僅僅更改select為poll
#pragma once
#include <iostream>
#include "socket.hpp"
#include <memory>
#include<poll.h>
#include "InetAddr.hpp"using namespace socket_ns;class PollServer
{const static int gdefaultfd = -1;const static int gnum=1024;public:PollServer(uint16_t port): _port(port), _listensock(std::make_unique<TcpSocket>()), _timeout(1000){}~PollServer() {}void InitServer(){_listensock->BuildListenSocket(_port);for(int i=0;i<gnum;i++){_events[i].fd=gdefaultfd;_events[i].events=0;_events[i].revents=0;}//把listen添加進來_events[0].fd=_listensock->sockfd();_events[0].events=POLLIN;//對讀事件關心}void Accepter(){// listen套接字得到一個新連接請求-----讀事件就緒// 因為已經就緒了,就不會被阻塞了,即accept不會再等了InetAddr client;SockPtr sock = _listensock->Accepter(&client);if (sock->sockfd() > 0){LOG(DEBUG, "get a new link,client info %s:%d\n", client.Ip().c_str(), client.Port());// 處理(但是這里不能直接處理,如果客戶端不發消息那我仍然阻塞了)// 那么如何得知fd底層的數據是否就緒了呢?仍然是select!這些fd都要由select管理起來// 所以select中的文件描述符會越來越多// 只需要將新獲得的連接套接字放入到fd_array中即可bool flag = false;// 看輔助數組中有沒有空余位置給新fd使用,有則插入for (int pos = 1; pos < gnum; pos++){if (_events[pos].fd == gdefaultfd){flag = true;//將新獲得的套接字,加入到poll管理的pollfd數組中_events[pos].fd = sock->sockfd();_events[pos].events=POLLIN;_events[pos].revents=0;LOG(INFO, "add %d to fd_array success!\n", sock->sockfd());break;}}// 遍歷完成發現是滿的if (flag == false){LOG(WARNING, "Server is Full!\n");// 因為處理不了了,所以直接關閉剛剛獲得到的連接的套接字::close(sock->sockfd());}}}void Handler_IO(int i){char buffer[1024];// 這里讀就不會阻塞了,因為select已經等過了ssize_t n = ::recv(_events[i].fd, buffer, sizeof(buffer) - 1, 0);if (n > 0){buffer[n] = 0;std::cout << "client say#" << buffer << std::endl;// 回復的時候也需要用select找到就緒的,但任何一個sockfd被創建的時候,他的讀寫緩沖區一定是空的,我們之前關心讀事件,是關心// 讀緩沖區有沒有數據,所以讀天然就是不就緒的,但是寫天然是就緒的// std::string echo_str="[server echo info]";// echo_str+=buffer;std::string content = "<html><body></h1>hello world</body></html>";std::string echo_str = "HTTP/1.0 200 OK\r\n";echo_str += "Content-Type: text/html\r\n";echo_str += "Cotent-Length:" + std::to_string(content.size()) + "\r\n\r\n";echo_str += content;::send(_events[i].fd, echo_str.c_str(), echo_str.size(), 0);}// 對方把連接關了else if (n == 0){LOG(INFO, "client quit...\n");// 把該文件描述符從fd_array中拿出來::close(_events[i].fd);_events[i].events=0;_events[i].revents=0;_events[i].fd = gdefaultfd;}else{LOG(ERROR, "recv error!\n");}}// 一定有大量的fd就緒,可能是普通sockfd套接字,也可能是linsten套接字void HandlerEvent(){//變量查看合法的fdfor(int i=0;i<gnum;i++){if(_events[i].fd==gdefaultfd){continue;}//合法的文件描述符,判斷是否就緒int fd=_events[i].fd;short revents=_events[i].revents;if(revents&POLLIN){//讀就緒//判斷是listen套接字就緒還是普通文件就緒,進行任務派發if(fd==_listensock->sockfd()){Accepter();}else{Handler_IO(i);}}if(revents&POLLOUT){//寫就緒}}}void Loop(){while (1){// 3.調用selectstruct timeval timeout = {3, 0};// 由于select是一個輸入輸出型參數,所以必須要有一個輔助數組來保存fd信息,用來重置參數int n = ::poll(_events,gnum,_timeout);switch (n){case 0:LOG(DEBUG, "time out\n");break;case -1:LOG(ERROR, "select error\n");break;default:// // 如果事件已經就緒了,但是我沒有做處理,則底層會一直通知我,告訴我有文件描述符就緒了,所以下一次調用select就不會再判斷了,直接通知LOG(INFO, "have event ready,n: %d\n", n);// 處理HandlerEvent();PrintDebug();break;}// sleep(1);}}void PrintDebug(){std::cout << "fd list:" << std::endl;for (int i = 0; i < gnum; i++){if (_events[i].fd=gdefaultfd){continue;}std::cout << _events[i].fd << " ";std::cout << std::endl;}}private:uint16_t _port;std::unique_ptr<Socket> _listensock;struct pollfd _events[gnum];int _timeout;
};