補充關於select在異步(非阻塞)connect中的應用,剛開始搞socket編程的時候
我一直都用阻塞式的connect,非阻塞connect的問題是由於當時搞proxy scan
而提出的呵呵
通過在網上與網友們的交流及查找相關FAQ,總算知道了怎麼解決這一問題.同樣
用select可以很好地解決這一問題.大致過程是這樣的:
1.將打開的socket設爲非阻塞的,可以用fcntl(socket, F_SETFL, O_NDELAY)完
成(有的系統用FNEDLAY也可).
2.發connect調用,這時返回-1,但是errno被設爲EINPROGRESS,意即connect仍舊
在進行還沒有完成.
3.將打開的socket設進被監視的可寫(注意不是可讀)文件集合用select進行監視,
如果可寫,用
getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, sizeof(int));
來得到error的值,如果爲零,則connect成功.
在許多unix版本的proxyscan程序你都可以看到類似的過程,另外在solaris精華
區->編程技巧中有一個通用的帶超時參數的connect模塊.
我們知道,缺省狀態下的套接字都是阻塞方式的,這意味着一個套接口的調用不能立即完成時,進程將進入睡眠狀態,並等待操作完成。對於某些應用,需要及時可控的客戶響應,而阻塞的方式可能會導致一個較長的時間段內,連接沒有響應。造成套接字阻塞的操作主要有recv, send, accept, connect.
下面主要以connect爲例,講講非阻塞的connect的工作原理。當一個TCP套接字設置爲非阻塞後,調用connect,會立刻返回一個EINPROCESS的錯誤。但TCP的三路握手繼續進行,我們將用select函數檢查這個連接是否建立成功。建立非阻塞的connect有下面三個用途:
1. 可以在系統做三路握手的時候做些其它事情,這段時間你可以爲所欲爲。
2. 可以用這個技術同時建立多個連接,在web應用中很普遍。
3. 可以縮短connect的超時時間,多數實現中,connect的超時在75秒到幾分鐘之間,累傻小子呢?
雖然非阻塞的conncet實現起來並不複雜,但我們必須注意以下的細節:
* 即使套接字是非阻塞的,如果連接的服務器是在同一臺主機,connect通常會立刻建立。(connect 返回 0 而不是 EINPROCESS)
* 當連接成功建立時,描述字變成可寫
* 當連接出錯時,描述字變成可讀可寫
例程:定義一個非阻塞的 connect 函數 connect_nonb
int connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
int flags, n, error;
socklen_t len;
fd_set rset, wset;
struct timeval tval;
// 獲取當前socket的屬性, 並設置 noblocking 屬性
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NOBLOCK);
errno = 0;
if ( (n = connect(sockfd, saptr, salen)) < 0)
if (errno != EINPROGRESS)
return (-1);
// 可以做任何其它的操作
if (n == 0)
goto done; // 一般是同一臺主機調用,會返回 0
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
wset = rset; // 這裏會做 block copy
tval.tv_sec = nsec;
tval.tv_usec = 0;
// 如果nsec 爲0,將使用缺省的超時時間,即其結構指針爲 NULL
// 如果tval結構中的時間爲0,表示不做任何等待,立刻返回
if ((n = select(sockfd+1, &rset, &west, NULL,nsec ?tval:NULL)) == 0) {
close(sockfd);
errno = ETIMEOUT;
return (-1);
}
if(FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &west)) {
len = sizeof(error);
// 如果連接成功,此調用返回 0
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
return (-1);
}
else err_quit(“select error: sockfd not set”);
done:
fcntl(sockfd, F_SETFL, flags); // 恢復socket 屬性
if (error) {
close(sockfd);
errno = error;
return (-1);
}
Return (0);
}
注意事項:
* 如果select調用之前,連接已經建立成功,並且有數據發送過來了,這時套接字將是即可讀又可寫,和連接失敗時是一樣的。所以我們必須用getsockopt來檢查套接字的狀態。
* 如果我們不能確定套接字可寫是成功的唯一情況時,我們可以採用以下的調用
(1) 調用getpeername,如果調用失敗,返回ENOTCONN,表示連接失敗
(2) 調用read,長度參數爲0,如果read失敗,表示connect失敗。
(3) 再調用connect一次,其應該失敗,如果錯誤是EISCONN,表示套接字已建立而且連接成功。
* 如果在一個阻塞的套接字上調用的connect,在TCP三路握手前被中斷,如果connect不被自動重啓,會返回EINTR。但是我們不能調用connect等待連接完成,這樣會返回EADDRINUSE,此時我們必須調用select,和非阻塞的方式一樣。