定时是指一段时间后触发某段代码的机制,Linux提供了以下三种定时方法:
- socket选项SO_RCVTIMEO和SO_SNDTIMEO
- SIGALRM信号
- I/O复用系统调用的超时参数
socket选项SO_RCVTIMEO和SO_SNDTIMEO
分别用来设置socket接收数据超时时间和发送数据超时时间,所以用于与socket数据收发有关的系统调用,如send,recv,sendmsg,recvmsg,accept,connect
等。
example,connect超时
int timeout_connect(const char* ip, int port, int time{
/*
初始化socket参数
*/
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
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);
assert(ret != -1);
ret = connect(sockfd, (sockaddr*)&addr, sizeof(addr));
if(ret == -1){
if(errno == EINPROGRESS){
// connect系统调用超时了,处理定时任务
return -1;
}
// 连接到server时的错误
return -1;
}
}
int main(){
int sockfd = timeout_connect(ip, port, time);
if(sockfd < 0){
return -1;
}
}
SIGALRM
由alarm和settimer函数设置的实时闹钟一旦超时,就会触发SIGALRM信号。
基于升序链表的定时器
定时器通常包含超时时间和任务回调函数。有时还含有回调函数的参数和是否重启定时器的信息。
原文中使用了双向链表,但没有使用头尾节点,编程实现繁琐,这里给出带头尾节点的实现方法:
Cpp实现
处理非活动连接
服务器通常需要定期处理非活动连接,给客户端发送重连请求或关闭连接。
Linux内核中提供了socket的KEEPALIVE选项来定期检查连接是否处于活动状态的定期检查机制。
但是这种机制使得应用程序对于连接的管理变得复杂,我们可以在应用层来实现类似的机制。
一下代码利用alarm函数周期性触发SIGALRM信号,该信号的信号处理函数利用管道通知主循环执行定时器链表上的定时任务——关闭非活动的连接。
代码实现链接
IO复用系统调用的超时参数
因为I/O复用可能在超时时间到期之前就返回(有就绪事件发生),所以如果需要使用I/O复用的超时的话,需要不断更新定时参数以反映剩余时间。
const int TIMEOUT = 5000;
int timeout = TIMEOUT;
time_t start = time(NULL);
time_t end = time(NULL);
while(true){
printf("The timeout is now %d mil-seconds\n", timeout);
start = time(NULL);
int number = epoll_wait(epollfd, events, MAX_NUM, timeout);
if(number < 0){
// error
break;
}
else if(number == 0){
// 说明epoll_wait超时时间到了,此时可处理定时任务,并重置定时时间
timeout = TIMEOUT;
continue;
}
end = time(NULL);
// 如果epoll_wait的返回值大于0,则本次调用的持续时间是
// (end - start) * 1000 ms, 我们需要处理一下
timeout = timeout - (end - start) * 1000;
if(timeout <= 0){
timeout = TIMEOUT;
}
// handle connections;
}