网络超时检测-11.9
-
在网络通信中,很多操作会使得进程阻塞:
- TCP套接字中的recv/accept
- UDP套接字中的recvfrom
-
超时检测的必要性
- 避免进程在没有数据时无限制地阻塞
- 实现某些特定协议要求,比如某些设备规定,发送请求数据后,如果多长时间后没有收到来自设备的回复,需要做出一些特殊处理
- 自带超时参数的函数
如使用select/poll/epoll函数最后一个参数可以设置超时。
1)select设置超时
struct timeval tm = {2, 0};//设置2s打算阻塞
sret = select(maxfd + 1, &tempfds, NULL, NULL, &tm);
第五个参数:
struct timeval {
long tv_sec; /*秒*/
long tv_usec; /*微秒*/
};
2.poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
第三个参数:时间单位是毫秒 -1阻塞, 2000=2s
ret = poll(event, num, 2000);//超时检测时间为2s
3.epoll 设置的是epoll_wait
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
第四个参数:时间单位是毫秒 -1阻塞, 2000=2s
ret = epoll_wait(epfd, events, 20, 2000);
设置超时后的返回值都为:<0 error
=0 超时
>0 正确
2.利用setsockopt属性设置
Linux中socket属性
选项名称 |
说明 |
数据类型 |
==== 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_REUSEADDR |
允许重用本地地址和端口 |
int |
SO_TYPE |
获得套接字类型 |
int |
SO_BSDCOMPAT |
与BSD系统兼容 |
int |
==== IPPROTO_IP 网络层 ==== |
||
IP_HDRINCL |
在数据包中包含IP首部 |
int |
IP_OPTINOS |
IP首部选项 |
int |
IP_TOS |
服务类型 |
int |
IP_TTL |
生存时间 |
int |
IP_ADD_MEMBERSHIP |
将指定的IP加入多播组 |
struct ip_mreq |
==== IPPRO_TCP 传输层 ==== |
||
TCP_MAXSEG |
TCP最大数据段的大小 |
int |
TCP_NODELAY |
不使用Nagle算法 |
int |
功能:设置/获取网络属性;
原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
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);
参数:
int sockfd:指定要设置/获取哪个套接字的属性;
int level:指定要控制的协议层次;
SOL_SOCKET:应用层 通用套接字选项; man 7 socket
IPPROTO_TCP:TCP选项 man 7 TCP
IPPROTO_UDP:UDP选项 man 7 UDP
IPPROTO_IP:IP选项; man 7 IP
int optname:指定要控制的内容,指定控制方式;
--- SOL_SOCKET: man 7 socket -----
SO_REUSEADDR:允许端口快速重用 optval: int*
SO_BROADCAST:允许广播 optval: int*
SO_RCVBUF/SO_SNDBUF:接收缓冲区 发送缓冲区大小
SO_RCVTIMEO/SO_SNDTIMEO:接收超时时间,发送超时时间
void *optval:根据optname不同,该类型不同;(数据类型)
socklen_t optlen/socklen_t *optlen:真实的optval指针指向的内存空间的大小;
返回值:
成功,返回0;
失败,返回-1,更新errno;
3、利用alarm定时器设置
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:在进程中设置一个定时器
参数:seconds:定时时间,单位为秒
返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则
返回上一个闹钟时间的剩余时间,否则返回0。
alarm(5) 闹钟 定时器
//5秒之后会,会有一个信号产生(SIGALRM)
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
功能:对接收到的指定信号处理
signum 信号
struct sigaction
{
void (*sa_handler)(int); //信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理函数
sigset_t sa_mask;
int sa_flags; //信号属性; SA_RESTART自重启属性
#define SA_RESTART 0x10000000
void (*sa_restorer)(void);
};
//设置信号属性
struct sigaction act;
sigaction(SIGALRM,NULL,&act);//获取原属性
act.sa_handler=handler;//修改属性
sigaction(SIGALRM,&act,NULL);//将修改的属性设置回去
注:在recv前调用alarm函数
alarm的 SIGALRM信号产生后会打断(终端)下面的系统调用recv;
打断后相当于recv错误返回。
信号改变行为后,当前进程所有行为都被改变,若想要再次改变回原行为,需要再次执行sigaction.
注意:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替,时间到了会给进程发送一个SIGALRM信号,这个信号也有结束进程的功能
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void handler(int sig)
{
printf("timeout................n");
}
int main(int argc, char const *argv[])
{
struct sigaction act;
sigaction(SIGALRM,NULL,&act);//获取原属性
act.sa_handler=handler;//修改属性
sigaction(SIGALRM,&act,NULL);//将修改的属性设置回去
// sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
char buf[64]={};
while (1)
{
alarm(2);
printf("hellon");
fgets(buf,sizeof(buf),stdin);
printf("fgets阻塞接触n");
int ret=alarm(2);
printf("%dn",ret);
read(0,buf,sizeof(buf));
printf("read阻塞接触n");
printf("buf:%sn",buf);
}
return 0;
}