Linux下TCP非阻塞連接的方法

一、背景

TCP連接函數用於連接服務器端口,若服務器地址不存在時,並不能在短時間內返回連接結果;

二、相關知識

1、連接超時機制

在非阻塞的socket下,調用connect連接函數會一直阻塞到連接建立或者連接失敗,連接建立的時候那時間比較快,而失敗的時候分錯誤情況如連接超時ETIMEOUT將爲75秒到幾分鐘的時間。

2、非阻塞socket

非阻塞socket在調用後立刻返回結果,不會阻塞當前線程,並能夠從當前結果結合 errno 進行判斷執行的情況;

非阻塞socket一般配合I/O複用模型(epoll、select)去監控、處理多個I/O;

三、實現

主要步驟:

1、臨時設置socket屬性爲非阻塞;

2、進行connect函數調用;

3、通過select檢測socket是否可寫;

4、處理異常,恢復socket原屬性;

主入口,輸入地址、端口、超時時間,輸出socket句柄值:

int api_tcp_connect_setup_nonblock(u32 u32ip, u16 u16port, int *pdst_fd, int timeout_sec)
{
    struct sockaddr_in dst_addr = {0};
    struct sockaddr_in lcl_addr = {0};
    memset(&dst_addr, 0, sizeof(struct sockaddr_in));
    memset(&lcl_addr, 0, sizeof(struct sockaddr_in));
    dst_addr.sin_family = AF_INET;
    dst_addr.sin_addr.s_addr = u32ip;
    dst_addr.sin_port = u16port;
    lcl_addr.sin_family = AF_INET;
    lcl_addr.sin_addr.s_addr = 0;
    lcl_addr.sin_port = 0;
    return __connect_setup_nonblock_b(lcl_addr, dst_addr, pdst_fd, timeout_sec);
}
二層嵌套,這個主要就是根據輸入的本地綁定的地址lcl_addr(可選)、連接的目的地址dst_addr去完成連接;
static int __connect_setup_nonblock_b(struct sockaddr_in lcl_addr, 
                                      struct sockaddr_in dst_addr, 
                                      int *pdst_fd, 
                                      int timeout_sec)
{
    int ret      = FAILURE;
    /* socket options */
    int old_option = 0;
    int new_option = 0;

    int reuse = 1;

    /* select */
    fd_set writefds;
    struct timeval timeout;

    /* reset socket error */
    int error = 0;
    socklen_t elen = sizeof(error);
    if ( !pdst_fd ) {
        LOGW("input err\n");
        goto _E1;
    }

    *pdst_fd = socket(AF_INET, SOCK_STREAM, 0);
    if ( *pdst_fd == FAILURE ) {
        LOGE("socket\n");
        goto _E1;
    }
    ret = setsockopt(*pdst_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse));
    if ( ret != SUCCESS ) {
        LOGE("Setsockopt() SO_REUSEADDR failed \n");
        goto _E2;
    }
    ret = bind(*pdst_fd, (struct sockaddr *)&lcl_addr, sizeof(struct sockaddr_in));
    if ( ret != SUCCESS ) {
        LOGE("Bind fail\n");
        goto _E2;
    }

    /* Set nonblocking */
    old_option = fcntl(*pdst_fd, F_GETFL);
    new_option = old_option | O_NONBLOCK;
    fcntl(*pdst_fd, F_SETFL, new_option);

    ret = connect(*pdst_fd, (struct sockaddr*)&dst_addr, sizeof(struct sockaddr_in));
    if ( ret == 0 ) {
        fcntl(*pdst_fd, F_SETFL, old_option);
        LOGD("connect success immediately\n");
        goto _S0;
    }
    else if ( errno != EINPROGRESS )
    {
        /* Connect error */
        LOGE("connect\n");
        goto _E2;
    }

    FD_ZERO(&writefds);
    FD_SET(*pdst_fd, &writefds);
    timeout.tv_sec  = timeout_sec;
    timeout.tv_usec = 0;

    ret = select(*pdst_fd + 1, NULL, &writefds, NULL, &timeout);
    if ( ret <= 0 ) {
        /* timeout or error */
        LOGE("timeout or error\n");
        goto _E2;
    }
    if ( !FD_ISSET(*pdst_fd, &writefds) ) {
        /* Not socketfd found */
        LOGE("Not socketfd found\n");
        goto _E2;
    }
    ret = getsockopt(*pdst_fd, SOL_SOCKET, SO_ERROR, (char *)&error, &elen);
    if ( ret < 0 ) {
        LOGE("getsockopt error\n");
        goto _E2;
    }
    if ( error != 0 ) {
        /* Connect failed after select */
        LOGW("Connect failed after select with the error: %d %s\n", error, strerror(error));
        goto _E2;
    }
    LOGD("connect success after select\n");
    /* Connect success, set to old fnctl option */
    fcntl(*pdst_fd, F_SETFL, old_option);
    goto _S0;
_E2:
    CLOSE_SOCK(*pdst_fd);
_E1:
    return FAILURE;
_S0:
    return SUCCESS;
}
需要注意的就是 connect 之後的 select 處理去判斷連接是否成功;

select 返回 0,表示連接超時;

select 返回1,檢測連接是否可寫,

若套接字不可寫則是有異常;

若套接字可寫則還得使用 SO_ERROR 看看套接字上是否存在有待處理的錯誤如 ECONNREFUSED、ETIMEDOUT;

四、總結

非阻塞connect接口主要用於控制連接的時間,防止異常連接帶來的過長的CPU佔用;

而需要注意的是select 對套接字有所限制,當進程已經開啓1024個fd後,select 接口將會導致錯誤,得考慮換成 epoll_wait 接口;

並且再進一步考慮時,就得考慮可移植性了,網上所說的主要圍繞 getsockopt 函數的可移植問題;


參考文章:

[1] Socket連接超時, http://www.cnblogs.com/highriver/archive/2012/01/16/2324035.html

[2] 關於阻塞非阻塞, http://blog.csdn.net/hguisu/article/details/7453390


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