Socket SO_RCVTIMEO, SO_SNDTIMEO

SO_RCVTIMEO, SO_SNDTIMEO介紹

套接字選項SO_RCVTIMEO: 用來設置socket接收數據的超時時間;
套接字選項SO_SNDTIMEO: 用來設置socket發送數據的超時時間;

比如,一般情況下,調用accept/connect/send/recv, 進程會阻塞,但是如果對端異常,進行可能無法正常退出等待。如何讓這些調用自動定時退出?

可以使用諸如alarm定時器、I/O復用設置定時器,還可以使用socket編程里函數級別的socket套接字選項SO_RCVTIMEO和SO_SNDTIMEO,僅針對與數據接收和發送相關,而無需設置專門的信號捕獲函數。

能夠作用的系統調用包括:send、sendmsg、recv、recvmsg、accept、connect。

image.png
  • EAGAIN通常和EWOULDBLOCK是同一個值;
  • SO_RCVTIMEO, SO_SNDTIMEO不要求系統調用對應fd是非阻塞(nonblocking)的,但是使用了該套接字選項的sock fd,會成為nonblocking(即使之前是blocking)的。參見man手冊ERRORS EAGAIN/EWOULDBLOCK的描述;

示例1:設置connect超時時間

根據系統調用accept的返回值,以及errno判斷超時時間是否已到,從而決定是否開始處理超時定時任務。

/**
 * 客戶端程序
 * 連接服務器,超時報錯、返回
 * build:
 * $ gcc timeout_connect.c
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
 
/* 超時連接 */
int timeout_connect (const char *ip, int port, int time)
{
    int ret = 0;
    struct sockaddr_in servaddr;
    
    printf("client start...\n");
 
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &servaddr.sin_addr);
    servaddr.sin_port = htons(port);
 
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    assert(sockfd >= 0);
 
    /* 通過選項SO_RCVTIMEO和SO_SNDTIMEO設置的超時時間的類型時timeval, 和select系統調用的超時參數類型相同 */
    struct timeval timeout;
    timeout.tv_sec = time;
    timeout.tv_usec = 0;
 
    socklen_t len = sizeof(timeout);
    ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len);
    if (ret == -1) {
        perror("setsockopt error");
 
        return -1;
    }
 
    if ((ret = connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) < 0) {
    /* 超時對于errno 為EINPROGRESS. 下面條件如果成立,就可以處理定時任務了 */
        if (errno == EINPROGRESS) {
            perror("connecting timeout, process timeout logic");
            return -1;
        }
 
        perror("error occur when connecting to server\n");
    }
 
    return sockfd;
}
 
int main(int argc, char *argv[])
{
    if (argc <= 2) {
        printf("usage: %s ip_address port_number\n", argv[0]);
        return 1;
    }
 
    const char *ip = argv[1];
    int port = atoi(argv[2]);
    printf("connect %s:%d...\n", ip, port);
 
    int sockfd = timeout_connect(ip, port, 10);
    if (sockfd < 0) {
        perror("timeout_connect error");
        return 1;
    }
 
    return 0;
}

運行結果(隨意輸入一個服務器IP、端口):

$ ./timeout_connect 192.168.0.105 8000
connect 192.168.0.105:8000...
client start...
connecting timeout, process timeout logic: Operation now in progress
timeout_connect error: Operation now in progress

可以看到,本來阻塞的connect調用,10秒后返回-1,并且errno設置為EINPROGRESS。

示例2:超時接收(服務器數據)

服務器端

監聽本地任意IP地址,端口8001
從鍵盤輸入一行數據,就發送給用戶;如果沒有數據,就阻塞。

/**
 * 服務器程序
 * 示例:超時接收服務器數據,超時時間例程中設置為10秒
 * 編譯: $ gcc timeout_recv_server.c -o server
 * 運行方式:
 * $ ./server
 * 默認監聽端口8001(根據實際情況修改)
 * 服務器功能:從鍵盤接收用戶輸入,每接收一行就向客戶輸出一行。如果沒有用戶輸入,
 * 則阻塞。
 * 客戶端需要跟服務器安裝在同一網段上,為了測試方便,就直接都安裝到同一機器上
 */
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
 
int sockfd = -1;
 
void sig_func(int sig_no)
{
    if (sig_no == SIGINT || sig_no == SIGTERM) {
        if (sockfd >= 0) {
            close(sockfd);
        }
        exit(1);
    }
}
 
int main()
{
    struct sockaddr_in servaddr, cliaddr;
    int listenfd;
 
    signal(SIGINT, sig_func);
 
    printf("server start...\n");
 
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket error");
        exit(1);
    }
    sockfd = listenfd;
 
    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
        perror("setsocketopt error");
        exit(1);
    }
 
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    // servaddr.sin_addr.s_addr = INADDR_ANY;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(8001);
 
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind error");
        exit(1);
    }
 
    if (listen(listenfd, 5) < 0) {
        perror("listen error");
        exit(1);
    }
 
    char buf[1024];
    socklen_t clilen = sizeof(cliaddr);
    int connfd;
 
    if ((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) {
        perror("accept error");
        exit(1);
    }
 
    printf("input a line string: \n");
    int nbytes;
    while (fgets(buf, sizeof(buf), stdin)) {
        nbytes = send(connfd, buf, strlen(buf), 0);
        if (nbytes < 0) {
            perror("send error");
            break;
        }
        else if (nbytes == 0) {
        
        }
        printf("send: %s\n", buf);
    }
    
    close(connfd);
    close(listenfd);
 
    return 0;
}
客戶端

設置10秒超時,接收服務器數據。

客戶端10秒以內,接收到服務器數據,則直接打印;超過10秒,就報錯退出。

