socket連接超時問題

一部分  

      把CSDN與中文yahoo翻了底朝天,也沒找到如何設置socket的連接超時的滿意方法,問此問題的兄弟已有一大堆,這裏偶就講一下win下如何設置socket的connect超時。
設置connect的超時很簡單,CSDN上也有人提到過使用select,但卻沒有一個令人滿意與完整的答案。偶所講的也正是select函數,此函數集成在winsock1.1中,簡單點講,"作用使那些想避免在套接字調用過程中被鎖定的應用程序,採取一種有序的方式,同時對多個套接字進行管理"(《Windows網絡編程技術》原話)。使用方法與解釋請見《Windows網絡編程技術》。
在使用此函數前,需先將socket設置爲非鎖定模式,這樣,在connect時,纔會立馬跳過,同時,通常也會產生一個WSAEWOULDBLOCK錯誤,這個錯誤沒關係。再執行select則是真正的超時。

WSADATA wsd;
SOCKET cClient;
int ret;
struct sockaddr_in server;
hostent *host=NULL;

if(WSAStartup(MAKEWORD(2,0),&wsd)){return 0;}
cClient=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(cClient==INVALID_SOCKET){return 0;}
//set Recv and Send time out
int TimeOut=6000; //設置發送超時6秒
if(::setsockopt(cClient,SOL_SOCKET,SO_SNDTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR){
return 0;
}
TimeOut=6000;//設置接收超時6秒
if(::setsockopt(cClient,SOL_SOCKET,SO_RCVTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR){
return 0;
}
//設置非阻塞方式連接
unsigned long ul = 1;
ret = ioctlsocket(cClient, FIONBIO, (unsigned long*)&ul);
if(ret==SOCKET_ERROR)return 0;

//連接
server.sin_family = AF_INET;
server.sin_port = htons(25);
server.sin_addr .s_addr = inet_addr((LPCSTR)pSmtp);
if(server.sin_addr.s_addr == INADDR_NONE){return 0;}

connect(cClient,(const struct sockaddr *)&server,sizeof(server));

//select 模型,即設置超時
struct timeval timeout ;
fd_set r;

FD_ZERO(&r);
FD_SET(cClient, &r);
timeout.tv_sec = 15; //連接超時15秒
timeout.tv_usec =0;
ret = select(0, 0, &r, 0, &timeout);
if ( ret <= 0 )
{
::closesocket(cClient);
return 0;
}
//一般非鎖定模式套接比較難控制,可以根據實際情況考慮 再設回阻塞模式
unsigned long ul1= 0 ;
ret = ioctlsocket(cClient, FIONBIO, (unsigned long*)&ul1);
if(ret==SOCKET_ERROR){
::closesocket (cClient);
return 0;
}

 

--------------------------------------------------------------------------------------------------------------

LINUX下的方法:

 

在阻塞套接字的一般情況下,connect ()直到客戶端對SYN消息的ACK消息到達之前纔會返回。使connect()調用具有超時機制的一個方法是讓套接字成爲非阻塞的套接字體,然後用select()來等待它完成。
[code:1:7901c37cf2]
s = socket(AF_INET, SOCK_STREAM, 0);
//下面獲取套接字的標誌
if ((flags = fcntl(s, F_GETFL, 0)) < 0) {
    //錯誤處理
}

//下面設置套接字爲非阻塞
if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) {
    //錯誤處理
}

if ((retcode = connect(s, (struct sockaddr*)&peer, sizeof(peer)) && 
    errno != EINPROGRESS) {
   //因爲套接字設爲NONBLOCK,通常情況下,連接在connect()返回
   //之前是不會建立的,因此它會返回EINPROGRESS錯誤,如果返回
   //任何其他錯誤,則要進行錯誤處理
}

if (0 == retcode) {  //如果connect()返回0則連接已建立
    //下面恢復套接字阻塞狀態
    if (fcntl(s, F_SETFL, flags) < 0) {
        //錯誤處理
    }

    //下面是連接成功後要執行的代碼
    
    exit(0)
}

FD_ZERO(&rdevents);
FD_SET(s, &rdevents);  //把先前的套接字加到讀集合裏面
wrevents = rdevents;   //寫集合
exevents = rdevents;   //異常集合

tv.tv_sec = 5;  //設置時間爲5秒
tv_tv_usec = 0;

retcode = select(s+1, &rdevents, &wrevents, &exevents, &tv);
if (retcode < 0) {  //select返回錯誤???
    //錯誤處理
}
else if (0 == retcode) {  //select 超時???
    //超時處理
}
esle {
    //套接字已經準備好
    if (!FD_ISSET(s, &rdevents) && !FD_ISSET(s, &wrevents)) {
        //connect()失敗,進行錯處理
    }

    if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
        //getsockopt()失敗,進行錯處理
    }

    if (err != 0) {
        //connect()失敗,進行錯處理
    }

    //到這裏說明connect()正確返回
    //下面恢復套接字阻塞狀態
    if (fcntl(s, F_SETFL, flags) < 0) {
        //錯誤處理
    }

    //下面是連接成功後要執行的代碼
    
    exit(0)

二部分

 

1.首先將標誌位設爲Non-blocking模式,準備在非阻塞模式下調用connect函數
2.調用connect,正常情況下,因爲TCP三次握手需要一些時間;而非阻塞調用只要不能立即完成就會返回錯誤,所以這裏會返回EINPROGRESS,表示在建立連接但還沒有完成。
3.在讀套接口描述符集(fd_set rset)和寫套接口描述符集(fd_set wset)中將當前套接口置位(用FD_ZERO()、FD_SET()宏),並設置好超時時間(struct timeval *timeout)
4.調用select( socket, &rset, &wset, NULL, timeout )
返回0表示connect超時
如果你設置的超時時間大於75秒就沒有必要這樣做了,因爲內核中對connect有超時限制就是75秒。


[From]http://www.ycgczj.com.cn/34733.html
網絡編程中socket的分量我想大家都很清楚了,socket也就是套接口,在套接口編程中,提到超時的概念,我們一下子就能想到3個:發送超時,接收超時,以及select超時(注: select函數並不是只用於套接口的,但是套接口編程中用的比較多),在connect到目標主機的時候,這個超時是不由我們來設置的。不過正常情況下這個超時都很長,並且connect又是一個阻塞方法,一個主機不能連接,等着connect返回還能忍受,你的程序要是要試圖連接多個主機,恐怕遇到多個不能連接的主機的時候,會塞得你受不了的。我也廢話少說,先說說我的方法,如果你覺得你已掌握這種方法,你就不用再看下去了,如果你還不瞭解,我願意與你分享。本文是已在Linux下的程序爲例子,不過拿到Windows中方法也是一樣,無非是換幾個函數名字罷了。
  Linux中要給connect設置超時,應該是有兩種方法的。一種是該系統的一些參數,這個方法我不講,因爲我講不清楚:P,它也不是編程實現的。另外一種方法就是變相的實現connect的超時,我要講的就是這個方法,原理上是這樣的:
 1.建立socket
 2.將該socket設置爲非阻塞模式
 3.調用connect()
 4.使用select()檢查該socket描述符是否可寫(注意,是可寫)
 5.根據select()返回的結果判斷connect()結果
 6.將socket設置爲阻塞模式(如果你的程序不需要用阻塞模式的,這步就省了,不過一般情況下都是用阻塞模式的,這樣也容易管理)
如果你對網絡編程很熟悉的話,其實我一說出這個過程你就知道怎麼寫你的程序了,下面給出我寫的一段程序,僅供參考。
/******************************
* Time out for connect() 
*  Write by Kerl W
******************************/
#include <sys/socket.h>
#include <sys/types.h>
#define TIME_OUT_TIME 20 //connect超時時間20秒
int main(int argc , char **argv)
{
   ………………
   int sockfd = socket(AF_INET, SOCK_STREAM, 0);
   if(sockfd < 0) exit(1);
   struct sockaddr_in serv_addr;
   ………//以服務器地址填充結構serv_addr
   int error=-1, len;
   len = sizeof(int);
   timeval tm;
   fd_set set;
   unsigned long ul = 1;
   ioctl(sockfd, FIONBIO, &ul); //設置爲非阻塞模式
   bool ret = false;
   if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
   {
     tm.tv_set  = TIME_OUT_TIME;
     tm.tv_uset = 0;
     FD_ZERO(&set);
     FD_SET(sockfd, &set);
     if( select(sockfd+1, NULL, &set, NULL, &tm) > 0)
     {
       getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
       if(error == 0) ret = true;
 else ret = false;
    } else ret = false;
  }
 else ret = true;
 ul = 0;
 ioctl(sockfd, FIONBIO, &ul); //設置爲阻塞模式
 if(!ret)
 {
  close( sockfd );
  fprintf(stderr , "Cannot Connect the server!/n");
   return;
   }
  fprintf( stderr , "Connected!/n");
  //下面還可以進行發包收包操作
  ……………
}

  以上代碼片段,僅供參考,也是爲初學者提供一些提示,主要用到的幾個函數,select, ioctl, getsockopt都可以找到相關資料,具體用法我這裏就不贅述了,你只需要在linux中輕輕的敲一個man <函數名>就能夠看到它的用法。
 此外我需要說明的幾點是,雖然我們用ioctl把套接口設置爲非阻塞模式,不過select本身是阻塞的,阻塞的時間就是其超時的時間由調用select 的時候的最後一個參數timeval類型的變量指針指向的timeval結構變量來決定的,timeval結構由一個表示秒數的和一個表示微秒數(long類型)的成員組成,一般我們設置了秒數就行了,把微妙數設爲0(注:1秒等於100萬微秒)。而select函數另一個值得一提的參數就是上面我們用到的fd_set類型的變量指針。調用之前,這個變量裏面存了要用select來檢查的描述符,調用之後,針對上面的程序這裏面是可寫的描述符,我們可以用宏FD_ISSET來檢查某個描述符是否在其中。由於我這裏只有一個套接口描述符,我就沒有使用FD_ISSET宏來檢查調用select之後這個sockfd是否在set裏面,其實是需要加上這個判斷的。不過我用了getsockopt來檢查,這樣纔可以判斷出這個套接口是否是真的連接上了,因爲我們只是變相的用select來檢查它是否連接上了,實際上select檢查的是它是否可寫,而對於可寫,是針對以下三種條件任一條件滿足時都表示可寫的:
1)套接口發送緩衝區中的可用控件字節數大於等於套接口發送緩衝區低潮限度的當前值,且或者i)套接口已連接,或者ii)套接口不要求連接(UDP方式的)
2)連接的寫這一半關閉。
3)有一個套接口錯誤待處理。
這樣,我們就需要用getsockopt函數來獲取套接口目前的一些信息來判斷是否真的是連接上了,沒有連接上的時候還能給出發生了什麼錯誤,當然我程序中並沒有標出那麼多狀態,只是簡單的表示可連接/不可連接。
 下面我來談談對這個程序測試的結果。我針對3種情形做了測試:
