基于升序链表的定时器踢掉空闲连接

服务器程序通常要定期处理空闲连接:给客户端发一个重连请求,或者关闭该链接,或者其他。linux内核中提供了对连接是否处于活动状态的定期检查机制,我们可以通过socket选项KEEPALIVE来激活它,不过使用这种方式将使得应用程序对廉洁的管理变得比较复杂。因此,我们可以考虑在应用层实现类似KEEPALIVE的机制,以管理所有长时间处于非活动状态的连接。

在以下代码中,我们利用alarm函数周期性的触发SIGALARM信号,该信号的信号处理函数利用管道通知主线程循环执行定时器链表上的定时任务——关闭处于空闲状态的连接

2_list_timer_kick_idle_conn.h

#ifndef LST_TIMER_H
#define LST_TIMER_H

#include <arpa/inet.h>
#include <time.h>
#include <stdio.h>

#define	BUFFER_SIZE 64
class UtilTimer;	//前向声明

/* 客户端数据:客户端socket地址、socket文件描述符、读缓冲区和定时器 */
struct ClientData {
	sockaddr_in addr;
	int sockfd;
	char buf[BUFFER_SIZE];
	UtilTimer* timer;
};

/* 定时器类 */
class UtilTimer {
public:
	UtilTimer() : prev(nullptr), next(nullptr) {}

	time_t expire;	//任务超时时间,这里使用绝对时间
	void (*timerCallback) (ClientData*);	//定时器回调函数
	ClientData* userData;
	UtilTimer* prev;
	UtilTimer* next;
};

/* 定时器链表。它是一个升序的双向链表,带头指针和尾指针 */
class SortTimerList {
private:
	UtilTimer* head;
	UtilTimer* tail;
public:
	SortTimerList() : head(nullptr), tail(nullptr) {}
	~SortTimerList() {
		UtilTimer* cur = head;
		while (cur) {
			head = cur->next;
			delete cur;
			cur = head;
		}
		head = tail = nullptr;
	}

	/* 将定时器timer添加到链表中 */
	void addTimer(UtilTimer* timer) {
		if (!timer) {
			return;
		}
		else if (!head) {
			head = tail = timer;
			return;
		}

		/* 这里要保证定时器链表有序 */
		if (timer->expire < head->expire) {
			timer->next = head;
			head->prev = timer;
			head = timer;
			return;
		}

		addTimer(timer, head);
	}

	/* 当某个定时器任务发生变化时,调整对应的定时器在链表中的位置(如收到客户端数据)。
	   这个函数只考虑被调整的定时器的超时时间延长的情况,即该定时器需要往链表尾部移动*/
	void adjustTimer(UtilTimer* timer) {
		if (!timer) {
			return;
		}
	
		UtilTimer* next = timer->next;
		/* 如果被调整的定时器在链表尾部,或该定时器新的超时时间仍然小于其下一个定时,则不用调整 */
		if (!next || (timer->expire < next->expire)) {
			return;
		}

		if (timer == head) {	//如果该定时器是链表头节点则将其取出再插入即可
			head = head->next;
			head->prev = nullptr;
			timer->next = nullptr;
			addTimer(timer, head);
		}
		else {		//否则将其取出,插入其原来所在位置之后的链表中
			timer->prev->next = timer->next;
			timer->next->prev = timer->prev;
			addTimer(timer, timer->next);
		}
	}

	/* 删除定时器 */
	void delTimer(UtilTimer* timer) {
		if (!timer) {
			return;
		}
		if ((timer == head) && (timer == tail)) {
			head = tail = nullptr;
			delete timer;
			return;
		}
		if (timer == head) {
			head = head->next;
			head->prev = nullptr;
			delete timer;
			return;
		}
		if (timer == tail) {
			tail = tail->prev;
			tail->next = nullptr;
			delete timer;
			return;
		}

		timer->prev->next = timer->next;
		timer->next->prev = timer->prev;
		delete timer;
	}

	/* 每次SIGALRM信号被触发时就调用tick函数,他处理到期的定时器 */
	void tick() {		
		time_t cur = time(nullptr);
		struct tm* tm_time = localtime(&cur);
		printf("timer tick at %d-%d-%d:%d.%d.%d\n", tm_time->tm_year + 1900,
													tm_time->tm_mon,
													tm_time->tm_mday,
													tm_time->tm_hour,
													tm_time->tm_min,
													tm_time->tm_sec);

		if (!head) {
			return;
		}
		UtilTimer* tmp = head;

		/* 从头结点开始处理每一个到期的定时器 */
		while (tmp) {
			/* 因为每个定时器都是以绝对时间作为超时值,所以我们可以通过系统当前时间得知定时器是否到期 */
			if (cur < tmp->expire) {
				break;
			}

			tmp->timerCallback(tmp->userData);	//调用定时器回调函数处理处理定时任务
			UtilTimer* prev = tmp;
			tmp = tmp->next;
			delete prev;
		}

		if (tmp) {
			head = tmp;
			head->prev = nullptr;
		}
		else {
			head = tail = nullptr;
		}
	}

private:
	/* 私有的重载函数,供addTimer和adjustTimer调用,将timer插入lstHead之后的链表中 */
	void addTimer(UtilTimer* timer, UtilTimer* lstHead) {
		UtilTimer* prev = lstHead;
		UtilTimer* tmp = lstHead->next;
		while (tmp) {
			if (timer->expire < tmp->expire) {
				prev->next = timer;
				timer->next = tmp;
				timer->prev = prev;
				tmp->prev = timer;
				return;
			}

			prev = tmp;
			tmp = tmp->next;
		}

		/* 在尾端插入定时器 */
		tail->next = timer;
		timer->next = nullptr;
		timer->prev = tail;
		tail = timer;
	}
};

