linux下connect阻塞處理

主要參考https://www.cnblogs.com/Anker/p/6413642.html


一,問題:

我在做一個tcp通信的項(xiàng)目,負(fù)責(zé)客戶端,使用非阻塞connect遇到connect阻塞的問題,使用阻塞connect則遇到 EINPROGRESS的錯誤,需要找到辦法解決。特此記錄以作學(xué)習(xí)所用。

二,非阻塞和阻塞connect

對于阻塞式套接字,調(diào)用connect函數(shù)將激發(fā)TCP的三次握手過程,而且僅在連接建立成功或者出錯時才返回,阻塞時長幾十秒到幾分鐘不等;
對于非阻塞式套接字,如果調(diào)用connect函數(shù)會之間返回-1(表示出錯),且錯誤為EINPROGRESS,表示連接建立,建立啟動但是尚未完成;
如果返回0,則表示連接已經(jīng)建立,這通常是在服務(wù)器和客戶在同一臺主機(jī)上時發(fā)生。

三,解決方法:

select是一種IO多路復(fù)用機(jī)制,它允許進(jìn)程指示內(nèi)核等待多個事件的任何一個發(fā)生,并且在有一個或者多個事件發(fā)生或者經(jīng)歷一段指定的時間后才喚醒它。connect本身并不具有設(shè)置超時功能,如果想對套接字的IO操作設(shè)置超時,可使用select函數(shù)。

對于select和非阻塞connect,注意兩點(diǎn):[1] 當(dāng)連接成功建立時,描述符變成可寫; [2] 當(dāng)連接建立遇到錯誤時,描述符變?yōu)榧纯勺x,也可寫,遇到這種情況,可調(diào)用getsockopt函數(shù)。

四,實(shí)現(xiàn)步驟:

(1) 創(chuàng)建socket,并利用fcntl將其設(shè)置為非阻塞
(2) 調(diào)用connect函數(shù),如果返回0,則連接建立;如果返回-1,檢查errno ,如果值為 EINPROGRESS,則連接正在建立;
(3) 為了控制連接建立時間,將該socket描述符加入到select的可讀可寫集合中,采用select函數(shù)設(shè)定超時;
(4) 如果規(guī)定時間內(nèi)成功建立,則描述符變?yōu)榭蓪懀环駝t,采用getsockopt函數(shù)捕獲錯誤信息;
(5) 恢復(fù)套接字的文件狀態(tài)并返回。

測試代碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char **argv)
{
    if (argc < 3) {
        printf("please input ip and port, for example ./main 120.12.34.56 80.\n");
        return -1;
    }

    char *ipaddr = argv[1];
    unsigned int port = atoi(argv[2]);
    int fd = 0;
    struct sockaddr_in  addr;
    fd_set fdr, fdw;
    struct timeval timeout;
    int err = 0;
    int errlen = sizeof(err);
    fd = socket(AF_INET,SOCK_STREAM,0);
    if (fd < 0) {
        fprintf(stderr, "create socket failed,error:%s.\n", strerror(errno));
        return -1;
    }

    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_pton(AF_INET, ipaddr, &addr.sin_addr);
    /*設(shè)置套接字為非阻塞*/
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0) {
        fprintf(stderr, "Get flags error:%s\n", strerror(errno));
        close(fd);
        return -1;
    }

    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) < 0) {
        fprintf(stderr, "Set flags error:%s\n", strerror(errno));
        close(fd);
        return -1;
    }

    /*阻塞情況下linux系統(tǒng)默認(rèn)超時時間為75s*/
    int rc = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
    if (rc != 0) {
        if (errno == EINPROGRESS) {
            printf("Doing connection.\n");
            /*正在處理連接*/
            FD_ZERO(&fdr);
            FD_ZERO(&fdw);
            FD_SET(fd, &fdr);
            FD_SET(fd, &fdw);
            timeout.tv_sec = 10;
            timeout.tv_usec = 0;
            rc = select(fd + 1, &fdr, &fdw, NULL, &timeout);
            printf("rc is: %d\n", rc);
            /*select調(diào)用失敗*/
            if (rc < 0) {
                fprintf(stderr, "connect error:%s\n", strerror(errno));
                close(fd);
                return -1;
            }

            /*連接超時*/
            if (rc == 0) {
                fprintf(stderr, "Connect timeout.\n");
                close(fd);
                return -1;
            }

            /*[1] 當(dāng)連接成功建立時,描述符變成可寫,rc=1*/
            if (rc == 1 && FD_ISSET(fd, &fdw)) {
                printf("Connect success\n");
                close(fd);
                return 0;
            }

            /*[2] 當(dāng)連接建立遇到錯誤時,描述符變?yōu)榧纯勺x,也可寫,rc=2 遇到這種情況,可調(diào)用getsockopt函數(shù)*/
            if (rc == 2) {
                if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
                    fprintf(stderr, "getsockopt(SO_ERROR): %s", strerror(errno));
                    close(fd);
                    return -1;
                }

                if (err) {
                    errno = err;
                    fprintf(stderr, "connect error:%s\n", strerror(errno));
                    close(fd);
                    return -1;
                }
            }
        }

        fprintf(stderr, "connect failed, error:%s.\n", strerror(errno));
        return -1;
    }

    return 0;
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容