21-非阻塞accept

1. 回憶accept函數

 

之前在10-在accept之前中止連接(連接異常)這一篇中已經討論過在accept之前中止連接的情況了,不過從最終的結果來看,accept並沒有返回錯誤,而是之後調用read讀取已連接套接字時產生了錯誤。

另外,當一個已完成連接正等待被服務端accept時,select會把該連接的套接字作爲讀描述符並返回。這意味着之後的accept就不應該阻塞,但是會引發一個bug:當客戶端跟服務器建立連接之後發送了一個RST包,這時accept會阻塞,直到有下一個已完成的連接準備好被accept爲止。

 

2. accept引發的問題

 

爲了說明這種情況,修改之前TCP服務器的代碼:

//select會返回已連接的描述符
select();
if(FD_ISSET(listenfd , &rset)){

   //sleep是爲了模擬accept阻塞的情況
   sleep(5);
   client_len = sizeof(client_addr);
   connfd = accept(listenfd , (struct sockaddr *)&client_addr , &client_len);
}

如上所示,select返回已連接的描述符之後,接着就阻塞了,導致無法調用accept,通常情況下服務器是沒有問題的。考慮這麼一種情況:如果在建立tcp連接之後,客戶端又馬上發送了RST,就出現了問題。這意味着客戶端在服務器調用accept之前中止了這個連接,但是Berkeley版本的linux不會把這個中止的連接返回給服務端,其他linux版本可能返回EPROTO錯誤,而不會返回ECONNABORTED錯誤。

因爲客戶端發送了RST後,這個已完成的連接被服務器tcp從已完成連接隊列中刪除掉了,我們假設此時隊列中沒有任何其他已完成的連接,那麼之後服務器調用accept就會阻塞,直到已完成連接隊列不爲空爲止,就服務器在aceept處阻塞期間來說,它無法處理其他事情。

 

非阻塞accept實現

爲了防止accept阻塞,當select監聽的某個套接字有一個已完成連接正等待被accept時,把監聽的套接字設置爲非阻塞,然後調用accept忽略以下錯誤:

  1. EWOULDBLOCK (Berkeley實現,客戶端中止連接時)、 ECONNABORTED (POSIX實現,客戶中止連接時)
  2. EPROTO(SVR4實現,客戶端中止連接時) 和 EINTR(如果信號被捕獲)

 

實現accept非阻塞:

//設置套接字非阻塞
fcntl(listenfd , F_SETFL , O_NONBLOCK);

while(1){
    //調用select函數
    FD_SET(listenfd , &rset);
    select(listenfd +1 , &rfds , NULL , NULL , ...);
    if(FD_ISSET(listenfd , &rset)){
    client_len = sizeof(client_addr);
    connfd = accept(listenfd , (struct sockaddr *)&client_addr , &client_len);
    if(connfd < 0){
        //忽略EWOULDBLOCK錯誤,繼續循環
        if(errno == EWOULDBLOCK)
            continue;
        perror("accept");
        exit(-1);
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章