1. 目標機器網絡正常的情況
  可以連接到目標主機,並能成功以阻塞方式進行發包收包作業。
2. 目標機器網絡斷開的情況
  在等待設置的超時時間(上面的程序中爲20秒)後,顯示目標主機不能連接。
3. 程序運行前斷開目標機器網絡,超時時間內,恢復目標機器的網絡
在恢復目標主機網絡連接之前,程序一隻等待,恢復目標主機後,程序顯示連接目標主機成功,並能成功以阻塞方式進行發包收包作業。
 以上各種情況的測試結果表明,這種設置connect超時的方法是完全可行的。我自己是把這種設置了超時的connect封裝到了自己的類庫,用在一套監控系統中,到目前爲止,運行還算正常。這種編程實現的connect超時比起修改系統參數的那種方法的有點就在於它只用於你的程序之中而不影響系統。
 
connect 超時,socket connect,socket 超時,socket連接超時設置,connect,mysql connect,connect by,connect by prior,connect player,media connect


關於c/s socket中超時問題的總結
[size=18:ac54d21053]在客戶端與服務器端通過socket連接時,有兩個問題必須考慮
1、connect連接時可能會發生連接不上的情況,需要實現超時退出程序。
2、連接後在接收數據的過程中,可能發生網絡中斷,不能接受數據的情況,需要退出程序。

