最近用c++實現了貝葉斯分類演算法,做了個自動識別垃圾資訊的小工具。工具中有個功能,通過綁定指定連接埠,和用戶端通訊。服務端使用的是epoll網路模型。在測試的時候發現,單使用者的情況下用戶端和伺服器通訊正常。但是在多使用者並發的情況下,用戶端和服務端通訊不正常。此時,用戶端能正常的連結,發送資料,但是一直卡在接收資料部分。如下圖:
出現這種問題,是因為不正確的使用了epoll中的ET(edge-trigger)模式。代碼如下:
| 01 |
/************************************************** |
| 05 |
***************************************************/ |
| 06 |
void acceptConn(int srvfd) |
| 08 |
struct sockaddr_in sin; |
| 09 |
socklen_t len = sizeof(struct sockaddr_in); |
| 12 |
int confd = accept(srvfd, (struct sockaddr*)&sin, &len); |
| 16 |
printf("%s: bad accept\n"); |
| 20 |
printf("Accept Connection: %d", confd); |
| 23 |
setNonblocking(confd); |
| 26 |
struct epoll_event event; |
| 27 |
event.data.fd = confd; |
| 28 |
event.events = EPOLLIN|EPOLLET; |
| 29 |
epoll_ctl(epollfd, EPOLL_CTL_ADD, confd, &event); |
注意倒數第二行:event.events = EPOLLIN|EPOLLET; 採用的是ET模式。下面我們來具體說下,問題出在那裡。
在epoll中有兩種模式:level-trigger模式,簡稱LT模式,和edge-trigger模式,簡稱ET模式。其中,LT是預設的工作模式。
這兩種模式的工作方式有些不同。在level-trigger模式下只要某個socket處於readable/writable狀態,無論什麼時候進行epoll_wait都會返回該socket;而edge-trigger模式下只有某個socket從unreadable變為readable或從unwritable變為writable時,epoll_wait才會返回該socket。
在ET模式socket非阻塞的情況下(上面代碼中就是這種情況),多個串連同時到達,伺服器的TCP就緒隊列瞬間積累多個就緒串連,由於是邊緣觸發模式,epoll只會通知一次,accept只處理一個串連,導致TCP就緒隊列中剩下的串連都得不到處理。因此,就出現了上面所提及的問題。
解決辦法是用while迴圈抱住accept調用,處理完TCP就緒隊列中的所有串連後再退出迴圈。如何知道是否處理完就緒隊列中的所有串連呢。accept返回-1並且errno設定為EAGAIN就表示所有串連都處理完。
修改後的代碼如下:
| 01 |
/************************************************** |
| 05 |
***************************************************/ |
| 06 |
void acceptConn(int srvfd) |
| 08 |
struct sockaddr_in sin; |
| 09 |
socklen_t len = sizeof(struct sockaddr_in); |
| 12 |
while((confd = accept(srvfd, (struct sockaddr*)&sin, &len)) > 0) { |