qinguoyi / TinyWebServer

:fire: Linux下C++轻量级WebServer服务器
Apache License 2.0
16.52k stars 3.9k forks source link

关于定时器超时信号处理的问题 #241

Open nansheng98 opened 1 year ago

nansheng98 commented 1 year ago

问题描述

/*在eventloop主函数中,对于超时信号的处理如下:*/
if (timeout)
{
    utils.timer_handler();//
    LOG_INFO("%s", "timer tick");
    timeout = false;
}
/*信号处理函数如下*/
void Utils::timer_handler()
{
    m_timer_lst.tick();
    alarm(m_TIMESLOT);
}

疑问

在这里仅删除了定时器,客户端连接任然存在,因此代码有些地方需要判断定时器是否存在。如下:

void WebServer::deal_timer(util_timer *timer, int sockfd)
{
    timer->cb_func(&users_timer[sockfd]);
    if (timer) // 这里
    {
        utils.m_timer_lst.del_timer(timer);
    }

    LOG_INFO("close fd %d", users_timer[sockfd].sockfd);
}
/*还有在处理读写事件中均有如下代码*/
if (timer)
{
      adjust_timer(timer);
}

这里只是删掉定时器,没有断开连接。 如果某个任务超时事件到达后,又一次产生读/写事件,怎么办呢? 会触发异常事件吗?

求教

此处是否可以改为删除定时器 并且断开连接,内核事件表删除相应事件呢?

nansheng98 commented 1 year ago

请各位大佬不吝赐教!

moshang1314 commented 1 year ago

这个是Server类中的处理定时器的函数,执行这个函数,就意味着这个连接超时了,在定时器事件的处理函数中(cb_fun())已经将该连接关闭了,因此接下来就是要去删除该连接的定时器对象。

nansheng98 commented 1 year ago

感谢答疑! 总结如下

在server类初始化时候:初始化了信号处理函数,并对改进程创建了定时信号。

// 信号处理函数
utils.addsig(SIGPIPE, SIG_IGN); // 这里少个参数?
utils.addsig(SIGALRM, utils.sig_handler, false);
utils.addsig(SIGTERM, utils.sig_handler, false);

alarm(TIMESLOT); // 设置信号SIGALRM 在经过参数seconds 指定的秒数后传送给目前的进程

当定时信号触发后:执行信号处理函数,该函数保证信号的可重入,并通过管道发送信号给主程序。(这里使用了统一事件处理方式)

// 信号处理函数
void Utils::sig_handler(int sig)
{
    // 为保证函数的可重入性,保留原来的errno
    int save_errno = errno;
    int msg = sig;
    send(u_pipefd[1], (char *)&msg, 1, 0);
    errno = save_errno;
}

主程序中,对信号有信号处理函数:

if ((sockfd == m_pipefd[0]) && (events[i].events & EPOLLIN))
{
    // EPOLLIN事件则只有当对端有数据写入时才会触发,
    // 所以触发一次后需要不断读取所有数据直到读完EAGAIN为止。
    // 否则剩下的数据只有在下次对端有写入时才能一起取出来了。

    bool flag = dealwithsignal(timeout, stop_server);
    if (false == flag)
        LOG_ERROR("%s", "dealclientdata failure");
}

在dealwithsignal函数中:核心是设置如下

timeout = true;
//然后执行如下函数
if (timeout)
{
    utils.timer_handler(); // 信号处理函数
    LOG_INFO("%s", "timer tick");
    timeout = false;
}

信号处理函数中,断开超时连接,然后删除内核事件表,重新打开定时器。

void Utils::timer_handler()
{
    // 改进:这里我觉得如果将只删除定时器,改为删除定时器,
    //      并且断开连接,内核事件表删除相应事件会更规范
    m_timer_lst.tick();
    alarm(m_TIMESLOT);
}