這兩個問題應該很常見,希望高手給大家詳細地講解一下,謝謝。[/size:ac54d21053]
 
【發表回覆】【查看論壇原帖】【添加到收藏夾】【關閉】

 

--------------------------------------------------------------------------------

 gadfly 回覆於:2003-08-11 14:40:32
這兩個都可以用非阻塞socket,select控制超時

--------------------------------------------------------------------------------

 yuanyawei 回覆於:2003-08-12 09:08:00
我覺得第一種情況用select可以很好解決。
但第二種情況在遇到客戶端直接拔網線的情況時,server端的情況較難判斷,要看內核的參數,linux下較好處理,BSD也沒問題,HP和AIX也能處理,但SCO下就不好辦了(參數老調不好)。

--------------------------------------------------------------------------------

 minsky 回覆於:2003-08-12 10:52:27
1.connect超時:
1)setsockopt();//將socket置爲非阻塞模式;
2)connect();
3)判斷connect()的返回值,一般情況會返回-1,這時你還必須判斷錯誤碼如果是EINPROGRESS,那說明connect還在繼續;如果錯誤碼不是前者那麼就是有問題了,不必往下執行,必須關掉socket;待下次重聯;
4)select();設置好函數中的超時時間,將select()中的read和write項置上,在超時時間內,如果select返回1,即描述字變爲了可寫,那麼連接成功;如果返回2,即描述字變爲即可讀又可寫,那麼出錯;如果返回0,那麼超時;
============================================
2.網絡中斷:
如果你的程序是客戶端.用select檢查描述符的狀態,如果可讀就recv(),根據recv()的返回值來判斷網絡情況;

