一个高性能服务器通常需要处理非活动连接,来提升性能和释放计算机资源,我们可以为每个客户端设置一个定时器,其中包含超时时间,处理函数以及函数的参数。通过升序双向链表将所有客户端的定时器组织起来,通过alarm函数定时,当SIGALRM信号到来时,遍历链表来调用超时客户端的回调函数来关闭非活动连接。下面通过代码来展示这一方法。
#ifndef _LIST_TIME_H
#define _LIST_TIME_H
#include
#include
#include
class util_timer;
//用户数据结构
struct client_data
{
sockaddr_in address;//客户端socket地址
int sockfd; //socket文件描述符
char buf[BUFSIZ]; //读缓存
util_timer *timer; //定时器
};
//定时器类
class util_timer
{
public:
time_t timeout; //任务超时时间
void (*function) (client_data*);//任务回调函数
client_data* user_data; //用户数据
util_timer *prev; //指向前一个定时器
util_timer *next; //指向后一个定时器
public:
util_timer();
};
/*定时器链表。升序、双向、且带有头结点和尾节点*/
class sort_list_time{
private:
util_timer *head;
util_timer *end;
/*被公有add_timer和adjust_tiemr函数调用
表示把定时器timer添加到node以后(包含node)*/
void add_timer(util_timer* timer,util_timer* node);
public:
sort_list_time();
/*链表被销毁时,删除其中所有的定时器*/
~sort_list_time();
/*将定时器添加到链表中*/
void add_timer(util_timer* timer);
/*将定时器从链表中删除*/
void del_timer(util_timer* timer);
/*调整定时器在链表中的位置*/
void adjust_timer(util_timer* timer);
/*处理链表上到期的任务*/
void tick();
};
#endif
list_time.cpp
#include"list_time.h"
#include
util_timer::util_timer():prev(NULL),next(NULL){}
sort_list_time::sort_list_time()
{
this->head = new util_timer();
this->end = new util_timer();
head->next = end;
end->prev = head;
}
sort_list_time::~sort_list_time()
{
util_timer *p = this->head;
while(p)
{
this->head = p->next;
delete p;
p = this->head;
}
}
void sort_list_time::add_timer(util_timer* timer)
{
if(timer == NULL)
return;
if(this->head->next == this->end)
{
timer->next = this->end;
timer->prev = this->head;
this->head->next = timer;
this->end->prev = timer;
return;
}
if(timer->timeout < this->head->next->timeout)
{
timer->next = this->head->next;
timer->prev = this->head;
this->head->next->prev = timer;
this->head->next = timer;
return;
}
add_timer(timer,this->head->next->next);
}
void sort_list_time::add_timer(util_timer* timer,util_timer* node)
{
while(node != this->end)
{
if(timer->timeout < node->timeout)
{
timer->next = node;
timer->prev = node->prev;
node->prev->next = timer;
node->prev = timer;
return;
}
node = node->next;
}
/*若没找到比timer大的,则添加在末尾*/
timer->next = this->end;
timer->prev = this->end->prev;
this->end->prev->next = timer;
this->end->prev = timer;
}
void sort_list_time::del_timer(util_timer* timer)
{
if(timer == NULL)
return;
/*当从链表中删除定时器时,将用户数据中指向定时器的指针悬空*/
timer->user_data->timer = NULL;
timer->prev->next = timer->next;
timer->next->prev = timer->prev;
delete timer;
}
/*当定时任务发生变换时,调整定时器在链表中的位置,此处
只考虑定时时间延长的情况*/
void sort_list_time::adjust_timer(util_timer* timer)
{
if(timer == NULL)
return;
/*若该节点已经是尾节点或者依旧比下一个节点小,则不调整*/
if((timer->next == this->end) || (timer->timeout < timer->next->timeout))
return;
/*否则将节点从链表中取出,在插入后面的合适位置*/
timer->prev->next = timer->next;
timer->next->prev = timer->prev;
add_timer(timer,timer->next->next);
}
void sort_list_time::tick()
{
if(this->head->next == this->end)
return;
time_t cur = time(NULL);/*获得当前系统时间*/
util_timer* node = this->head->next;
while(node != this->end)
{
if(node->timeout > cur)
return;
/*调用定时器中的回调函数,以执行定时任务*/
node->function(node->user_data);
util_timer* p = node;
node = node->next;
/*执行完后从链表中删除*/
del_timer(p);
}
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"list_time.h"
#include
#define TIMEOUT 5
#define MAX_EVENT_NUMBER 1024
static int epollfd;
static sort_list_time timer_list;
static int pipefd[2];
void set_nonblock(int fd)
{
int flag = fcntl(fd,F_GETFL);
flag |= O_NONBLOCK;
fcntl(fd,F_SETFL,flag);
}
void addfd(int fd,bool flag)
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN;
if(flag)
event.events |= EPOLLET;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
set_nonblock(fd);
}
void sig_handler(int sig)
{
send(pipefd[1],(char*)&sig,1,0);
}
void set_sig(int sig)
{
struct sigaction sa;
sa.sa_handler = sig_handler;
sa.sa_flags |= SA_RESTART;
sigfillset(&sa.sa_mask);
sigaction(sig,&sa,NULL);
}
/*定时器回调函数,删除非活动连接socket上的注册事件,并关闭连接*/
void cb_function(client_data* user)
{
epoll_ctl(epollfd,EPOLL_CTL_DEL,user->sockfd,NULL);
close(user->sockfd);
printf("close fd %d\n",user->sockfd);
}
void timer_handler()
{
timer_list.tick();//定时处理任务
alarm(TIMEOUT);//重新定时,以不断触发sigalarm信号
}
int main(int argc,const char* argv[])
{
if(argc < 3)
{
printf("usage: %s ip_address port_number\n",argv[0]);
exit(1);
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int listenfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in listenaddr,connaddr;
socklen_t connaddrlen = sizeof(connaddr);
listenaddr.sin_family = AF_INET;
listenaddr.sin_port = htons(port);
inet_pton(AF_INET,ip,&listenaddr.sin_addr);
bind(listenfd,(struct sockaddr*)&listenaddr,sizeof(listenaddr));
listen(listenfd,5);
epoll_event events[MAX_EVENT_NUMBER];
epollfd = epoll_create(3);
addfd(listenfd,false);//LT模式加入epoll中
int ret = socketpair(AF_UNIX,SOCK_STREAM,0,pipefd);
assert(ret != -1);
addfd(pipefd[0],true);
set_sig(SIGALRM);
set_sig(SIGTERM);
client_data *users = new client_data[MAX_EVENT_NUMBER];
bool stop_server = false;
bool timeout= false;
alarm(TIMEOUT)/*定时*/;
while(!stop_server)
{
int num = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
if(num == -1)
{
if(errno == EINTR)
continue;
perror("epoll_wait:");
break;
}
for(int i = 0;i < num;i++)
{
int sockfd = events[i].data.fd;
/*处理新到的客户端连接*/
if(sockfd == listenfd)
{
memset(&connaddr,'\0',sizeof(connaddr));
int connfd = accept(listenfd,(struct sockaddr*)&connaddr,&connaddrlen);
addfd(connfd,true);//ET模式加入epoll中
users[connfd].address = connaddr;
users[connfd].sockfd = connfd;
util_timer* timer = new util_timer;
time_t cur = time(NULL);
timer->timeout = cur + (2*TIMEOUT);
timer->function = cb_function;
timer->user_data = &users[connfd];
users[connfd].timer = timer;
timer_list.add_timer(timer);
}
/*处理信号*/
else if(sockfd == pipefd[0])
{
int ret = 0;
char signals[BUFSIZ];
while(1)
{
memset(signals,'\0',sizeof(signals));
ret = recv(sockfd,signals,sizeof(signals),0);
if(ret == -1)
{
if(errno != EAGAIN)
{
perror("recv signal error:");
}
break;
}
for(int i = 0;i < ret;i++)
{
switch(signals[i])
{
case SIGALRM:
{
/*不立即处理定时任务,先用timeout标记有定时
任务需要处理,优先处理I/O任务*/
timeout = true;
break;
}
case SIGTERM:
{
stop_server = true;
break;
}
}
}
}
}
else if(events[i].events & EPOLLIN){
while(true)
{
memset(users[sockfd].buf,'\0',sizeof(users[sockfd].buf));
int ret = recv(sockfd,users[sockfd].buf,sizeof(users[sockfd].buf),0);
if(ret == -1)
{
if(errno != EAGAIN)
{
/*如果发生错误,则关闭连接,移除定时任务*/
cb_function(&users[sockfd]);
timer_list.del_timer(users[sockfd].timer);
}
break;
}
else if(ret == 0)
{
/*若对端关闭,则关闭连接,移除定时任务*/
cb_function(&users[sockfd]);
timer_list.del_timer(users[sockfd].timer);
break;
}
send(sockfd,users[sockfd].buf,strlen(users[sockfd].buf),0);
}
if(users[sockfd].timer)
{
/*如果某个客户端有数据可读,则调整该连接的定时器,以延迟
该连接关闭的事件*/
time_t cur = time(NULL);
users[sockfd].timer->timeout = cur + (2*TIMEOUT);
printf("adjust timer once\n");
timer_list.adjust_timer(users[sockfd].timer);
}
}
}
if(timeout)
{
timer_handler();
timeout = false;
}
}
close(epollfd);
close(listenfd);
close(pipefd[0]);
close(pipefd[1]);
delete [] users;
return 0;
}
参考:Linux高性能服务器编程 游双