首先我们实现一个数据结构用来存储定时器,它是一个升序的双向链表。主要在其中实现了插入定时器,删除定时器,调整定时器位置的操作,其实现如下:
cpp
#ifndef LST_TIMER
#define LST_TIMER
#include <netinet/in.h>
#include <stdio.h>
#include <time.h>
#define BUFFER_SIZE 64
class util_timer;
/*用户数据结构:客户端socket地址,socket文件描述符、读缓存、计时器*/
struct client_data{
sockaddr_in address;
int sockfd;
char buf[BUFFER_SIZE];
util_timer* timer;
};
/*定时器类*/
class util_timer{
public:
util_timer() : prev(NULL), next(NULL){
}
public:
time_t expire; /*任务的超时时间,这里是绝对时间*/
void (*cb_func) (client_data*); /*任务回调函数*/
client_data* user_data; /*用户数据*/
util_timer* prev; /*前一个定时器*/
util_timer* next; /*下一个定时器*/
};
/*定时器链表,一个升序、双向的链表,且带有头节点和尾节点*/
class sort_timer_lst{
public:
sort_timer_lst(): head(NULL), tail(NULL){
}
~sort_timer_lst(){
util_timer* tmp = head;
while(tmp){
head = tmp -> next;
delete tmp;
tmp = head;
}
}
/*将目标定时器timer添加到链表中*/
void add_timer(util_timer* timer){
if( !timer ) return ; /*timer不存在,插入不可能实现,直接返回*/
if( !head ){ /*头节点不存在,则当前定时器作为链表的头节点和尾节点*/
head = tail = timer;
return ;
}
/*如果定时器的超时时间比头节点的超时时间少,则当前定时器作为头节点*/
if(timer -> expire < head -> expire){
timer -> next = head;
head -> prev = timer;
head = timer;
return ;
}
/*如果定时器的超时时间比头节点的超时时间多,则调用重载函数add_timer(util_timer* timer, util_timer* lst_head)
来插入到链表的指定位置,保证了链表的升序性质
*/
add_timer(timer, head);
}
/*当定时器任务发生改变时,修改定时器在链表中的位置*/
void adjust_timer(util_timer* timer){
if( !time ) return ;
util_timer* tmp = timer -> next;
if( !tmp || (timer -> expire < tmp -> expire)) return ; /*如果此时的timer为队尾或者定时器的超时时间仍然比下一个元素小,则不改变位置*/
/*如果定时器是链表的头节点,则把当前定时器取出来重新插入*/
if(timer == head){
head = head -> next;
head -> prev = NULL;
timer -> next = NULL;
add_timer(timer, head);
}
else{ /*如果不是队首,则将该定时器取出并从它后面的链表中选位置插入*/
timer -> prev -> next = timer -> next;
timer -> next -> prev = timer -> prev;
add_timer(timer, timer -> next);
}
}
void del_timer(util_timer* timer){ /*删除目标定时器*/
if( !timer ) return ;
if( (timer == head) && (timer == tail)){/*如果链表中只有一个定时器*/
delete timer;
head = NULL;
tail = NULL;
return ;
}
if(timer == head){
head = head -> next;
head -> prev = NULL;
delete timer;
return;
}
if(timer == tail){
tail = tail -> prev;
tail -> next = NULL;
delete tail;
return ;
}
timer -> prev -> next = timer -> next;
timer -> next -> prev = timer -> prev;
return ;
}
/*SIGALRM信号每次被触发就在其信号处理函数中执行一次tick函数,用来处理链表上到期的任务*/
void tick(){
if( !head ){
return ;
}
printf("time tick\n");
time_t cur = time(NULL); /*获取系统当前的时间*/
util_timer* tmp = head;
/*从头节点开始依次处理每个定时器,直到遇到一个尚未到期的定时器*/
while(tmp){
/*当前定时器往后的定时器们都不会到期,直接退出循环*/
if(cur < tmp -> expire) break;
/*调用定时器的回调函数,执行定时任务*/
tmp -> cb_func(tmp -> user_data);
/*执行完任务后,就把他从链表中删除*/
/*重置链表头*/
head = tmp -> next;
if(head){
head -> prev = NULL;
}
delete tmp;
tmp = head;
}
}
private:
/*重载函数实现将定时器插入到除头节点的指定位置*/
void add_timer(util_timer* timer, util_timer* lst_head){
util_timer* prev = lst_head;
util_timer* tmp = prev -> next;
/*遍历head之后的链表,直到找到超时时间大于定时器的节点,并把定时器插入*/
while(tmp){
if(timer -> expire < tmp -> expire){
prev -> next = timer;
timer -> next = tmp;
tmp -> prev = timer;
timer -> prev = prev;
break;
}
prev = tmp;
tmp = tmp -> next;
}
/*如果没有找到超时时间大于定时器的节点,就把当前定时器设置为尾节点*/
if( !tmp ){
prev -> next = timer;
timer -> prev = prev;
timer -> next = NULL;
tail = timer;
}
}
private:
util_timer* head;
util_timer* tail;
};
#endif
在接下来的对于非活动连接的处理方面,我们将每一对服务端和客户端之间的连接相关的连接数据保存下来,并且加上其超时时间共同打包到上述数据结构的单个节点中。
同时,我们设置了统一事件源,SIGALRM信号每一次被触发,主循环中调用一次定时任务处理函数,用来处理到期的定时器。
cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>
#include "lst_timer.h"
#define FD_LIMIT 65535
#define MAX_EVENT_NUMBER 1024
#define TIMESLOT 5
static int pipefd[2];
static sort_timer_lst timer_lst; /*升序链表*/
static int epollfd = 0;
int setnonblocking(int fd){
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
void addfd(int epollfd, int fd){ /*往内核事件表中添加要监听的事件*/
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET; /*设置该事件数据可读,并且采用ET模式来操作该文件描述符*/
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); /*往事件表中注册该事件*/
setnonblocking(fd);
}
void sig_handler(int sig){
int save_errno = errno;
int msg = sig;
send(pipefd[1], (char*)&msg, 1, 0);
errno = save_errno;
}
void addsig(int sig){
struct sigaction sa; /*该结构体描述信号的细节*/
memset(&sa, '\0', sizeof(sa));
sa.sa_handler = sig_handler; /*信号处理函数*/
sa.sa_flags |= SA_RESTART;
sigfillset(&sa.sa_mask); /*设置信号集的掩码为所有信号*/
assert( sigaction(sig, &sa, NULL) != -1);
}
void timer_handler(){
timer_lst.tick();
alarm( TIMESLOT );
}
void cb_func(client_data* user_data){
epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data -> sockfd, 0);
assert(user_data);
close(user_data -> sockfd);
printf("close fd %d\n", user_data -> sockfd);
}
int main(int argc, char* argv[]){
if(argc <= 2){
printf("usag: %s ip_address port_number \n", basename(argv[0]));
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]); /*atoi是将字符串转化为整数*/
int ret = 0;
struct sockaddr_in address; /*ipv4类型的socket结构体*/
bzero(&address, sizeof(address)); /*初始化结构体为\0*/
address.sin_family = AF_INET; /*设置地址族*/
address.sin_port = htons(port); /*htons是将主机字节序转化为网络字节序,该行设置socket的端口*/
inet_pton(AF_INET, ip, &address.sin_addr); /*网络字节序和点分十进制的转化,设置socket的ip地址*/
int listenfd = socket(PF_INET, SOCK_STREAM, 0);/*设置TCP传输方式,该socket的文件描述符为listenfd*/
assert(listenfd >= 0);/*如果socket没有创建成功,则assert会打出断言失败的信息*/
/*assert函数,其中表达式为真,继续执行,其中表达式为假,输出断言失败信息*/
ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));/*将address的地址分配给listenfd文件描述符
即代表listenfd和address绑定*/
assert(ret != -1);
ret = listen(listenfd, 5);/*监听listenfd上的客户端连接*/
assert(ret != -1);
epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);/*创建一个文件描述符来标识内核事件表,并设置内核事件表大小为5*/
assert(epollfd != -1);
addfd(epollfd, listenfd); /*将listenfd文件描述符的可读事件添加到内核事件表中*/
/*创建无名套接字存放在pipefd中,这对套接字全双工*/
ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);
assert(ret != -1);
setnonblocking(pipefd[1]); /*设置写端非阻塞*/
addfd(epollfd, pipefd[0]); /*将读端加入到epoll内核事件表中监视*/
/*设置信号处理函数*/
addsig(SIGALRM);
addsig(SIGTERM);
bool stop_server = false;
client_data* users = new client_data[FD_LIMIT];/*客户端数组*/
bool timeout = false;
alarm(TIMESLOT);/*设置定时器*/
while( !stop_server ){
int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1); /*在一段时间内等待文件描述符上的事件,返回的number为就绪文件描述符的个数*/
if( (number < 0) && (errno != EINTR)){
printf("epoll failure\n");
break;
}
/*循环遍历各个就绪的文件描述符*/
for(int i = 0; i < number; i ++){
int sockfd = events[i].data.fd;
/*处理新到的客户端连接*/
if(sockfd == listenfd){
/*初始化客户端连接地址*/
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof(client_address);
/*同意服务端和客户端连接,connfd为该连接的文件描述符*/
int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength);
/*将该连接加入内核事件表进行监听*/
addfd(epollfd, connfd);
/*初始化该连接对应的连接资源*/
users[connfd].address = client_address;
users[connfd].sockfd = connfd;
/*创建定时器,设置其回调函数与超时时间*/
util_timer* timer = new util_timer;
timer -> user_data = &users[connfd];
timer -> cb_func = cb_func;
time_t cur = time(NULL);
timer -> expire = cur + 3 * TIMESLOT;
users[connfd].timer = timer;
timer_lst.add_timer(timer);
}
/*处理定时器信号 */
else if( (sockfd == pipefd[0]) && (events[i].events & EPOLLIN)){
int sig;
char signals[1024];
ret = recv(pipefd[0], signals, sizeof( signals ), 0);
if(ret == -1) continue;
else if(ret == 0) continue;
else{
for(int i = 0; i < ret ; ++ i){
switch( signals[i] ){
case SIGALRM:{
timeout = true;
break;
}
case SIGTERM:{
stop_server = true;
}
}
}
}
}
/*处理客户端上接收到的数据*/
else if(events[i].events & EPOLLIN){
/*初始化缓存区*/
memset(users[sockfd].buf, '\0', BUFFER_SIZE);
/*接收数据到缓存区*/
ret = recv(sockfd, users[sockfd].buf, BUFFER_SIZE - 1, 0);
printf("get %d bytes of client data %s from %d\n", ret, users[sockfd].buf, sockfd);
util_timer* timer = users[sockfd].timer;
if(ret < 0){
if(errno != EAGAIN){
cb_func( &users[sockfd]);
if(timer){
timer_lst.del_timer(timer);
}
}
}
else if(ret == 0){
cb_func(&users[sockfd]);
if(timer){
timer_lst.del_timer(timer);
}
}
else {
/*如果有数据传输,则将超时时间延后三个单位,并且调整定时器在队列中的位置*/
if(timer){
time_t cur = time(NULL);
timer -> expire = cur + TIMESLOT * 3;
printf("adjust timer once\n");
timer_lst.adjust_timer(timer);
}
}
}
else{
//others
}
}
if(timeout){
timer_handler();
timeout = false;
}
}
close(listenfd);
close(pipefd[1]);
close(pipefd[0]);
delete [] users;
return 0;
}