--------------------------------------------------------------------------------

 calfen 回覆於:2003-12-18 15:18:55
unp上明確說setsockopt只能用在讀寫時候不能用在connect上啊...

--------------------------------------------------------------------------------

 grouploo 回覆於:2004-06-25 23:06:35
/********************************************/
/****   作者::夕君                **/
/****   時間:2004.04.04                     **/
/****   北京金萬維科技 http://www.gnway.com           **/
/*******************************************/
/*此函數實現判斷m_server的m_port端口是否可以連上,超時限制爲nTimeOut秒*/
BOOL ConnectTest(char * m_server,int m_port)
{

        struct hostent* host = NULL;
        struct sockaddr_in saddr;
        unsigned int s = 0;
        BOOL  ret;
        time_t start;
        int error;
        host = gethostbyname (m_server);
        if (host==NULL)return  FALSE;

        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(m_port);
        saddr.sin_addr = *((struct in_addr*)host->h_addr);


        if( (s=socket(AF_INET, SOCK_STREAM, 0))<0){
                return FALSE;
        }


        fcntl(s,F_SETFL, O_NONBLOCK);

        if(connect(s,(struct sockaddr*)&saddr, sizeof(saddr)) == -1) {
                if (errno == EINPROGRESS){// it is in the connect process
                        struct timeval tv;
                        fd_set writefds;
                        tv.tv_sec = m_nTimeOut;
                        tv.tv_usec = 0;
                        FD_ZERO(&writefds);
                        FD_SET(s, &writefds);
                        if(select(s+1,NULL,&writefds,NULL,&tv)>0){
                                int len=sizeof(int);
                               //下面的一句一定要,主要針對防火牆
                                getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len);
                                if(error==0) ret=TRUE;
                                else ret=FALSE;
                        }else   ret=FALSE;//timeout or error happen
                }else ret=FALSE;
        }
        else    ret=TRUE;

        close(s);
        return ret;


}
 

 
 
 
setsockopt函數解析(轉) - [IT]{#timeline}
Tag:IT
int setsockopt (
  SOCKET s,                
  int level,               
  int optname,             
  const char FAR * optval, 
  int optlen               
);

The Windows Sockets setsockopt function sets a socket option.

中文解釋好像是:設置套接字的選項。

先看如下代碼:
setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int))

這裏是設置SockRaw這個套接字的ip選項中的IP_HDRINCL

參考以下資料:

***************************************************************************************************

Linux網絡編程--8. 套接字選項
有時候我們要控制套接字的行爲(如修改緩衝區的大小),這個時候我們就要控制套接字的選項了. 

8.1 getsockopt和setsockopt 
int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen)
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)
level指定控制套接字的層次.可以取三種值:
1)SOL_SOCKET:通用套接字選項.
2)IPPROTO_IP:IP選項.
3)IPPROTO_TCP:TCP選項. 
optname指定控制的方式(選項的名稱),我們下面詳細解釋 
optval獲得或者是設置套接字選項.根據選項名稱的數據類型進行轉換 

選項名稱        說明                  數據類型
========================================================================
            SOL_SOCKET
