wordpress后臺亂了是怎么回事專業(yè)網(wǎng)站優(yōu)化公司
從零開始使用多路轉(zhuǎn)接IO
- 1 前言
- 1 poll接口介紹
- 3 代碼編寫
- 4 總結(jié)
1 前言
上一篇文章我們學(xué)習(xí)了多路轉(zhuǎn)接中的Select,其操作很簡單,但有一些缺陷:
- 每次調(diào)用 select,都需要手動設(shè)置 fd 集合, 從接口使用角度來說也非常不便。
- 每次調(diào)用 select, 都需要把 fd 集合從用戶態(tài)拷貝到內(nèi)核態(tài), 這個開銷在 fd 很多時會很大。這個是多路轉(zhuǎn)接IO無法避免的問題!
- 同時每次調(diào)用 select 都需要在內(nèi)核遍歷傳遞進來的所有 fd,這個開銷在 fd 很多時很大。
- select 支持的文件描述符數(shù)量太小!雖然操作系統(tǒng)中文件描述符也有限制,但是這是操作系統(tǒng)的缺陷。同樣select也是缺點
而poll方案可以解決其中的兩個缺點:
- select支持的文件描述符少,poll理論上可以支持無限個文件描述符。
- select每次調(diào)用接口都需要手動設(shè)置fd集合,poll不需要!
那么接下來我們就來看poll是怎樣實現(xiàn)的。
1 poll接口介紹
首先poll的作用與select一模一樣:等待多個文件描述符!只負責(zé)等待!
我們來看看poll接口:
OLL(2) Linux Programmer's Manual POLL(2)NAMEpoll, ppoll - wait for some event on a file descriptorSYNOPSIS#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);#define _GNU_SOURCE /* See feature_test_macros(7) */#include <signal.h>#include <poll.h>int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask);
poll接口中只有三個參數(shù):
struct pollfd *fds
:這時一個文件描述符數(shù)組,其中每個元素是一個結(jié)構(gòu)體,其中包含文件描述符,需要處理的事件類型。nfds_t nfds
:表示文件描述符的數(shù)量!timeout
:輸入性參數(shù),這里直接采用的是毫秒,不使用結(jié)構(gòu)體!等于0時是非阻塞IO,等于-1時是阻塞IO!- 返回值表示是否成功:大于0 即有n個就緒了;等于0表示超時了;小于0就是poll出錯了!
我們來看看struct pollfd
內(nèi)部是怎么樣的 :
struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */};
我們對比一下select,select需要傳入三個事件集,輸入輸出性參數(shù),每次都會發(fā)生改變!所以才需要每次調(diào)用都要進行初始化。而poll使用一個結(jié)構(gòu)體,對于這個文件描述符有兩種事件:requested events 與 returned events
!輸入輸出并不互相干擾!那么就解決了select需要不斷初始化的問題。
那么事件類型有哪些呢?
宏定義 | 描述 |
---|---|
POLLIN | 普通或優(yōu)先級帶數(shù)據(jù)可讀 |
POLLRDNORM | 同POLLIN |
POLLRDBAND | 數(shù)據(jù)可讀(優(yōu)先級帶數(shù)據(jù)) |
POLLPRI | 高優(yōu)先級數(shù)據(jù)可讀 |
POLLOUT | 普通數(shù)據(jù)可寫 |
POLLWRNORM | 同POLLOUT |
POLLWRBAND | 數(shù)據(jù)可寫(優(yōu)先級帶數(shù)據(jù)) |
POLLERR | 發(fā)生錯誤 |
POLLHUP | 掛起,對方關(guān)閉連接 |
POLLNVAL | 描述字不是一個打開的文件 |
這些都是宏定義,short events;
是一個16位位圖,可以通過宏定義進行匹配設(shè)置!我們想要查看哪些事件,或者有哪些事件就緒了,就都可以通過位運算進行判斷就可以了!
通過結(jié)構(gòu)體的兩個位圖:
- 用戶就可以告訴內(nèi)核需要幫我們對fd的哪些事件進行等待了
- 內(nèi)核也可以通過位圖告訴用戶fd的哪些事件就緒了!
3 代碼編寫
我們僅僅需要對select的代碼做出一些修改即可:
首先,poll需要一個struct pollfd數(shù)組,這里儲存需要處理的fd。初始化事遍歷進行將對應(yīng)fd設(shè)置為-1,事件設(shè)置為0,將listen套接字加入就可以:
void Initserver(){// 對數(shù)組進行初始化for (int i = 0; i < gnum; i++){fd_array[i].fd = gdefault;fd_array[i].events = 0;fd_array[i].revents = 0;}// 加入監(jiān)聽套接字fd_array[0].fd = _listensock->GetSockfd();fd_array[0].events = POLLIN;}//...// pollstruct pollfd fd_array[gnum];
然后對Loop函數(shù)進行修改,我們不在需要對數(shù)據(jù)遍歷更新rfds了,這樣代碼看起來就整潔了許多!
void Loop(){// 進入服務(wù)while (true){// 創(chuàng)建timeoutint timeout = 1000;// 進行selectint n = ::poll(fd_array, gnum, timeout);switch (n){case 0:// 超時LOG(DEBUG, "timeout \n");break;case -1:// 出錯了LOG(ERROR, "select error\n");break;default:// 正常LOG(INFO, "have event ready: n = %d\n", n);// 處理事件HandlerEvent();PrintDebug();break;}}}
接下來就是HandlerEvent函數(shù),進行判斷的策略依然是遍歷,這里只關(guān)心讀事件:
void HandlerEvent(){// 遍歷fd_array判斷是否有就緒的新事件for (int i = 0; i < gnum; i++){if (fd_array[i].fd == gdefault)continue;// 如果有新事件if (fd_array[i].revents & POLLIN){// 進行判斷是scokfd 還是普通fdif (fd_array[i].fd == _listensock->GetSockfd()){Accepter();}// 普通fd 進行正常讀寫else{HandlerIO(fd_array[i]);}}}}
然后就是對于普通套接字和監(jiān)聽套接字的處理,針對數(shù)組進行稍微修改即可:
void Accepter(){// 連接事件就緒InetAddr addr;int sockfd = _listensock->Accepter(&addr); // 已經(jīng)就緒 ,不會阻塞// 這時會得到一個新連接if (sockfd > 0){LOG(DEBUG, "get a new link , client info %s:%d\n", addr.Ip().c_str(), addr.Port());// 將新獲取的fd加入到數(shù)組中LOG(INFO, "get new fd :%d\n", sockfd);bool flag = false;for (int i = 0; i < gnum; i++){if (fd_array[i].fd == gdefault){flag = true;fd_array[i].fd = sockfd;fd_array[i].events = POLLIN;break;}elsecontinue;}if (flag == false){LOG(WARNING, "fd_array have fill!\n");return;// 可以進行擴容}}}void HandlerIO(struct pollfd &sp){char buffer[1024];int n = ::recv(sp.fd, buffer, sizeof(buffer) - 1, 0);if (n > 0){// 讀取到了數(shù)據(jù)buffer[n] = 0;std::string echo_str = "[client say]#";echo_str += buffer;std::cout << echo_str << std::endl;// 返回一個報文std::string content = "<html><body><h1>hello bite</h1></body></html>";std::string ret_str = "HTTP/1.0 200 OK\r\n";ret_str += "Content-Type: text/html\r\n";ret_str += "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";ret_str += content;// echo_str += buffer;::send(sp.fd, ret_str.c_str(), ret_str.size(), 0); // 臨時方案}else if (n == 0){// 此時fd退出了LOG(INFO, "fd:%d quit!\n", sp.fd);::close(sp.fd);sp.fd = gdefault;sp.events = 0;sp.revents = 0;}else{LOG(ERROR, "recv error! errno:%d\n", errno);::close(sp.fd);sp.fd = gdefault;}}
來看效果:
很好的實現(xiàn)了我們的需求!代碼也比select更加的簡單了!
4 總結(jié)
Poll的底層其實也是遍歷,對我們傳入的數(shù)據(jù)進行遍歷,這樣的效率其實比select并不能高出太多!也就是說poll依然有這樣的缺點:
- 每次調(diào)用 select, 都需要把 fd 集合從用戶態(tài)拷貝到內(nèi)核態(tài), 這個開銷在 fd 很多時會很大。這個是多路轉(zhuǎn)接IO無法避免的問題!
- 同時每次調(diào)用 select 都需要在內(nèi)核遍歷傳遞進來的所有 fd,這個開銷在 fd 很多時很大。
這樣poll 的處境就很尷尬,沒有select資歷早,適配性不如select。性能又比不過epoll!
下一篇文章我們來學(xué)習(xí)epoll!