- 在网络通信中,很多操作会使得进程阻塞:
- 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("hello\n");
fgets(buf,sizeof(buf),stdin);
printf("fgets阻塞接触\n");
int ret=alarm(2);
printf("%d\n",ret);
read(0,buf,sizeof(buf));
printf("read阻塞接触\n");
printf("buf:%s\n",buf);
}
return 0;
}