定时事件
当服务器遇见一些idle连接的时候需要及时处理它们以保证资源的充足性,需要定期检测连接是否处于活动状态。每个连接一个定期事件,事件可能非常多,因此需要将这些时间组织起来,这些事件在预期时间达到时触发某种处理机制而不影响服务器的主要逻辑。每个定时事件封装为定时器,然后将这些定时器用某种容器统一管理(添加定时器、删除定时器、激活定时机制等)。前一篇博文的《非阻塞connect》是通过在socket描述符里设置SO_SNDTIMETO选项来设置处理超时连接的情形,更为普遍的方式是通过alarm系统调用产生SIGALRM信号激活定时处理函数。
前面提到首先要封装定时器(一段时间后触发某段代码的事件),然后再将他们用定时器容器统一管理。容器的选择具有很大的灵活性比如:
1、基于超时时间升序的链表,将每个定时器按照超时时间升序排列起来,一旦有SIGALARM产生就检查链表中哪些定时器超时了就激活这些定时器的超时处理代码,显然插入一个定时器时间复杂度为O(n)n为容器大小。
2、容器采用时间轮的方式,时间轮就是一个圆盘分为N份,每份就是个时间槽,相邻的两个时间槽的时间间隔相等,然后每个槽指向的是一个链表,该链表的元素是定时器,形象的说看起来像下雨天撑开的雨伞在滴水。值得注意的是每个定时器还有个特殊的数据结构那就是rotation表示时间轮旋转多少圈该定时器到期。那么现在判断定时器超时的条件是:时间轮的指针指向一个槽,该槽对应上的所有定时器接受超时检查,而检查方法就是看rotation是否为0若为0表明本轮转到此处该定时器已经超时,那么执行超时逻辑,若rotation仍大于0则减一。alarm每次发送SIGALRM信号时间轮的指针就向前走一个时间槽,然后该槽上对应的链表的所有定时器接受定时检查操作。注意这里每个槽对应的定时器链表是无序的,所以才需要rotation辅助判断超时,所有该链上所有的定时器都接受检查,不像1那样只需要检查到链表的某个位置即可终止(定时器不超时的那个终止升序的缘故)。这看起来像散列,插入操作理论上还是O(n)其中n为定时器个数,但是实际上只要运气不是那么背不会出现所有定时器都散列到同一个槽上的悲惨情形吧,总之效率上比1不会劣。
3、时间堆,采用小根堆将定时器组织起来,每次只需要获取堆顶元素判断其是否超时(这里堆调整的依据是定时器的超时时间大小)。这样alarm每次发送SIGALARM信号,就检查小根堆的堆顶直至堆顶的定时器没有超时为止,获得的这些堆顶元素都是超时定时器它们就该去执行它们自己相应的超时逻辑了。
4、IO复用技术,前面有博文《统一事件源》的思想是将信号和IO事件统一起来。这里可以利用IO复用技术统一处理定时事件,具体做法是:以处理客户连接socke描述符为例,三组IO复用技术都有一个超时参数timeout,在超时间内timeout内等待各描述符上是否有事件发生(这里不是超时事件,而是发送数据之类的某种事件),若timeout内没有事件发生则说明超时了执行超时逻辑。
接下来将给出上述4个的代码实例,其顺序分别为:升序链表定时器容器,基于升序链表的处理非活动连接;时间轮定时器容器,基于时间轮处理非活动连接;时间堆定时器容器,基于时间堆处理非活动连接;IO复用定时器伪码;客户端代码。
#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<assert.h>#include<stdio.h>#include<unistd.h>#include<string.h>#include<stdlib.h>#include<errno.h>#include<iostream>#define BUF_SIZE 64using namespace std;int main(int argc,char* argv[]){ if(argc<=4){ cout<<"argc<=4"<<endl; return 1; } const char* ip=argv[1];//服务端IP int port=atoi(argv[2]);//服务端端口号 int time=atoi(argv[3]);//睡眠发送时间,即睡眠多少s后发送数据,模拟非活动连接 const char* send_char=argv[4];//发送的数据内容,用于区分不同的连接 struct sockaddr_in address; bzero(&address,sizeof(address)); address.sin_family=AF_INET; inet_pton(AF_INET,ip,&address.sin_addr); address.sin_port=htons(port); int sockfd=socket(PF_INET,SOCK_STREAM,0); assert(sockfd>=0); if(connect(sockfd,(struct sockaddr*)&address,sizeof(address))<0){ cout<<"connect error:"<<strerror(errno)<<endl; return 1; } else{ while(1){ sleep(time);//睡眠时间的设定区分活动连接和非活动连接 int ret=send(sockfd,send_char,sizeof(send_char),0); if(ret<0){ cout<<"send error"<<endl; break; } } } close(sockfd); return 0;}