非阻塞connect的作用及代碼示例

connect 函數的調用涉及到TCP連接的三次握手過程,通常阻塞的connect 函數會等待三次握手成功或失敗後返回,0成功,-1失敗。如果對方未響應,要隔6s,重發嘗試,可能要等待75s的嘗試並最終返回超時,才得知連接失敗。即使是一次嘗試成功,也會等待幾毫秒到幾秒的時間,如果此期間有其他事務要處理,則會白白浪費時間,而用非阻塞的connect 則可以做到並行,提高效率。

而通常,非阻塞的connect 函數與 select 函數配合使用:在一個TCP套接口被設置爲非阻塞之後調用connect,connect (函數本身返回-1)會立即返回EINPROGRESS或EWOULDBLOCK錯誤,表示連接操作正在進行中,但是仍未完成;同時TCP的三路握手操作繼續進行;在這之後,我們可以調用select來檢查這個鏈接是否建立成功。

若建立連接成功,select的結果中該描述符可寫;若失敗,則可寫可讀,此時可以使用getsockopt獲取錯誤信息。

正常的三次握手時序

在這裏插入圖片描述

(以下內容轉自http://blog.163.com/li_xiang1102/blog/static/60714076201110298170975/)

非阻塞connect有三種用途

  • 我們可以在三路握手的同時做一些其它的處理。connect 操作要花一個往返時間完成,而且可以是在任何地方,從幾個毫秒的局域網到幾百毫秒或幾秒的廣域網,在這段時間內我們可能有一些其他的處理想要執行;
  • 可以用這種技術同時建立多個連接.在Web瀏覽器中很普遍;
  • 由於我們使用select 來等待連接的完成,因此我們可以給select設置一個時間限制,從而縮短connect 的超時時間。在大多數實現中,connect 的超時時間在75秒到幾分鐘之間。有時候應用程序想要一個更短的超時時間,使用非阻塞connect 就是一種方法。

非阻塞connect 聽起來雖然簡單,但是仍然有一些細節問題要處理:

  • 即使套接口是非阻塞的。如果連接的服務器在同一臺主機上,那麼在調用connect 建立連接時,連接通常會立即建立成功,我們必須處理這種情況。
  • 源自Berkeley的實現(和Posix.1g)有兩條與select 和非阻塞IO相關的規則:
    A. 當連接建立成功時,套接口描述符變成可寫;
    B. 當連接出錯時,套接口描述符變成既可讀又可寫。

    所以,套接字可讀並不一定是連接以建立的標誌。

處理非阻塞connect的步驟(重點):

  • 創建socket,返回套接口描述符;
  • 調用fcntl 把套接口描述符設置成非阻塞;
  • 調用connect 開始建立連接;
  • 判斷連接是否成功建立。

判斷連接是否成功建立:

  • 如果connect 返回0,表示連接成功(服務器和客戶端在同一臺機器上時就有可能發生這種情況);
  • 調用select 來等待連接建立成功完成;

如果select 返回0,則表示建立連接超時。我們返回超時錯誤給用戶,同時關閉連接,以防止三路握手操作繼續進行下去。如果select 返回大於0的值,則需要檢查套接口描述符是否可寫,如果套接口描述符可寫,則我們可以通過調用getsockopt來得到套接口上待處理的錯誤(SO_ERROR)。如果連接建立成功,這個錯誤值將是0;如果建立連接時遇到錯誤,則這個值是連接錯誤所對應的errno值(比如:ECONNREFUSED,ETIMEDOUT等)。

代碼示例

int conn_nonb(int sockfd, const struct sockaddr_in *saptr, socklen_t salen, int nsec)
{
    int flags, n, error, code;
    socklen_t len;
    fd_set wset;
    struct timeval tval;
 
    flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
 
    error = 0;
    if ((n == connect(sockfd, saptr, salen)) == 0) {
        goto done;
    } else if (n < 0 && errno != EINPROGRESS){
        return (-1);
    }
 
    /* Do whatever we want while the connect is taking place */
 
    FD_ZERO(&wset);
    FD_SET(sockfd, &wset);
    tval.tv_sec = nsec;
    tval.tv_usec = 0;
 
    if ((n = select(sockfd+1, NULL, &wset, 
                    NULL, nsec ? &tval : NULL)) == 0) {
        close(sockfd);  /* timeout */
        errno = ETIMEDOUT;
        return (-1);
    }
 
    if (FD_ISSET(sockfd, &wset)) {
        len = sizeof(error);
        code = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
        /* 如果發生錯誤,Solaris實現的getsockopt返回-1,
         * 把pending error設置給errno. Berkeley實現的
         * getsockopt返回0, pending error返回給error. 
         * 我們需要處理這兩種情況 */
        if (code < 0 || error) {
            close(sockfd);
            if (error) 
                errno = error;
            return (-1);
        }
    } else {
        fprintf(stderr, "select error: sockfd not set");
        exit(0);
    }
 
done:
    fcntl(sockfd, F_SETFL, flags);  /* restore file status flags */
    return (0);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章