本文始發於我的公衆號:兩猿社,原創不易,求個關注html
使用Webbench對服務器進行壓力測試,建立1000個客戶端,併發訪問服務器10s,正常狀況下有接近8萬個HTTP請求訪問服務器。web
結果顯示僅有7個請求被成功處理,0個請求處理失敗,服務器也沒有返回錯誤。此時,從瀏覽器端訪問服務器,發現該請求也不能被處理和響應,必須將服務器重啓後,瀏覽器端才能訪問正常。編程
<div align=center><img src="http://ww1.sinaimg.cn/large/005TJ2c7ly1gc6n34smiqj30fq05lq3l.jpg" height="200"/> </div>瀏覽器
經過查詢服務器運行日誌,對服務器接收HTTP請求鏈接,HTTP處理邏輯兩部分進行排查。服務器
日誌中顯示,7個請求報文爲:GET / HTTP/1.0
的HTTP請求被正確處理和響應,排除HTTP處理邏輯錯誤。併發
<div align=center><img src="http://ww1.sinaimg.cn/large/005TJ2c7ly1gc6nhkp8gbj30jd0eiq7i.jpg" height="300"/> </div>socket
所以,將重點放在接收HTTP請求鏈接部分。其中,服務器端接收HTTP請求的鏈接步驟爲socket -> bind -> listen -> accept;客戶端鏈接請求步驟爲socket -> connect。函數
#include<sys/socket.h> int listen(int sockfd, int backlog)
從上面的分析中能夠看出,accept若是沒有將隊列中的鏈接取完,就緒隊列中剩下的鏈接都得不處處理,也不能接收新請求,這個特性與壓力測試的Bug十分相似。測試
//對文件描述符設置非阻塞 int setnonblocking(int fd){ int old_option=fcntl(fd,F_GETFL); int new_option=old_option | O_NONBLOCK; fcntl(fd,F_SETFL,new_option); return old_option; } //將內核事件表註冊讀事件,ET模式,選擇開啓EPOLLONESHOT void addfd(int epollfd,int fd,bool one_shot) { epoll_event event; event.data.fd=fd; event.events=EPOLLIN|EPOLLET|EPOLLRDHUP; if(one_shot) event.events|=EPOLLONESHOT; epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event); setnonblocking(fd); } //建立內核事件表 epoll_event events[MAX_EVENT_NUMBER]; int epollfd=epoll_create(5); assert(epollfd!=-1); //將listenfd設置爲ET邊緣觸發 addfd(epollfd,listenfd,false); int number=epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1); if(number<0&&errno!=EINTR) { printf("epoll failure\n"); break; } for(int i=0;i<number;i++) { int sockfd=events[i].data.fd; //處理新到的客戶鏈接 if(sockfd==listenfd) { struct sockaddr_in client_address; socklen_t client_addrlength=sizeof(client_address); //定位accept //從listenfd中接收數據 int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength); if(connfd<0) { printf("errno is:%d\n",errno); continue; } //TODO,邏輯處理 } }
分析代碼發現,web端和服務器端創建鏈接,採用epoll的邊緣觸發模式同時監聽多個文件描述符。spa
從上面的定位分析,問題多是錯誤使用epoll的ET模式。
嘗試將listenfd設置爲LT阻塞,或者ET非阻塞模式下while包裹accept對代碼進行修改,這裏以ET非阻塞爲例。
for(int i=0;i<number;i++) { int sockfd=events[i].data.fd; //處理新到的客戶鏈接 if(sockfd==listenfd) { struct sockaddr_in client_address; socklen_t client_addrlength=sizeof(client_address); //從listenfd中接收數據 //這裏的代碼出現使用錯誤 while ((connfd = accept (listenfd, (struct sockaddr *) &remote, &addrlen)) > 0){ if(connfd<0) { printf("errno is:%d\n",errno); continue; } //TODO,邏輯處理 } } }
將代碼修改後,從新進行壓力測試,問題獲得解決,服務器成功完成75617個訪問請求,且沒有出現任何失敗的狀況。壓測結果以下:
<div align=center><img src="http://ww1.sinaimg.cn/large/005TJ2c7ly1gc6n55oypzj30fv05jaap.jpg" height="200"/> </div>
該Bug的出現,本質上對epoll的ET和LT模式實踐編程較少,沒有深入理解和深刻應用。
若是以爲有所收穫,請順手點個關注吧,大家的舉手之勞對我來講很重要。
<div align=center><img src="http://ww1.sinaimg.cn/large/005TJ2c7ly1gc5hnxxwzqj30ij0cvjt8.jpg" height="350"/> </div>
原文出處:https://www.cnblogs.com/qinguoyi/p/12355519.html