【问题标题】:Multiple timers in CC中的多个计时器
【发布时间】:2013-09-21 18:36:51
【问题描述】:

我有一个应用程序需要执行以下操作:

  • 如果发生事件(与服务器断开连接),则会启动一个长计时器(例如 5 分钟)。然后应用程序会尝试重新连接到服务器。
  • 如果重新连接失败,则会启动一个短计时器(20 秒),该计时器应尝试再次重新连接。
  • 如果成功,长计时器应该继续继续运行。
  • 当长定时器到期时,如果没有连接,应用程序应该退出,否则它应该正常继续。

我的限制在于我不能使用线程,只能使用进程。我也不能等待 reconnect() 的结果返回。

到目前为止,我有一个这样的设计:

int main(int argc, char **argv)
{
    /* do main loop work, if disconnected, call reconnect() & continue doing work */
}

void reconnect()
{
    pid = fork();

    if (pid >= 0) {
        /*Successful fork*/
        if (pid == 0) {

            rv = attempt_reconnect;               
            if (rv == 0) {
                /*Notify sig_child Success*/
                exit(0);
            } else {
                /*Notify sig_child Fail*/
                exit(1);
            }
        }
    } 
}

void sig_child(int signum)
{
    if(fork returned success) {
        set flag to continue network stuff
    }
    else {
        alarm(20);
    }
}

void sig_alarm(int signo)
{
    /*Received alarm, trying to reconnect...*/
    reconnect();
}

任何帮助将不胜感激!

编辑

我想我有一个解决方案来自示例 here。它允许我创建具有单独 ID 的计时器,然后确定哪个已经向程序发出信号

【问题讨论】:

  • 这将是特定于操作系统的。我假设您打算在 Linux 上工作?您不能使用线程有什么特别的原因吗?
  • 是的,我在 Linux 上工作,该应用程序将在嵌入式 Linux 版本上运行,并且其中一个要求说线程不是一个选项

标签: c process timer signals alarm


【解决方案1】:

使用以下代码I found here,我已经实现了(我认为)我想要做的事情。

它允许分别使用函数“makeTtimer”和“timerHandler”创建和处理多个timer_t对象:

timer_t reconnect_timer_id;
timer_t timeout_timer_id;

int main(int argc, char **argv)
{
    /* do main loop work */
    if(disconnected()) {
        /*IF TIMEOUT SET, SKIP, IF OK, RESET*/
        if(timeout_set != 1) {
            "Schedule Alarm"
            makeTimer("Timeout Timer", &timeout_timer_id, 600,0);
            timeout_set = 1;
        } else {
              "Timeout alarm already set..";
        }
        reconnect();
    }
}

void reconnect()
{
    pid = fork();

    if (pid >= 0) {
        /*Successful fork*/
        if (pid == 0) {

            rv = attempt_reconnect;               
            if (rv == 0) {
                /*Notify sig_child Success*/
                exit(0);
            } else {
                /*Notify sig_child Fail*/
                exit(1);
            }
        }
    } 
}

void sig_child(int signum)
{
    if(fork returned success) {
        set flag to continue network stuff
    }
    else {
        "Reconnect fail, retrying in 20 seconds...";
        makeTimer("Reconnect Timer", &reconnect_timer_id, 20,0);
    }
}

static void
timerHandler( int sig, siginfo_t *si, void *uc )
{
    timer_t *tidp;
    tidp = si->si_value.sival_ptr;
    if ( *tidp == timeout_timer_id ) {

        if(state != STATE_CONNECTED) {
            "Timeout alarm received, not connected to server, exiting..."
            exit(0);
        } else {
            "Timeout alarm received, connected to server, continuing..."
            timeout_set = 0;
        }

    } else if ( *tidp == reconnect_timer_id ) {
        "Reconnect alarm received, retrying...";
        reconnect();
    }
}

static int
makeTimer( char *name, timer_t *timerID, int expireSeconds, int intervalSeconds )
{
    struct sigevent         te;
    struct itimerspec       its;
    struct sigaction        sa;
    int sigNo = SIGRTMIN;

    /* Set up signal handler. */
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = timerHandler;
    sigemptyset(&sa.sa_mask);

    if (sigaction(sigNo, &sa, NULL) == -1) {
          "Failed to setup signal handling for" *name;

        return(-1);
    }

    /* Set and enable alarm */
    te.sigev_notify = SIGEV_SIGNAL;
    te.sigev_signo = sigNo;
    te.sigev_value.sival_ptr = timerID;
    timer_create(CLOCK_REALTIME, &te, timerID);
    its.it_interval.tv_sec = intervalSeconds;
    its.it_interval.tv_nsec =0;

    its.it_value.tv_sec = expireSeconds;
    its.it_value.tv_nsec = 0;
    timer_settime(*timerID, 0, &its, NULL);
    return(0);
}

