定时器处理非活动连接
在这我们主要使用的是前面介绍的第二种信号量的方法。
如果某一用户connect()到服务器之后,长时间不交换数据,一直占用服务器端的文件描述符,导致连接资源的浪费。这时候就应该利用定时器把这些超时的非活动连接释放掉,关闭其占用的文件描述符。这种情况也很常见,当你登录一个网站后长时间没有操作该网站的网页,再次访问的时候你会发现需要重新登录。
项目中使用的是SIGALRM信号来实现定时器,利用alarm函数周期性的触发SIGALRM信号,信号处理函数利用管道通知主循环,主循环接收到该信号后对升序链表上所有定时器进行处理,若该段时间内没有交换数据,则将该连接关闭,释放所占用的资源。
上一篇文章用代码实现了升序定时器链表,在这我们利用这个升序定时器链表处理这个非活动连接。
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/uio.h>
#include<string.h>
#include "lst_time.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);//这个函数主要提供对文件描述符的各种控制操作,fd是被操作的文件描述符,后面这个参数是获取fd的状态标志,返回值就是fd的状态标志
int new_option=old_option|O_NONBLOCK;
fcntl(fd,F_SETFL,new_option);//设置fd的状态标志
return old_option;//返回旧的状态标志,以便日后恢复
}
void addfd(int epollfd,int fd)
{
epoll_event event;
event.data.fd = fd;
event.events=EPOLLIN|EPOLLET;
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);//send是往文件描述符pipefd[1]写入数据,接下来两个参数分别是缓冲区位置和大小,TCP数据读写
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);//因为一次alarm调用只会引起一次SIGALRM信号,所以需要重新定时,以不断触发SIGALRM信号
}
//定时器回调函数,它删除非活动连接socket上的注册事件,并关闭
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(argv<=2)
{
char* ip_address=argv[0];
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int ret=0;
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 listenfd = socket(PF_INET,SOCK_STREAM,0);//使用默认协议
assert(listenfd>=0);
ret = bind(lestenfd,(struct sockaddr*)&address,sizeof(address));
assert(ret!=-1);
ret=listen(lestenfd,5);
assert(ret!=-1);
epoll_event events[MAX_EVENT_NUMBER];
int eppllfd = epoll_create(5);
assert(epollfd!=-1);
addfd(epollfd,listenfd);
ret = socketpair(PF_UNIX,SOCK_STREAM,0,pipefd);
assert(ret!=-1);
setnonblocking(pipefd[1]);
addfd(epollfd,pipefd[0]);
//设置信号处理
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);
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);
int connfd = accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
addfd(epollfd,connfd);
users[connfd].address = client_address;
users[connfd].sockfd=connfd;
//创建定时器,设置其回调函数与超时时间,然后绑定定时器与用户数据,最后将定时器添加到链表TIMER_LST中
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("从文件描述符的哪个位置读了多少字节的数据");
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+3*TIMESLOT;
printf("adjust timer once\n");
timer_lst.adjust_timer(timer);
}
}
}
}
if(timeout)
{
timer_handler();
timeout = false;
}
}
close(listenfd);
close(pipefd[1]);
close(pipefd[0]);
delete[] users;
return 0;
}