Socket编程实践(9) --套接字IO超时设置方法
引:超时设置3种方案
1. alarm超时设置方法
- //代码实现: 这种方式较少用
- void sigHandlerForSigAlrm(int signo)
- {
- return ;
- }
- signal(SIGALRM, sigHandlerForSigAlrm);
- alarm(5);
- int ret = read(sockfd, buf, sizeof(buf));
- if (ret == -1 && errno == EINTR)
- {
- // 超时被时钟打断
- errno = ETIMEDOUT;
- }
- else if (ret >= 0)
- {
- // 正常返回(没有超时), 则将闹钟关闭
- alarm(0);
- }
2. 套接字选项: SO_SNDTIMEO, SO_RCVTIMEO
调用setsockopt设置读/写超时时间
- //示例: read超时
- int seconds = 5;
- if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &seconds, sizeof(seconds)) == -1)
- err_exit("setsockopt error");
- int ret = read(sockfd, buf, sizeof(buf));
- if (ret == -1 && errno == EWOULDBLOCK)
- {
- // 超时,被时钟打断
- errno = ETIMEDOUT;
- }
3. select方式[重点]
_timeout函数封装
1. read_timeout封装
- /**
- *read_timeout - 读超时检测函数, 不包含读操作
- *@fd: 文件描述符
- *@waitSec: 等待超时秒数, 0表示不检测超时
- *成功(未超时)返回0, 失败返回-1, 超时返回-1 并且 errno = ETIMEDOUT
- **/
- int read_timeout(int fd, long waitSec)
- {
- int returnValue = 0;
- if (waitSec > 0)
- {
- fd_set readSet;
- FD_ZERO(&readSet);
- FD_SET(fd,&readSet); //添加
- struct timeval waitTime;
- waitTime.tv_sec = waitSec;
- waitTime.tv_usec = 0; //将微秒设置为0(不进行设置),如果设置了,时间会更加精确
- do
- {
- returnValue = select(fd+1,&readSet,NULL,NULL,&waitTime);
- }
- while(returnValue < 0 && errno == EINTR); //等待被(信号)打断的情况, 重启select
- if (returnValue == 0) //在waitTime时间段中一个事件也没到达
- {
- returnValue = -1; //返回-1
- errno = ETIMEDOUT;
- }
- else if (returnValue == 1) //在waitTime时间段中有事件产生
- returnValue = 0; //返回0,表示成功
- // 如果(returnValue == -1) 并且 (errno != EINTR), 则直接返回-1(returnValue)
- }
- return returnValue;
- }
2. write_timeout封装
- /**
- *write_timeout - 写超时检测函数, 不包含写操作
- *@fd: 文件描述符
- *@waitSec: 等待超时秒数, 0表示不检测超时
- *成功(未超时)返回0, 失败返回-1, 超时返回-1 并且 errno = ETIMEDOUT
- **/
- int write_timeout(int fd, long waitSec)
- {
- int returnValue = 0;
- if (waitSec > 0)
- {
- fd_set writeSet;
- FD_ZERO(&writeSet); //清零
- FD_SET(fd,&writeSet); //添加
- struct timeval waitTime;
- waitTime.tv_sec = waitSec;
- waitTime.tv_usec = 0;
- do
- {
- returnValue = select(fd+1,NULL,&writeSet,NULL,&waitTime);
- } while(returnValue < 0 && errno == EINTR); //等待被(信号)打断的情况
- if (returnValue == 0) //在waitTime时间段中一个事件也没到达
- {
- returnValue = -1; //返回-1
- errno = ETIMEDOUT;
- }
- else if (returnValue == 1) //在waitTime时间段中有事件产生
- returnValue = 0; //返回0,表示成功
- }
- return returnValue;
- }
3. accept_timeout函数封装
- /**
- *accept_timeout - 带超时的accept
- *@fd: 文件描述符
- *@addr: 输出参数, 返回对方地址
- *@waitSec: 等待超时秒数, 0表示不使用超时检测, 使用正常模式的accept
- *成功(未超时)返回0, 失败返回-1, 超时返回-1 并且 errno = ETIMEDOUT
- **/
- int accept_timeout(int fd, struct sockaddr_in *addr, long waitSec)
- {
- int returnValue = 0;
- if (waitSec > 0)
- {
- fd_set acceptSet;
- FD_ZERO(&acceptSet);
- FD_SET(fd,&acceptSet); //添加
- struct timeval waitTime;
- waitTime.tv_sec = waitSec;
- waitTime.tv_usec = 0;
- do
- {
- returnValue = select(fd+1,&acceptSet,NULL,NULL,&waitTime);
- }
- while(returnValue < 0 && errno == EINTR);
- if (returnValue == 0) //在waitTime时间段中没有事件产生
- {
- errno = ETIMEDOUT;
- return -1;
- }
- else if (returnValue == -1) // error
- return -1;
- }
- /**select正确返回:
- 表示有select所等待的事件发生:对等方完成了三次握手,
- 客户端有新的链接建立,此时再调用accept就不会阻塞了
- */
- socklen_t socklen = sizeof(struct sockaddr_in);
- if (addr != NULL)
- returnValue = accept(fd,(struct sockaddr *)addr,&socklen);
- else
- returnValue = accept(fd,NULL,NULL);
- return returnValue;
- }
4. connect_timeout函数封装
(1)我们为什么需要这个函数?
TCP/IP在客户端连接服务器时,如果发生异常,connect(如果是在默认阻塞的情况下)返回的时间是RTT(相当于客户端阻塞了这么长的时间,客户需要等待这么长的时间,显然这样的客户端用户体验并不好(完成三次握手需要使用1.5RTT时间));会造成严重的软件质量下降.
(2)怎样实现connect_timeout?
1)sockfd首先变成非阻塞的; 然后试着进行connect,如果网络状况良好,则立刻建立链接并返回,如果网络状况不好,则链接不会马上建立,这时需要我们的参与:调用select,设置等待时间,通过select管理者去监控sockfd,一旦能够建立链接,则马上返回,然后建立链接,这样就会大大提高我们的软件质量.
2)需要注意:select机制监控到sockfd可写(也就是可以建立链接时),并不代表调用connect就一定能够成功(造成sockfd可写有两种情况: a.真正的链接可以建立起来了; b.建立链接的过程中发生错误,然后错误会回写错误信息,造成sockfd可写);
通过调用getsockopt做一个容错即可(见下例)!
(3)代码实现:
- /**设置文件描述符fd为非阻塞/阻塞模式**/
- bool setUnBlock(int fd, bool unBlock)
- {
- int flags = fcntl(fd,F_GETFL);
- if (flags == -1)
- return false;
- if (unBlock)
- flags |= O_NONBLOCK;
- else
- flags &= ~O_NONBLOCK;
- if (fcntl(fd,F_SETFL,flags) == -1)
- return false;
- return true;
- }
- /**
- *connect_timeout - connect
- *@fd: 文件描述符
- *@addr: 要连接的对方地址
- *@waitSec: 等待超时秒数, 0表示使用正常模式的accept
- *成功(未超时)返回0, 失败返回-1, 超时返回-1 并且 errno = ETIMEDOUT
- **/
- int connect_timeout(int fd, struct sockaddr_in *addr, long waitSec)
- {
- if (waitSec > 0) //设置为非阻塞模式
- setUnBlock(fd, true);
- socklen_t addrLen = sizeof(struct sockaddr_in);
- //首先尝试着进行链接
- int returnValue = connect(fd,(struct sockaddr *)addr,addrLen);
- //如果首次尝试失败(并且errno == EINPROGRESS表示连接正在处理当中),则需要我们的介入
- if (returnValue < 0 && errno == EINPROGRESS)
- {
- fd_set connectSet;
- FD_ZERO(&connectSet);
- FD_SET(fd,&connectSet);
- struct timeval waitTime;
- waitTime.tv_sec = waitSec;
- waitTime.tv_usec = 0;
- do
- {
- /*一旦建立链接,则套接字可写*/
- returnValue = select(fd+1, NULL, &connectSet, NULL, &waitTime);
- }
- while (returnValue < 0 && errno == EINTR);
- if (returnValue == -1) //error
- return -1;
- else if (returnValue == 0) //超时
- {
- returnValue = -1;
- errno = ETIMEDOUT;
- }
- else if (returnValue == 1) //正确返回,有一个套接字可写
- {
- /**由于connectSet只有一个文件描述符, 因此FD_ISSET的测试也就省了**/
- /**注意:套接字可写有两种情况:
- 1.连接建立成功
- 2.套接字产生错误(但是此时select是正确的, 因此错误信息没有保存在errno中),需要调用getsockopt获取
- */
- int err;
- socklen_t errLen = sizeof(err);
- int sockoptret = getsockopt(fd,SOL_SOCKET,SO_ERROR,&err,&errLen);
- if (sockoptret == -1)
- return -1;
- // 测试err的值
- if (err == 0) //确实是链接建立成功
- returnValue = 0;
- else //连接产生了错误
- {
- errno = err;
- returnValue = -1;
- }
- }
- }
- if (waitSec > 0)
- setUnBlock(fd, false);
- return returnValue;
- }
- /**测试:使用connect_timeout的client端完整代码(server端如前)**/
- int main()
- {
- int sockfd = socket(AF_INET, SOCK_STREAM, 0);
- if (sockfd == -1)
- err_exit("socket error");
- struct sockaddr_in serverAddr;
- serverAddr.sin_family = AF_INET;
- serverAddr.sin_port = htons(8001);
- serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
- int ret = connect_timeout(sockfd, &serverAddr, 5);
- if (ret == -1 && errno == ETIMEDOUT)
- {
- cerr << "timeout..." << endl;
- err_exit("connect_timeout error");
- }
- else if (ret == -1)
- err_exit("connect_timeout error");
- //获取并打印对端信息
- struct sockaddr_in peerAddr;
- socklen_t peerLen = sizeof(peerAddr);
- if (getpeername(sockfd, (struct sockaddr *)&peerAddr, &peerLen) == -1)
- err_exit("getpeername");
- cout << "Server information: " << inet_ntoa(peerAddr.sin_addr)
- << ", " << ntohs(peerAddr.sin_port) << endl;
- close(sockfd);
- }
附-RTT(Round-Trip Time)介绍:
RTT往返时延:在计算机网络中它是一个重要的性能指标,表示从发送端发送数据开始,到发送端收到来自接收端的确认(接收端收到数据后便立即发送确认),总共经历的时延。
RTT由三个部分决定:即链路的传播时间、末端系统的处理时间以及路由器的缓存中的排队和处理时间。其中,前面两个部分的值作为一个TCP连接相对固定,路由器的缓存中的排队和处理时间会随着整个网络拥塞程度的变化而变化。所以RTT的变化在一定程度上反映了网络拥塞程度的变化。简单来说就是发送方从发送数据开始,到收到来自接受方的确认信息所经历的时间。
Socket编程实践(9) --套接字IO超时设置方法的更多相关文章
- 套接字IO超时设置和使用select实现超时管理
在涉及套接字IO超时的设置上有一下3种方法: 1.调用alarm,它在指定的时期满时产生SIGALRM信号.这个方法涉及信号的处理,而信号处理在不同的实现上存在差异,而且可能干扰进程中现有的alarm ...
- select实现超时(套接字IO超时设置)
实现超时的三种方式: 1.SIGALARM信号 void handler(int sig) { return 0; } signal(SIGALRM,handler); alarm(5); int ...
- Linux系统编程(37)—— socket编程之原始套接字
原始套接字的特点 原始套接字(SOCK_RAW)可以用来自行组装IP数据包,然后将数据包发送到其他终端.也就是说原始套接字是基于IP数据包的编程(SOCK_PACKET是基于数据链路层的编程).另外, ...
- Python黑帽编程2.8 套接字编程
Python黑帽编程2.8 套接字编程 套接字编程在本系列教程中地位并不是很突出,但是我们观察网络应用,绝大多数都是基于Socket来做的,哪怕是绝大多数的木马程序也是如此.官方关于socket编程的 ...
- Socket编程实践(10) --select的限制与poll的使用
select的限制 用select实现的并发服务器,能达到的并发数一般受两方面限制: 1)一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n(number)来调整或 ...
- Socket编程实践(6) --TCP服务端注意事项
僵尸进程处理 1)通过忽略SIGCHLD信号,避免僵尸进程 在server端代码中添加 signal(SIGCHLD, SIG_IGN); 2)通过wait/waitpid方法,解决僵尸进程 sign ...
- Socket编程实践(6) --TCPNotes服务器
僵尸进程过程 1)通过忽略SIGCHLD信号,避免僵尸进程 在server端代码中加入 signal(SIGCHLD, SIG_IGN); 2)通过wait/waitpid方法.解决僵尸进程 sign ...
- Linux网络编程:原始套接字简介
Linux网络编程:原始套接字编程 一.原始套接字用途 通常情况下程序员接所接触到的套接字(Socket)为两类: 流式套接字(SOCK_STREAM):一种面向连接的Socket,针对于面向连接的T ...
- Socket编程实践(2) Socket API 与 简单例程
在本篇文章中,先介绍一下Socket编程的一些API,然后利用这些API实现一个客户端-服务器模型的一个简单通信例程.该例子中,服务器接收到客户端的信息后,将信息重新发送给客户端. socket()函 ...
随机推荐
- react 或 vue 中引用 jQuery 插件
前言 今天与遇到一个令人抓狂的事情, 因为项目中有个交互太过于复杂而且冷门, 没有人封装类似react-swiper那种的移植过来的插件 只有现成的jQuery插件. 而时间并不宽裕,自己重写成rea ...
- iOS支付宝,微信,银联支付集成封装调用(下)
一.越来越多的app增加第三方的功能,可能app有不同的页面但调用相同的支付方式,例如界面如下: 这两个页面都会使用第三方支付支付:(微信,支付宝,银联)如果在每一个页面都直接调用第三方支付的接口全部 ...
- SpringMVC之Ajax与Controller交互
前面学习了拦截器,通过拦截器我们可以拦截请求,做进一步处理之后再往下进行,这里我们使用Ajax的时候会有一个问题就是会把js.css这些静态资源文件也进行了拦截,这样在jsp中就无法引入的静态资源文件 ...
- Redis监控工具,命令和调优
Redis监控工具,命令和调优 1.图形化监控 因为要对Redis做性能测试,发现了GitHub上有个python写的RedisLive监控工具评价不错.结果鼓捣了半天,最后发现其主页中引用了Goog ...
- Linux-2.6.25 TCPIP函数调用大致流程
插口层系统调用send sys_send sys_sendtosendto sys_sendto sock_sendmsgsendmsg sys_send ...
- Java HashMap并发死循环
在淘宝内网里看到同事发了贴说了一个CPU被100%的线上故障,并且这个事发生了很多次,原因是在Java语言在并发情况下使用HashMap造成Race Condition,从而导致死循环.这个事情我4. ...
- time,gettimeofday,clock_gettime
time()提供了秒级的精确度 1.头文件 <time.h> 2.函数原型 time_t time(time_t * timer) 函数返回从UTC1970-1-1 0:0:0开始到现在的 ...
- RocketMQ,JStorm与Tair使用笔记
关于RocketMQ 启动mq nohup sh mqnamesrv -n 10.150.0.94:9876 & nohup sh mqbroker -n 10.150.0.94:9876 ...
- Java进阶(四十一)多线程讲解
Java多线程讲解 前言 接到菜鸟网络的电话面试,面试官让自己谈一下自己对多线程的理解,现将其内容整理如下. 线程生命周期 Java线程具有五种基本状态 新建状态(New):当线程对象创建后,即进入了 ...
- Cocos2D在Xcode7和iOS 9.2上IMP调用出错
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 原来的代码一直在Xcode6.4上和iOS 8.4上运行,没有 ...