#endif //!LST_TIMER_H

2_list_timer_kick_idle_conn.cc

#include "2_list_timer_kick_idle_conn.h"
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define FD_LIMIT 65535
#define MAX_EVENT_NUMBER 1024
#define TIMESLOT 10		//定时时间间隔(单位秒)

static int pipefd[2];
static SortTimerList timerList;
static int epollfd = 0;

int setNonblocking(int fd) {
	int oldOp = fcntl(fd, F_GETFL);
	int newOp = oldOp | oldOp;
	fcntl(fd, F_SETFL, newOp);
	return oldOp;
}

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);
}

//定时器处理函数,这里往管道里面写入数据即可,唤醒处于epoll_wait中的主线程
void sigHandler(int sig) {
	int saveErrno = errno;
	int msg = sig;
	send(pipefd[1], (char*)&msg, 1, 0);
	errno = saveErrno;
}

//添加信号
void addSignal(int sig) {
	struct sigaction sa;
	memset(&sa, '\0', sizeof(sa));
	sa.sa_handler = sigHandler;
	sa.sa_flags |= SA_RESTART;
	sigfillset(&sa.sa_mask);	//初始化信号集
	assert(sigaction(sig, &sa, nullptr) != -1);
}

void timerHandler() {
	/* 调用定时器处理函数 */
	timerList.tick();

	/* 需要重新注册定时器,因为一次alarm调用只会引起一次SIGALARM信号 */
	alarm(TIMESLOT);
}

/* 放到每个timer里的回调函数 */
void timerCallback(ClientData* userData) {
	printf("client %s:%d is idle!\n", inet_ntoa(userData->addr.sin_addr), ntohs(userData->addr.sin_port));
	epoll_ctl(epollfd, EPOLL_CTL_DEL, userData->sockfd, 0);
	close(userData->sockfd);
}

int main( int argc, char* argv[] )
{
    const char* ip = "127.0.0.1";
    int port = 8000;
	printf("server listen at %s:%d\n", ip, port);

    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( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );

    ret = listen( listenfd, 5 );
    assert( ret != -1 );

    epoll_event events[ MAX_EVENT_NUMBER ];
    int epollfd = epoll_create( 5 );
    assert( epollfd != -1 );
    addfd( epollfd, listenfd );

    ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);	  //socketpair()函数用于创建一对无名的、相互连接的套接字
    assert( ret != -1 );
    setNonblocking( pipefd[1] );
    addfd( epollfd, pipefd[0] );	//epoll需要专注套接字的读端

    // add all the interesting signals here
    addSignal( SIGALRM );		//由alarm或settimer设置的实时闹钟超时引起
    addSignal( SIGTERM );		//终止进程。kill命令默认就是通过SIGTERM信号终止进程
    bool stop_server = false;

    ClientData* users = new ClientData[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].addr = client_address;
                users[connfd].sockfd = connfd;
                UtilTimer* timer = new UtilTimer;
                timer->userData = &users[connfd];
                timer->timerCallback = timerCallback;
                time_t cur = time( NULL );
                timer->expire = cur + TIMESLOT;		//注意要设置为绝对超时时间
                users[connfd].timer = timer;
                timerList.addTimer( 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 ) {
                    // handle the error
                    continue;
                }
                else if( ret == 0 ) {
                    continue;
                }
                else
                {
                    for( int i = 0; i < ret; ++i )
                    {
                        switch( signals[i] )
                        {
                            case SIGALRM:	//闹钟超时
                            {
                                timeout = true;	//注意这里闹钟超时并不马上处理而是设置了标志,因为网络IO的优先级高于定时器事件
                                break;
                            }
                            case SIGTERM:	//终止进程
                            {
                                stop_server = true;
                            }
                        }
                    }
                }
            }
            else if(  events[i].events & EPOLLIN )	//网络IO
            {
                memset( users[sockfd].buf, '\0', BUFFER_SIZE );
                ret = recv( sockfd, users[sockfd].buf, BUFFER_SIZE-1, 0 );
                printf( "get %d bytes data from %s:%d at fd.%d\n", ret, inet_ntoa(users[sockfd].addr.sin_addr), ntohs(users[sockfd].addr.sin_port), sockfd );
                UtilTimer* timer = users[sockfd].timer;
                if( ret < 0 )
                {
                    if( errno != EAGAIN )
                    {
                        timerCallback( &users[sockfd] );
                        if( timer )
                        {
                            timerList.delTimer( timer );
                        }
                    }
                }
                else if( ret == 0 )
                {
                    timerCallback( &users[sockfd] );
                    if( timer )
                    {
                        timerList.delTimer( timer );
                    }
                }
                else
                {
                    send(sockfd, users[sockfd].buf, ret, 0 );
                    if( timer )
                    {
                        time_t cur = time( NULL );
                        timer->expire = cur + TIMESLOT;
                        //printf( "adjust timer once\n" );
                        timerList.adjustTimer( timer );
                    }
                }
            }
            else
            {
                // others
            }
        }

        if(timeout)		//处理定时事件
        {
            timerHandler();
            timeout = false;
        }
    }

    close( listenfd );
    close( pipefd[1] );
    close( pipefd[0] );
    delete [] users;
    return 0;
}

更多网络编程细节请查看:https://github.com/inmail/matelib

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值