網絡編程之非阻塞socket的連接

補充關於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,和非阻塞的方式一樣。

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