1. 程式人生 > >為套接字設定超時

為套接字設定超時

  unp上講述了以下三種方法:

1.呼叫alarm,它在指定超時期滿時將產生SIGALRM訊號。

2. 使用select為函式設定超時

3.使用SO_RCVTIMEO套接字選項為函式設定超時

(1.1).使用 SIGALRM 訊號為 connect設定超時

static void connect_alarm(int);

int
connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
    Sigfunc *sigfunc; 
    int     n;

    sigfunc = Signal(SIGALRM, connect_alarm);//設定新的訊號處理函式
if (alarm(nsec) != 0)//檢測以前有沒有設定過時鐘,如果有就會覆蓋 err_msg("connect_timeo: alarm was already set"); if ( (n = connect(sockfd, saptr, salen)) < 0) { close(sockfd); if (errno == EINTR) errno = ETIMEDOUT; } alarm(0); /* turn off the alarm */ Signal(SIGALRM, sigfunc); /* 恢復先前的處理函式 */
return(n); } static void connect_alarm(int signo) { return; /* just interrupt the connect() */ } /* end connect_timeo */ void Connect_timeo(int fd, const SA *sa, socklen_t salen, int sec) { if (connect_timeo(fd, sa, salen, sec) < 0) err_sys("connect_timeo error"); }

注意事項:

  1. EINTR 錯誤

1.產生原因:

   在阻塞於該系統呼叫上的時候,該程序收到一個訊號並且訊號處理函式返回之後,該系統呼叫就會返回一個EINTR錯誤。如上例,在客戶端,我們設定了訊號處理機制,當阻塞於connect時,收到訊號並且訊號處理函式返回,那麼connect就會產生一個EINTR錯誤。

2.如何解決?

   可以對該系統呼叫進行重啟,當然了,要注意的是:對於有一些系統呼叫是不能夠重啟的。例如:connect函式。若connetct函式返回一個EINTR錯誤的時候,則不能再次呼叫它,否則將返回一個錯誤。其他的(accept、read、wiret、seletc、ioctl和open之類的函式)都是可以進行重啟的。

(1.2)使用 SIGALRMrecvfrom設定超時

static void sig_alrm(int);

void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
    int n;
    char    sendline[MAXLINE], recvline[MAXLINE + 1];

    Signal(SIGALRM, sig_alrm);

    while (Fgets(sendline, MAXLINE, fp) != NULL) {

        Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

        alarm(5);
        if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0) {
            if (errno == EINTR) //輸出資訊並繼續執行
                fprintf(stderr, "socket timeout\n");
            else
                err_sys("recvfrom error");
        } else {
            alarm(0);
            recvline[n] = 0;    /* null terminate */
            Fputs(recvline, stdout);
        }
    }
}

static void
sig_alrm(int signo)
{
    return;         /* just interrupt the recvfrom() */
}

其實這就是一個對於可以重啟的系統呼叫的例子而已

(2).使用selectrecvfrom設定超時

int
readable_timeo(int fd, int sec)
{
    fd_set          rset;
    struct timeval  tv;

    FD_ZERO(&rset);
    FD_SET(fd, &rset);

    tv.tv_sec = sec;
    tv.tv_usec = 0;

    return(select(fd+1, &rset, NULL, NULL, &tv));
    /*出錯返回-1,超時返回0,否則返回已就緒的描述符數目
    只是等待給定的描述符變為可讀,因此適用於任何型別套接字,TCP/UDP*/
}
/* end readable_timeo */

(3).使用SO_RCVTIMEO套接字選項為recvfrom設定超時

void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
    int             n;
    char            sendline[MAXLINE], recvline[MAXLINE + 1];
    struct timeval  tv;

    tv.tv_sec = 5;
    tv.tv_usec = 0;
    Setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    while (Fgets(sendline, MAXLINE, fp) != NULL) {

        Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

        n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
        if (n < 0) {
            if (errno == EWOULDBLOCK) {
                fprintf(stderr, "socket timeout\n");
                continue;
            } else
                err_sys("recvfrom error");
        }

        recvline[n] = 0;    /* null terminate */
        Fputs(recvline, stdout);
    }
}

1.優勢就在於一次性設定即可,前面的兩種操作都是需要在設定時間限制操作之前做些工作的。SO_RCVTIMEO只應用於讀操作。SO_SNDTIMEO用於寫操作,( UNP上說兩個都不能為connect設定超時 ,但是經過查詢後發現,其實是可以的,可以自己try一下哦,點選這裡檢視詳情)

2.超時函式會(在這裡是recvfrom)返回EWOULDBLOCK錯誤。

比較:

一:第一種方法只適用於單執行緒或者未執行緒化的程式中。很難適用於多執行緒化的程式中。(或者說程式設計時需要注意很多)

二: 一般(我估計的)就是使用第三種方法

附錄:

connect函式返回錯誤的情況,常見的有下面三種:

  • 返回TIMEOUT,即SYN_RECV(未完成連線佇列)都滿了,對於客戶端發來的三次握手第一次的SYN都沒有辦法響應,這時候TCP會隔6s,24s重發,直到75s,如果還是沒有被接受,最後返回TIMEOUT錯誤。
  • 返回ECONNREFUSED錯誤,表示伺服器主機沒有在相應的埠開啟監聽。
  • 返回EHOSTUNREACHENETUNREACH,表示在某個中間路由節點返回了ICMP錯誤,這個錯誤被核心先儲存,之後繼續按照6s,24s重發,直到75s,如果還是沒有響應,就返回EHOSTUNREACHENETUNREACH錯誤。

Just a question :

1:如果服務端在listen之後沒有accept,那客戶端的connect會返回嗎?為什麼? 
2:此時呼叫send發資料會怎麼樣?

   其實第一個問題只要你懂得listen,accept,connect的作用和 連線過程很容易就能想明白。ok,那我們來說說:

首先,來講listen函式:

   #include <sys/types.h>          /* See NOTES */
   #include <sys/socket.h>

   int listen(int sockfd, int backlog);

功能:——–》兩個

一:把一個未連線的套接字轉換為一個被動套接字,指示核心應該接受指向該套接字的請求。狀態從CLOSED變為LISTEN狀態。

二:第二個引數指定了為該套接字排隊的最大連線個數。(目前仍然沒有正式定義)
摘自man 手冊(4.17.12-100.fc27.x86_64 #1 SMP Fri Aug 3 15:00:33 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux)
“`c++

The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a con‐
nection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if
the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection suc‐
ceeds.
/翻譯/

backlog引數定義sockfd的掛起連線佇列可能增長的最大長度。如果一個
當佇列已滿時,請求到達,客戶端可能會收到帶有ECONNREFUSED指示的錯誤,或者,如果基礎協議支援重傳,可以忽略該請求,以便稍後重新連線成功。

“`

必須認識到的一點:核心會為任何一個給定的監聽套接字維護兩個佇列。

1.一個未完成佇列:客戶端主動開啟套接字,發來SYN分節,在到達伺服器這邊時,等待伺服器傳送(SYN + ACK)分節確認。這些套接字處於SYN_RCVD狀態。

2.一個已完成連線佇列:按字面意思理解既可。這些套接字處於ESTABLISHED狀態。(也就是說服務端已經發送了SYN+ACK,並且收到客戶端發來的ACK之後,立馬就會將未完成連線佇列中的該套接字拿到已連線套接字佇列中)

– - - - - - - - - – - - - - - - – - - - - - - – - - - - - - [ 持 續 更 新 ]