Socket程式設計 非阻塞connect

2021-08-08 20:27:23 字數 3702 閱讀 2796

閱讀skynet原始碼的過程中,發現一種非阻塞connect方式。以前不知道,這次好好學習一下。

文章參考自:非阻塞connect編寫方法介紹

(董的部落格)

tcp連線的建立涉及到乙個三次握手的過程,鑑於rtt波動範圍很大,從區域網的幾個毫秒到幾百個毫秒甚至廣域網上的幾秒。這段時間內,我們可以執行其他處理工作,以便做到並行。因此,非阻塞connect可以為我所用。

1、fcntl

fcntl函式可執行各種描述符的控制操作,對於socket描述符,可以設定其io為阻塞或非阻塞。預設情況下connect為阻塞,現修改為非阻塞:

int flag = fcntl(fd, f_getfl);	//獲取當前flag

if ( -1 == flag )

fcntl(fd, f_setfl, flag | o_nonblock); //設定為非阻塞

2、connect

對於阻塞式套接字,呼叫connect函式將激發tcp的三次握手過程,而且僅在連線建立成功或者出錯時才返回;對於非阻塞式套接字,如果呼叫connect函式返回-1(表示出錯),且錯誤為einprogress,表示連線建立中,尚未完成;如果返回0,則表示連線已經建立。

3、epoll

epoll作為linux下出色的io多路復用工具,它可以讓核心監控多個socket。使用epoll與非阻塞connect相互配合,可以達到非同步建立tcp連線的目的。需要注意:

[1] 當連線成功建立時,connect描述符變成可寫; 

[2] 當連線建立遇到錯誤時,描述符變為即可讀,也可寫,遇到這種情況,可呼叫getsockopt函式檢視連線狀態。

於是,非阻塞connect步驟如下:

1)建立socket,設定為非阻塞;

2)呼叫connect函式,如果返回0,則連線建立;如果返回-1,檢查errno ,如果值為 einprogress,表明連線正在建立,否則表明連線出錯;

3)將connect用的fd置於epoll監控下;

4)待epoll監測到fd可寫,同時用getsockopt檢視連線狀態,如沒有錯誤,表明連線建立成功。

乙個簡單的例子如下:

客戶端:

#include #include #include #include #include #include #include #include int main()

int status = -1;

int connect_fd = -1;

for (ai_ptr = ai_list; ai_ptr != null; ai_ptr = ai_ptr -> ai_next)

// 檢視getaddrinfo函式後返回的目的ip和port,注意各種轉換。inet_ntop、ntohs。

char buffer[100];

struct sockaddr * addr = ai_ptr->ai_addr;

void * sin_addr = (ai_ptr->ai_family == af_inet) ? (void*)&((struct sockaddr_in *)addr)->sin_addr : (void*)&((struct sockaddr_in6 *)addr)->sin6_addr;

if (inet_ntop(ai_ptr->ai_family, sin_addr, buffer, sizeof(buffer)))

int flag = fcntl(connect_fd, f_getfl);

if (-1 == flag)

fcntl(connect_fd, f_setfl, flag | o_nonblock);

status = connect(connect_fd, ai_ptr -> ai_addr, ai_ptr -> ai_addrlen);

if(status != 0 && errno != einprogress)

break;

} if (0 == status)

else

if (wait_event[i].data.fd == connect_fd && (wait_event[i].events & epollout))

}} }

}

伺服器:

#include #include #include #include #include #include #include #include #include const int server_port = 8888;  

int main()

close(server_socket);

return 0;

}

客戶端執行結果:

sin_addr: 127.0.0.1, port: 8888

epoll...

ret: 1

0 1connected!

可以發現,開始第一次 connect 的結果表明連線建立中,後面通過 epoll 監控到tcp連線建立完成。

ps:getsockopt 函式很重要!在不啟動伺服器的情況下,客戶端**是可以正常執行到 getsockopt 函式所在地方的,而且此時epoll反饋connect_fd是可讀可寫的,其實已經出錯了。通過該函式的 err 引數返回錯誤碼 111 可知已經從出錯。

linux下常見的socket錯誤碼:

eacces, eperm:使用者試圖在套接字廣播標誌沒有設定的情況下連線廣播位址或由於防火牆策略導致連線失敗。

eaddrinuse 98:address already in use(本地位址處於使用狀態)

eafnosupport 97:address family not supported by protocol(引數serv_add中的位址非合法位址)

eagain:沒有足夠空閒的本地埠。

ealready 114:operation already in progress(套接字為非阻塞套接字,並且原來的連線請求還未完成)

ebadf 77:file descriptor in bad state(非法的檔案描述符)

econnrefused111:connection refused(遠端位址並沒有處於監聽狀態)

efault:指向套接字結構體的位址非法。

einprogress115:operation now in progress(套接字為非阻塞套接字,且連線請求沒有立即完成)

eintr:系統呼叫的執行由於捕獲中斷而中止。

eisconn 106:transport endpoint is already connected(已經連線到該套接字)

enetunreach 101:network is unreachable(網路不可到達)

enotsock 88:socket operation on non-socket(檔案描述符不與套接字相關)

etimedout 110:connection timed out(連線超時)

此外,這次用了與之前不一樣的初始化addr的方式,getaddrinfo 函式,該函式能夠處理名字到位址以及服務到埠這兩種轉換,返回的是乙個addrinfo的結構(列表)指標。

TCP非阻塞accept和非阻塞connect

非阻塞accept 當乙個已完成的連線準備好被accept的時候,select會把監聽socket標記為可讀。因此,如果用select等待外來的連線時,應該不需要把監聽socket設定為非阻塞模式,因為如果select告訴我們連線已經就緒,accept就不應該被阻塞。不過這樣做的時候有乙個bug 當...

非阻塞socket程式設計

socket程式設計中可能出現阻塞的呼叫有4個 1.write send sendto sendmsg sendv等,如果某個程序呼叫乙個阻塞的tcp套接字 預設設定 如果傳送緩衝區沒有空間,呼叫程序將會睡眠,直到有空間為止。如果tcp套接字是非阻塞的,且沒有空間可寫,則會返回乙個ewouleblo...

非阻塞socket程式設計

阻塞 阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起。該程序被標記為睡眠狀態並被排程出去。函式只有在得到結果之後才會返回。當socket工作在阻塞模式的時候,如果沒有資料的情況下呼叫該函式,則當前執行緒就會被掛起,直到有資料為止。非阻塞 非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函式不...