/**
 * 客戶端程序
 * 示例:超時接收服務器數據,超時時間例程中設置為10秒
 * 編譯: $ gcc timeout_recv_client.c -o client
 * 運行方式:
 * 如本地運行(對應服務器實際監聽的IP地址和端口號) $ ./client 127.0.0.1 8001
 */
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
 
int timeout_recv(int fd, char *buf, int len,  int nsec)
{
    struct timeval timeout;
    timeout.tv_sec = nsec;
    timeout.tv_usec = 0;
 
    printf("timeout_recv called, timeout %d seconds\n", nsec);
 
    if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
        perror("setsockopt error");
        exit(1);
    }
 
    int n = recv(fd, buf, len, 0);
 
    return n;
}
 
int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("usage: %s <ip address> <port>\n", argv[0]);
    }
 
    char *ip = argv[1];
    uint16_t port = atoi(argv[2]);
    
    printf("client start..\n");
    printf("connect to %s:%d\n", ip, port);
 
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket error");
        exit(1);
    }
    
    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &servaddr.sin_addr);
    servaddr.sin_port = htons(port);
    
    int connfd;
    if ((connfd = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0) {
        perror("connect error");
        exit(1);
    }
 
    printf("success to connect server %s:%d\n", ip, port);
    printf("wait for server's response\n");
    char buf[100];
    while (1) {
        int nread;
        
        nread = timeout_recv(sockfd, buf, sizeof(buf), 10);
        if (nread < 0) {
            perror("timeout_recv error");
            exit(1);
        }
        else if (nread == 0) {
            shutdown(sockfd, SHUT_RDWR);
            break;
        }
 
        write(STDOUT_FILENO, buf, nread);
    }
 
    return 0;
}

客戶端運行結果:
可以看到,超過10秒后,客戶端自動退出程序,而不再阻塞在recv。

$ ./client 127.0.0.1 8001
client start..
connect to 127.0.0.1:8001
success to connect server 127.0.0.1:8001
wait for server's response
timeout_recv called, timeout 10 seconds
hello # 服務器端用戶輸入數據
timeout_recv called, timeout 10 seconds
nihao # 服務器端用戶輸入數據
timeout_recv called, timeout 10 seconds
timeout_recv error: Resource temporarily unavailable # 服務器端超時未輸入數據,客戶端程序運行結束

寫超時

[mapan@localhost sockOption]$ ls
client.cpp  makefile  server.cpp
[mapan@localhost sockOption]$ cat client.cpp 
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#define MAXLINE 4096
 
 
int main(int argc,char **argv)
{
   int connfd,ret;
   char sendbuf[400000]={0};
   struct sockaddr_in servaddr;
 
   if(argc != 2)
   {
      printf("error\n");
   }
 
   connfd=socket(AF_INET,SOCK_STREAM,0);
   memset(&servaddr,0,sizeof(servaddr));
   servaddr.sin_family=AF_INET;
   servaddr.sin_port=htons(6666);
   inet_pton(AF_INET,argv[1],&servaddr.sin_addr);
 
   connect(connfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
   
   struct timeval stTimeValStruct;
   stTimeValStruct.tv_sec=5;
   stTimeValStruct.tv_usec=0;
 
   setsockopt(connfd,SOL_SOCKET,SO_SNDTIMEO,&stTimeValStruct,sizeof(stTimeValStruct));
   while(1)
   {
     ret= write(connfd,sendbuf,sizeof(sendbuf));
     printf("ret=%d\n",ret);
   }
  
   close(connfd);
   return 0;
}
 
 
[mapan@localhost sockOption]$ cat server.cpp 
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#define MAXLINE 4096
 
 
int main()
{
   int listenfd,acceptfd;
   struct sockaddr_in servaddr;
 
 
   listenfd=socket(AF_INET,SOCK_STREAM,0);
   memset(&servaddr,0,sizeof(servaddr));
   servaddr.sin_family=AF_INET;
   servaddr.sin_port=htons(6666);
   servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
 
   bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
   listen(listenfd,10);
 
   acceptfd=accept(listenfd,(struct sockaddr *)NULL,NULL);   
    
   getchar();
   close(acceptfd);
   close(listenfd); 
   return 0;
}
 
[mapan@localhost sockOption]$ cat makefile 
all:server client
 
server.o:server.cpp
    g++ -c server.cpp
client.o:client.cpp
    g++ -c client.cpp
server:server.o
    g++ -o server server.o
client:client.o
    g++ -o client client.o
 
clean:
    rm -f server client *.o
[mapan@localhost sockOption]$ 

編譯并運行,客戶端需要新打開一個窗口執行。

[mapan@localhost sockOption]$ make
g++ -c server.cpp
g++ -o server server.o
g++ -c client.cpp
g++ -o client client.o
[mapan@localhost sockOption]$ ./server 
[mapan@localhost sockOption]$ ./client 127.0.0.1
ret=400000
ret=400000
ret=400000
ret=254012
ret=-1
ret=-1
^C
[mapan@localhost sockOption]$

再看看看接收緩沖區和發送緩沖區:

socket緩沖區配置

cat /proc/sys/net/ipv4/tcp_wmem
4096 65535 33554432

65535 : tcp 發送緩沖區的默認值

cat /proc/sys/net/ipv4/tcp_rmem
4096 65535 16777216

65535 :tcp接收緩沖區的默認值

cat /proc/sys/net/core/wmem_max

33554432
33554432: tcp 或 udp 發送緩沖區最大可設置值的一半。

tcp 或udp收發緩沖區最小值

tcp 或udp接收緩沖區的最小值為 256 bytes,由內核的宏決定;

tcp 或udp發送緩沖區的最小值為 2048 bytes,由內核的宏決定

ss -nt | grep 6666

image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370