【讨论】:

    【解决方案2】:

    一个想法可能是不使用alarm( ),而是创建计时器作为子进程,kill 主进程在sleep() 之后具有两个不同的信号:

    void timer( int nsec, int signum )
    {
        pid_t pid = getpid();
        if( fork() > 0 ) {
            sleep( nsec );
            kill( pid, signum );
            exit( 0 );
        }
    }
    

    通过使用两个不同的信号(例如 SIGUSR1 用于短定时器,SIGUSR2 用于长定时器),您可以拥有这样的信号处理程序:

    void sig_timer( int signum )
    {
        if( signum == SIGUSR1 ) {
            // reconnect
        } else if( signumm == SIGUSR2 ) {
            if( !reconnected )
                exit( 11 );
        } 
    }
    

    【讨论】:

      【解决方案3】:

      您只能通过在主循环中轮询警报和标志来执行此操作。在主循环中选择断开事件后,您可以设置 20 秒的警报并使用标志在处理程序或主循环中处理重新连接。继续设置 20 秒的警报,直到您重新连接或 5 五分钟到期。如果 20 秒警报发生 15 次,您可以在警报处理程序中再次检查。 现在,如果 5 分钟到期,那么您可以重置警报(0)并通知主循环以关闭。 像这样。

      int disconnect_event = 0;
      int reconnect_try = 1;
      int five_minutes  = 0;
      int shutdown = 0;
      void alarm_handler(int signum)
      {
        if(five_minutes == 15)
        {
          alarm(0);
          shutdown = 1;
        }
        else
        {
          reconnect_try = 1;
          alarm(20);
          five_minutes++
        }
      
      } 
      
      int main(int argc, char*argv[])
      {
        signal(SIGALRM, alarm_handler);
        while(1)
        {
          if(disconnect_event && reconnect_try)
          {
            if(reconnect() == 0)
            {
              alarm(20);
              reconnect_try = 0;
            }
            else
            {
               disconnect_event = 0;
               reconnect_try = 1;
            }
          }
          if(shutdown)
          {
            disconnect_event = 0;
            reconnect_try = 0;
            alarm(0)
          }
      
        }
      
      }
      

      【讨论】:

      • 一个问题是我等不及reconnect()的结果,主循环正忙于做其他工作..
      • 抱歉,您将不得不创建孩子,因为您不能等待重新连接,即使您从警报处理程序调用重新连接,它也必须是非阻塞的。由于您不允许使用线程,所以我认为这可以通过使用 pid 将子级和父级分开并仅在子级中执行重新连接来在主循环本身中完成。然后孩子将设置一些标志来确认连接,可以将其读取为父母以停止进一步重新连接。所以孩子只需要执行重新连接。
      【解决方案4】:

      从问题中不清楚应用程序是否需要继续运行 (1) 在 connect() 正在进行时,以及 (2) 在它处于“短暂等待”时。

      如果确实不需要需要在这两个周期中的任何一个中运行,那么当你需要使用长定时器时,将长定时器到期的绝对时间放入某个变量中,像这样:

      time_t run_until = time(NULL) + 60;
      

      在每次connect()失败后检查当前时间是否大于run_until:

      if (connect_rc) {if (time(NULL) > run_until) shutdown(); else reconnect();}
      

      您将获得上面描述的逻辑,无需任何进程或线程。

      如果您需要您的进程在 connect() 正在进行时继续运行,和/或当您在重新连接之前处于短暂的暂停状态时,您可以使用非阻塞套接字。如何做到这一点是一个冗长的讲座;本质上,您将主循环安排在 select() 或 poll() 周围。这些函数让您等待一组事件中的一个发生,这些事件是:后台 connect() 的完成,或为 recv()/read() 准备好到达的数据,或者更重要的是,计时器到期。这种方法也不会涉及线程或进程。

      【讨论】:

      • 很抱歉没有说明清楚,main 中正在完成的工作即使已断开连接也应该继续。它将调用重新连接并照常继续。
      • @Tomcelic,如果你能负担得起同步 connect(),即阻塞进程直到 connect() 成功或失败,你仍然可以使用我的第一种方法。通过 alarm() 和 SIGALRM 的信号处理程序实现短计时器。将长计时器实现为连接后检查的变量。
      猜你喜欢
      • 1970-01-01
      • 2020-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-14
      • 1970-01-01
      • 2020-07-24
      • 1970-01-01
      相关资源
      最近更新 更多