------------------------------------------------------------------------
SO_BROADCAST      允許發送廣播數據            int
SO_DEBUG        允許調試                int
SO_DONTROUTE      不查找路由               int
SO_ERROR        獲得套接字錯誤             int
SO_KEEPALIVE      保持連接                int
SO_LINGER        延遲關閉連接              struct linger
SO_OOBINLINE      帶外數據放入正常數據流         int
SO_RCVBUF        接收緩衝區大小             int
SO_SNDBUF        發送緩衝區大小             int
SO_RCVLOWAT       接收緩衝區下限             int
SO_SNDLOWAT       發送緩衝區下限             int
SO_RCVTIMEO       接收超時                struct timeval
SO_SNDTIMEO       發送超時                struct timeval
SO_REUSERADDR      允許重用本地地址和端口         int
SO_TYPE         獲得套接字類型             int
SO_BSDCOMPAT      與BSD系統兼容              int
==========================================================================
            IPPROTO_IP
--------------------------------------------------------------------------
IP_HDRINCL       在數據包中包含IP首部          int
IP_OPTINOS       IP首部選項               int
IP_TOS         服務類型
IP_TTL         生存時間                int
==========================================================================
            IPPRO_TCP
--------------------------------------------------------------------------
TCP_MAXSEG       TCP最大數據段的大小           int
TCP_NODELAY       不使用Nagle算法             int
=========================================================================
關於這些選項的詳細情況請查看 Linux Programmer"s Manual 
8.2 ioctl 
ioctl可以控制所有的文件描述符的情況,這裏介紹一下控制套接字的選項. 
int ioctl(int fd,int req,...)
 
==========================================================================
            ioctl的控制選項
--------------------------------------------------------------------------
SIOCATMARK       是否到達帶外標記            int
FIOASYNC        異步輸入/輸出標誌            int
FIONREAD        緩衝區可讀的字節數           int
==========================================================================
詳細的選項請用 man ioctl_list 查看. 

 


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=588497
 
1.closesocket(一般不會立即關閉而經歷TIME_WAIT的過程)後想繼續重用該socket:
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
2. 如果要已經處於連接狀態的soket在調用closesocket後強制關閉,不經歷
TIME_WAIT的過程:
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
3.在send(),recv()過程中有時由於網絡狀況等原因,發收不能預期進行,而設置收發時限:
int nNetTimeout=1000;//1秒
//發送時限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收時限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
4.在send()的時候,返回的是實際發送出去的字節(同步)或發送到socket緩衝區的字節
(異步);系統默認的狀態發送和接收一次爲8688字節(約爲8.5K);在實際的過程中發送數據
和接收數據量比較大,可以設置socket緩衝區,而避免了send(),recv()不斷的循環收發:
// 接收緩衝區
int nRecvBuf=32*1024;//設置爲32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//發送緩衝區
int nSendBuf=32*1024;//設置爲32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
5. 如果在發送數據的時,希望不經歷由系統緩衝區到socket緩衝區的拷貝而影響
程序的性能:
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
6.同上在recv()完成上述功能(默認情況是將socket緩衝區的內容拷貝到系統緩衝區):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
7.一般在發送UDP數據報的時候,希望該socket發送的數據具有廣播特性:
BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
8.在client連接服務器過程中,如果處於非阻塞模式下的socket在connect()的過程中可
以設置connect()延時,直到accpet()被呼叫(本函數設置只有在非阻塞的過程中有顯著的
作用,在阻塞的函數調用中作用不大)
BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));
9.如果在發送數據的過程中(send()沒有完成,還有數據沒發送)而調用了closesocket(),以前我們
一般採取的措施是"從容關閉"shutdown(s,SD_BOTH),但是數據是肯定丟失了,如何設置讓程序滿足具體
應用的要求(即讓沒發完的數據發送出去後在關閉socket)?
struct linger {
u_short l_onoff;
u_short l_linger;
};
linger m_sLinger;
m_sLinger.l_onoff=1;//(在closesocket()調用,但是還有數據沒發送完畢的時候容許逗留)
// 如果m_sLinger.l_onoff=0;則功能和2.)作用相同;
m_sLinger.l_linger=5;//(容許逗留的時間爲5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));


本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/chenguangf/archive/2008/02/19/2107302.aspx

 

發佈了138 篇原創文章 · 獲贊 9 · 訪問量 102萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章