socket超时设置详解(connect/read/write)

一.基本概念 (摘自:《unix网络编程》卷1 14.2 套接字超时)
在涉及套接字的I/O操作上设置超时的方法有以下三种
(1)调用 alarm ,它在指定超时期满时产生 SIGALARM 。这个方法涉及信号处理,而信号处理在不同的实现上存在差异,而且可能干扰进程中现有的 alarm 调用。
(2)在 select 中阻塞等待I/O( select 有内置的时间限制),以此替代直接阻塞在 read write 调用上。
(3)使用 SO_RECVTIMEO SO_SNDTIMEO 套接字选项
上述三个技术都适用于输入输出操作(例如 read write 及其诸如 recvfrom sendto 之类的变体),不过我们依然期待可用于 connect 的技术,因为TCP内置的 connect 超时相当长(典型值为75秒钟)。 select可用来在connect上设置超时的先决条件是相应的套接字是处于非阻塞模式,而那两个套接字选项对connect并不适用 。我们还指出,前两个技术适用于任何描述符,而第三个技术仅仅适用于套接字描述符。


注:阻塞模式下,当服务器连接不上时,通过命令 “netstat -tlnap|grep SENT" 可以看到客户端会处于SYN_SENT状态,直到connect超时


二.非阻塞模式socket
1. connect 不需要考虑超时问题,立即返回一个错误 EINPROGRESS ,可通过 检测fd 是否可用判断连接是否建立完成
2. read 不需要考虑超时问题,立即返回
3. write 不需要考虑超时问题,立即返回


三.阻塞模式socket
1. connect 需要考虑超时问题,否则当连接IP不可达的情况下,需要等待很长一段时间(默认时长)
2. read 需要考虑超时问题,可通过 setsockopt 设置 SO_RECVTIMEO 选项
3. write 需要考虑超时问题,可通过 setsockopt 设置 SO_SNDTIMEO 选项

四.带超时的connect,适用于阻塞模式与非阻塞模式socket(本质上是非阻塞connect)


附上测试代码,可通过更改main中的IP和端口进行测试,读懂这段代码需要了解select与getsockopt函数

-----  g++ connect_nonb.cpp -----

[cpp]  view plain  copy
  1. #include <sys/types.h>  
  2. #include <sys/time.h>  
  3. #include <sys/socket.h>  
  4. #include <netinet/in.h>    
  5. #include <arpa/inet.h>   
  6. #include <unistd.h>  
  7. #include <fcntl.h>  
  8. #include <stdio.h>  
  9. #include <errno.h>  
  10. #include <stdlib.h>  
  11. #include <string.h>  
  12.   
  13. int connect_nonb(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int nsec)  
  14. {  
  15.     int flags, n, error;  
  16.     socklen_t len;  
  17.     fd_set rset, wset;  
  18.     struct timeval tval;  
  19.   
  20.     /* 调用fcntl把套接字设置为非阻塞 */  
  21.     flags = fcntl(sockfd, F_GETFL, 0);  
  22.     fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);  
  23.   
  24.     /* 发起非阻塞connect。期望的错误是EINPROGRESS,表示连接建立已经启动但是尚未完成  */  
  25.     error = 0;  
  26.     if ( (n = connect(sockfd, addr, addrlen)) < 0)  
  27.         if (errno != EINPROGRESS)  
  28.             return(-1);  
  29.       
  30.     /* 如果非阻塞connect返回0,那么连接已经建立。当服务器处于客户端所在主机时这种情况可能发生 */  
  31.     if (n == 0)  
  32.         goto done; /* connect completed immediately */  
  33.   
  34.     /* 调用select等待套接字变为可读或可写,如果select返回0,那么表示超时 */  
  35.     FD_ZERO(&rset);  
  36.     FD_SET(sockfd, &rset);  
  37.     wset = rset;  
  38.     tval.tv_sec = nsec;  
  39.     tval.tv_usec = 0;  
  40.   
  41.     if ( (n = select(sockfd+1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) {  
  42.         close(sockfd); /* timeout */  
  43.         errno = ETIMEDOUT;  
  44.         return(-1);  
  45.     }  
  46.   
  47.     /* 检查可读或可写条件,调用getsockopt取得套接字的待处理错误,如果建立成功,该值将为0 */  
  48.     if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {  
  49.         len = sizeof(error);  
  50.         if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)  
  51.             return(-1); /* Solaris pending error */  
  52.     } else {  
  53.         perror("select error: sockfd not set");  
  54.         exit(1);  
  55.     }  
  56.   
  57.     /* 恢复套接字的文件状态标志并返回 */  
  58. done:  
  59.     fcntl(sockfd, F_SETFL, flags); /* restore file status flags */  
  60.     if (error) {  
  61.         close(sockfd); /* just in case */  
  62.         errno = error;  
  63.         return(-1);  
  64.     }  
  65.     return(0);  
  66. }  
  67.   
  68. int main()  
  69. {  
  70.     // socket    
  71.     struct sockaddr_in servaddr;      
  72.     short port = 9999;      
  73.     int sockfd = socket(AF_INET, SOCK_STREAM, 0);      
  74.     servaddr.sin_family = AF_INET;      
  75.     servaddr.sin_addr.s_addr = inet_addr("113.107.231.211");      
  76.     servaddr.sin_port = htons(port);  
  77.     int timeout = 3;  
  78.       
  79.     if (connect_nonb(sockfd, (sockaddr *) &servaddr, sizeof(sockaddr_in), 2) < 0) {    
  80.         perror("connect fail: ");  
  81.         return(-1);  
  82.     }  
  83.     printf("connect success!\n");  
  84.   
  85.     return 0;  
  86. }  


