服务端处理非活动的连接

超时事件也是网络程序需要处理的事件,比如下面讲到的服务端定期检验一个客户端连接的活动状态。通常服务器程序需要管理众多定时事件,所以需要程序有效组织这些事件,使之能在预期的时间点触发且不影响程序逻辑。
 在这里我们将所有定时器及对应超时时间存放在升序双向链表中,在超时处理函数中依次处理所有到期的定时器事件,以实现对定时事件的统一管理。升序定时器链表定义如下:

#ifndef LST_TIMER
#define LST_TIMER

#include<time.h>
#define BUFFER_SZ 64
struct cli_data;
//定时器节点和用户数据相互包含,这样可以通过整个程序使用的定时器找到用户数据,也可以通过用户数据找到整个程序使用的定时器链表
//定时器节点
class util_timer
{
public:
    time_t expire;  /*任务的超时时间*/
    void (*cb_func)(cli_data*); /*任务回调函数*/
    util_timer* pre;
    util_timer* next;
    cli_data* user_data;

    util_timer():pre(NULL),next(NULL) {}
};
struct cli_data
{
    sockaddr_in addr;
    int sockfd;
    char buf[BUFFER_SZ];
    util_timer* timer;
};

//链表操作函数类
class sort_timer_lst
{
public:
    void add_timer(util_timer* timer)
    {
        if(!timer) return;
        if(!head)
        {
            head = tail = timer;
            return;
        }
        //插入位置是其他位置
        if(timer->expire < head->expire)
        {
            timer->next = head;
            head->pre = timer;
            head = timer;
            return;
        }
        //插入位置是其他位置
        add_timer(timer,head);
    }
    //定时器时间延长时间后往链表尾移动
    void adjust_timer(util_timer* timer)
    {
        if(!timer) return;

        util_timer* tmp = timer->next;
        if(!tmp || (timer->expire < tmp->expire))
            return;
        if(timer == head) //若是头节点则往第2个节点后的位置移动
        {
            head = head->next;
            head->pre = NULL;
            timer->next = NULL;
            add_timer(timer,head);
        }
        else //非头节点,从链表中删除该节点并在头节点后合适的位置插入
        {
            timer->pre->next = timer->next;
            timer->next->pre = timer->pre;
            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->pre = NULL;
            delete timer;
            return;
        }
        if(timer == tail)
        {
            tail = tail->pre;
            tail->next = NULL;
            delete timer;
            return;
        }
        timer->pre->next = timer->next;
        timer->next->prr = timer->pre;
        delete timer;
    }
    //SIGALARM每次被触发执行一次tick()函数(信号处理函数->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->pre = NULL;

            delete tmp;
            tmp =  head;
        }
    }
private:
    util_timer* head;   //指向头节点
    util_timer* tail;   //指向尾节点

    //在非头节点后的合适位置插入timer
    void add_timer(util_timer* timer, util_timer* lst_head) //升序插入
    {
        util_timer* pre = lst_head;
        util_timer* tmp = pre->next;

        while (tmp)
        {
            if (timer->expire < tmp->expire)
            {
                pre->next = timer;
                timer->next = tmp;
                tmp->pre = timer;
                timer->pre = pre;
                break;
            }
            pre = tmp;
            tmp = tmp->next;
        }

        //最后一个位置
        if (!tmp)
        {
            pre->next = timer;
            timer->pre = pre;
            timer->next = NULL;
            tail = timer;   
        }
    }
};

sort_timer_lst::tick()函数是超时信号SIGALARM的超时函数中被调用的,sort_timer_lst::tick()又会调用对应已经超时的定时器的回调函数。在这里我们要实现关闭非活动的连接,即在回调函数中关闭该连接。
 程序的主体脉络如下:
 (1)为每个连接的客户端都分配一个定时器、设置超时时间和超时回调函数(回调函数关闭连接),并将定时器加入升序双向链表中。
 (2)发生超时后执行超时信号处理函数。为了统一事件源,在处理函数中(用管道技术)通知程序主循环。
 (3)在主循环中判断是超时事件对应链表节点中的tick()函数,在tick()中调用每个已经超时的对应客户端的超时回调函数(此时升序链表发挥重大作用),即关闭连接。
 (4)主循环中若判断是接收到数据,则接收完数据后重置该客户端连接的定时器超时时间。

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>
#include "lst_timer.h"

#define ERRP(con, ret, ...) do                              \
{                                                           \
    if (con)                                                \
    {                                                       \
        perror(__VA_ARGS__);                                \
        ret;                                                \
    }                                                       \
}while(0)

#define FD_LIMIT 65535
#define TIMESLOT 5
#define MAX_EVENT_NUMBER 1024

static const char* ip = "192.168.239.136";
static int port = 9660;

static int pipe_fd[2];
static int epfd;
static sort_timer_lst timer_lst;

//信号处理函数,只是将该信号值写入管道写端
void sig_handler(int sig)
{
    int errno_bak = errno;
    int msg = sig;
    send(pipe_fd[1], (char*)&msg, 1, 0);
    errno = errno_bak;
}

