朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型
? ? ? ? 在閱讀完《樸素、Select、Poll和Epoll網(wǎng)絡(luò)編程模型實現(xiàn)和分析——Select模型》和《樸素、Select、Poll和Epoll網(wǎng)絡(luò)編程模型實現(xiàn)和分析——Poll模型》兩篇文章后,我們發(fā)現(xiàn)一個問題,不管select函數(shù)還是poll函數(shù)都不夠智能,它們只能告訴我們成功、失敗或者超時。如果成功,我們需要遍歷整個數(shù)組去檢查哪些socket需要被處理。對于性能有嚴(yán)格要求的服務(wù)器來說,這種浪費的行為是不可容忍的。而本文介紹的Epoll模型就完美的解決了這個問題。(轉(zhuǎn)載請指明出于breaksoftware的csdn博客)
? ? ? ? 和Poll模型類似,Epoll模型對同時處理的Socket沒有限制。但是我們使用Epoll模型時需要定義epoll_event類型的結(jié)構(gòu)體數(shù)組
struct epoll_event ev, events[SOCKET_LIST_COUNT];
? ? ? ? 創(chuàng)建Socket、綁定端口和開始監(jiān)聽等操作和《樸素、Select、Poll和Epoll網(wǎng)絡(luò)編程模型實現(xiàn)和分析——Select模型》一文中一致,本文就不再列出代碼。
? ? ? ? 和其他幾種模式不同,epoll模型需要創(chuàng)建一個epoll文件描述符。
int epfd, nfds;epfd = epoll_create1(0);
? ? ? ? 之后我們將對該epoll文件描述符進(jìn)行操作。在進(jìn)行操作之前,我們先了解下EPOLL的模式:ET和LT模式。這塊內(nèi)容網(wǎng)上很多,我就大概說下。
? ? ? ? ET是邊界觸發(fā)(edge-triggered),它的特點是一旦被監(jiān)控的文件描述符狀態(tài)發(fā)生改變,就會通知應(yīng)用層,應(yīng)用層需要“一次性”完成完該事件對應(yīng)的操作(讀或者寫等)。如果沒有全部完成(比如讀/寫了一半),ET模式就認(rèn)為是全部完成了,故不再通知該文件描述符本次事件未完成。它是EPOLL模型中第一個被開發(fā)出來的模式,但是由于種種原因,后來又引入了LT模式。
? ? ? ? LT是level-triggered。它并不要求用戶一次性完成本次事件對應(yīng)操作,如果操作沒有完成,則下次還會通知應(yīng)用層。所以這種模式是非常安全的。它也是Epoll模型中缺省方式。
? ? ? ? 至于網(wǎng)上很多人說ET模式比LT模式效率高很多,我覺得這個觀點從原理上來說是成立的,但是到實際應(yīng)用時,可能它們的效率是差不多的。但是這些關(guān)于效率的話題,只有測試數(shù)據(jù)才是有力的證據(jù),其他都是理論推理。
? ? ? ? 再回到我們的程序上來,我們定義之后接入的socket需要關(guān)注的消息以及模式,我們暫且使用ET模式吧。
in_events = EPOLLIN | EPOLLET;out_events = EPOLLOUT | EPOLLET;
? ? ? ? 下一步,我們將創(chuàng)建的監(jiān)聽socket加入到epoll文件描述符關(guān)聯(lián)的信息中
ev.data.fd = listen_sock;ev.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev);
? ? ? ? 此時,我們讓監(jiān)控的事件events是EPOLLIN,即發(fā)生讀事件。且我們使用了缺省模式——LT模式。這樣我們可以保證接入的socket將不會因為意外而被丟棄,從而盡可能的保證不丟包。
? ? ? ? 最后,我們還是要在一個死循環(huán)中不停監(jiān)聽相關(guān)事件
while (1) {nfds = epoll_wait(epfd, events, sizeof(events), 500);
? ? ? ? epoll_wait中第一個參數(shù)是epoll文件描述符,第二個參數(shù)是用于保存發(fā)生事件的epoll_event對象數(shù)組;第三個參數(shù)是該數(shù)組的最大個數(shù);第四個參數(shù)是等待的超時時間。注意一下第二個參數(shù),我們傳入的是數(shù)組首地址。當(dāng)被監(jiān)控的socket有被關(guān)注的事件發(fā)生時,events數(shù)組里將保存它的一個副本。這樣就讓發(fā)生了事件的元素保存到該數(shù)組中,而沒有發(fā)生的則不在這個數(shù)組中,之后我們就不用遍歷整個數(shù)組了。epoll_wait函數(shù)在執(zhí)行成功時,將返回填充到events數(shù)組中的元素個數(shù)。于是我們就開始遍歷這個“全部有效”的數(shù)組。
for (index = 0; index < nfds; ++index) {int fd = events[index].data.fd;
? ? ? ? 和Select、Poll模型類似,我們需要區(qū)分文件描述符是否是監(jiān)聽socket。如果是監(jiān)聽socket,則通過accept獲取接入的socket,并使用epoll_ctl將該socket和epoll文件描述符產(chǎn)生關(guān)聯(lián)。
if (fd == listen_sock) {int new_sock;new_sock = accept(fd, NULL, NULL);if (new_sock < 0) {if (errno == EMFILE) {}else {perror("accept error");exit(EXIT_FAILURE);}}else {request_add(1);ev.data.fd = new_sock;ev.events = in_events;epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock, &ev);}}
? ? ? ? 如果不是監(jiān)聽socket,那就是之后接入的socket。我們需要判斷下發(fā)生的事件類型,如果是發(fā)生了可讀事件,則我們?nèi)プx該文件描述符,并使用epoll_ctl將該文件描述符的狀態(tài)改成“可寫”。
else {if (events[index].events & EPOLLIN) {if (0 != server_read(fd)) {close(fd);epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);}else {events[index].events = out_events;epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &events[index]);}}
? ? ? ? 如果是接入的socket是寫狀態(tài),我們就寫入數(shù)據(jù)。最后關(guān)閉該連接,同時使用epoll_ctl將該文件描述符和epoll文件描述符斷開關(guān)系。
else if (events[index].events & EPOLLOUT) {server_write(fd);close(fd);//memset(&events[index], 0, sizeof(events[index]));//events[index].data.fd = -1;epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);}}}}close(epfd);return 0;
}
? ? ? ? 一切就是這么簡單。我們看下epoll模型的執(zhí)行效率。我們采用和《樸素、Select、Poll和Epoll網(wǎng)絡(luò)編程模型實現(xiàn)和分析——樸素模型》一文中相同的環(huán)境和壓力,看下服務(wù)器的數(shù)據(jù)輸出
? ? ? ? 再看下客戶端輸出
? ? ? ? 可見在當(dāng)前環(huán)境下,每秒可以處理11000左右個請求。可見它的效率的確比Select和Poll模型好。
總結(jié)
以上是生活随笔為你收集整理的朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 朴素、Select、Poll和Epoll
- 下一篇: 朴素、Select、Poll和Epoll