参考:《unix网络编程》卷1


End;


  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的例子,演示如何使用C语言编写一个简单的socket程序,以发送文件。 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <fcntl.h> #define PORT 8080 #define MAX_BUFFER_SIZE 1024 int main(int argc, char const *argv[]) { int sockfd, connfd; struct sockaddr_in servaddr, clientaddr; char buffer[MAX_BUFFER_SIZE]; char filename[256]; int n; // 创建套接字 sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { printf("Failed to create socket.\n"); exit(EXIT_FAILURE); } // 设置地址信息 memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = INADDR_ANY; servaddr.sin_port = htons(PORT); // 绑定端口 if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) { printf("Failed to bind socket.\n"); exit(EXIT_FAILURE); } // 监听端口 if (listen(sockfd, 5) != 0) { printf("Failed to listen on socket.\n"); exit(EXIT_FAILURE); } printf("Server listening on port %d.\n", PORT); // 接受连接 connfd = accept(sockfd, (struct sockaddr *)&clientaddr, (socklen_t*)&n); if (connfd < 0) { printf("Failed to accept connection.\n"); exit(EXIT_FAILURE); } printf("Client connected: %s:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); // 读取文件名 memset(buffer, 0, MAX_BUFFER_SIZE); read(connfd, buffer, MAX_BUFFER_SIZE); sscanf(buffer, "%s", filename); // 打开文件 int fd = open(filename, O_RDONLY); if (fd < 0) { printf("Failed to open file.\n"); exit(EXIT_FAILURE); } // 发送文件内容 memset(buffer, 0, MAX_BUFFER_SIZE); while ((n = read(fd, buffer, MAX_BUFFER_SIZE)) > 0) { if (write(connfd, buffer, n) < 0) { printf("Failed to send file.\n"); exit(EXIT_FAILURE); } memset(buffer, 0, MAX_BUFFER_SIZE); } printf("File sent successfully.\n"); // 关闭连接和套接字 close(connfd); close(sockfd); return 0; } ``` 该程序的工作流程如下: 1. 创建套接字并绑定端口。 2. 监听端口并等待连接。 3. 接受连接,并读取客户端发送的文件名。 4. 打开文件并读取文件内容。 5. 将文件内容写入套接字,发送给客户端。 6. 关闭连接和套接字。 这是一个非常简单的例子,实际中可能需要添加更多的错误检查和异常处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值