void add_signal(int sig)
{
    struct sigaction sa;
    bzero(&sa, sizeof(sa));
    sa.sa_handler = sig_handler;
    sa.sa_flags |= SA_RESTART;
    sigfillset(&sa.sa_mask);
    sigaction(sig, &sa, NULL);
}

int set_fd_nonblock(int fd)
{
    int old_opt = fcntl(fd, F_GETFL);
    int new_opt = old_opt | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_opt);

    return old_opt;
}

void add_fd_to_epoll(int epfd, int fd)
{
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;   //可读事件 | 边沿触发
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);    
    set_fd_nonblock(fd);
}

void cb_func(cli_data* user_data)
{
    //关闭非活动连接前先将该连接从epoll监听表中移除
    epoll_ctl(epfd, EPOLL_CTL_DEL, user_data->sockfd, 0);
    close(user_data->sockfd);
    printf("close fd %d\n", user_data->sockfd);
}

void timer_handler()
{
    timer_lst.tick();
    alarm(TIMESLOT);
}

int main(void)
{
    int ret;
    bool is_stop = false;
    bool timeout = false;
    cli_data* users = new cli_data[FD_LIMIT];
    epoll_event events[MAX_EVENT_NUMBER] = {0};

    //创建监听套接字
    int listen_fd = socket(PF_INET, SOCK_STREAM, 0);
    ERRP(listen_fd < 0, return -1, "socket");

    //命名套接字
    struct sockaddr_in addr;
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &addr.sin_addr);
    addr.sin_port = htons(port);    
    ret = bind(listen_fd, (struct sockaddr*)&addr, sizeof(struct sockaddr));
    ERRP(ret < 0, goto ERR1, "bind");

    //创建监听队列
    ret = listen(listen_fd, 5);
    ERRP(ret < 0, goto ERR1, "listen");

    //创建epoll内核事件列表
    epfd = epoll_create(5);
    ERRP(epfd < 0, goto ERR1, "epoll_create");

    //将描述符的可读事件加入epoll内核时间表
    add_fd_to_epoll(epfd, listen_fd);

    //创建管道,在信号处理函数中通过该管道和程序主循环通信,以快速完毕信号处理事件
    socketpair(PF_UNIX, SOCK_STREAM, 0, pipe_fd);
    ERRP(epfd < 0, goto ERR2, "socketpair");
    set_fd_nonblock(pipe_fd[1]);        //设置管道写端为非阻塞
    add_fd_to_epoll(epfd, pipe_fd[0]);  //将管道读端加入epoll内核事件表

    //注册相关信号的处理函数
    add_signal(SIGALRM);
    add_signal(SIGTERM);

    alarm(TIMESLOT);    
    while (!is_stop)
    {
        int num = epoll_wait(epfd, events, MAX_EVENT_NUMBER, -1);

        //系统产生SIGALRM超时信号,会中断epoll的监听,即errno=EINTR,num=-1,此时不break
        //随后sig_handler(int sig)得到执行,该函数向管道写端pipe_fd[1]写入信号值,epoll再次返回,此时num=1
        if ((num < 0) && (errno != EINTR))
        {
            printf("epoll failer\n");
            break;
        }

        for (int i = 0; i < num; i++)
        {
            int fd = events[i].data.fd;

            //有新的客户端连接
            if (fd == listen_fd)
            {
                struct sockaddr_in cli_addr;
                socklen_t len = sizeof(struct sockaddr_in);
                int connfd = accept(listen_fd, (struct sockaddr* )&cli_addr, &len);

                //将客户端连接描述符加入epoll监听表中
                add_fd_to_epoll(epfd, connfd);
                users[connfd].addr = cli_addr;
                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; //一开始定义超时事件为15s,后面将是5s
                users[connfd].timer = timer;
                timer_lst.add_timer(timer);
            }
            //信号处理函数往管道写端写入信号值
            else if ((fd == pipe_fd[0]) && (events[i].events & EPOLLIN))
            {
                char signals[1024] = {0};
                ret = recv(pipe_fd[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:
                                is_stop = true;

                        }
                    }
                }
            }
            //客户端发来数据
            else if (events[i].events & EPOLLIN)
            {
                bzero(users[fd].buf, BUFFER_SZ);
                ret = recv(fd, users[fd].buf, BUFFER_SZ - 1, 0);
                printf("get %d bytes of client data %s from %d\n", ret, users[fd].buf, fd);

                util_timer* timer = users[fd].timer;
                if (ret < 0)
                {
                    if (errno != EAGAIN)
                    {
                        cb_func(&users[fd]);
                        if (timer)
                        {
                            timer_lst.del_timer(timer);
                        }
                    }
                }
                else if (ret == 0)
                {
                    cb_func(&users[fd]);
                    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);           
                    }
                }
            }
            else {
                printf("noting\n");
            }
        }

        //通过timer_handler()执行定时器的tick(),进而调用回调函数,回调函数中关闭连接
        if (timeout)
        {
            timer_handler();
            timeout = false;
        }
    }

    close(pipe_fd[1]);
    close(pipe_fd[0]);
    delete[] users;

ERR2:
    close(epfd);
ERR1:
    close(listen_fd);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值