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