【TCP/IP】多进程服务器的实现(进阶) - 信号处理及signal、sigaction函数

目录

信号

signal函数

sigaction函数

用信号来处理僵尸进程


        在之前我们学习了如何处理“僵尸进程”,不过可能也会有疑问:调用wait和waitpid函数时我们关注的始终是在子进程上,那么在父进程上如何实现对子进程的管控呢?为此,我们引入一个概念——信号处理。

信号

        信号这个概念大家应该不陌生,诸如汽车的鸣笛、闹钟的响铃等,这些都是信号,用以告诉人们某件事情的发生。而在计算机中,同样有这个概念。在编程上常用信号来对接收到的某些数据或者指令做出与之对应的响应处理。

signal函数

        让我们先来看看库中的signal函数,其结构如下:

#include 

void (*signal (int signo , void (*func)(int))) (int);


// 为了在产生信号时调用,返回之前注册的函数指针。

// 函数名:signal
// 参数 int signo, void (* func)(int)
// 返回类型:参数为int型,返回void型函数指针。

        signo参数用来标记触发的条件,func则为将要调用的函数的指针

        与参数所相关的宏有:

  • SIGALRM: 按照已通过调用alarm函数注册的时间为信号点。
  • SIGINT: 输入CTRL+C暂停时为信号点
  • SIGCHILD: 子进程终止时为信号点。

补充:

        alarm函数的用法及意义如下:

#include 

unsigned int alarm (unsigned int seconds);

// 返回 以秒为单位 的,距 SIGALRM 信号发生时所剩余的时间

        其中 seconds 参数用以传入欲设定的alarm的周期(以s为单位)。若传递0至变量中,则意味着将会取消对SIGALRM信号的预约。同时,若未指定该产生信号所对应的处理函数时,则该进程将被终止。

        接下来,让我们来验证下 SIGALRMSIGINT 两种触发条件下的signal函数吧。

singal.cpp

#include 
#include 
#include 

void alarmEvent(int sig) //包括下面的keyEvent,这类函数被称为信号处理器(Handler)
{
    if (sig == SIGALRM)
    {
        printf("Time out!\n");
    }

    alarm(2);
}

void keyEvent(int sig)
{
    if (sig == SIGINT)
    {
        printf("\nCTRL+C pressed\n");
    }
}

int main(int argc, char *argv[])
{
    //注册:设置SIGGALRM为触发条件,调用alarmEvent函数
    signal(SIGALRM, alarmEvent); 
    //注册:设置SIGINT为触发条件,调用keyEvent函数
    signal(SIGINT, keyEvent);

    //设置时钟周期为2s
    alarm(2); 

    for (size_t i = 0; i < 5; i++)
    {
        printf("Wait for an event to occur...\n");
        sleep(50);  //实际操作上无法休眠50s
    }

    return 0;
}

        运行结果:

【TCP/IP】多进程服务器的实现(进阶) - 信号处理及signal、sigaction函数_第1张图片

        想法得到验证。

        Q:为什么程序在实际运行上,没有感受到在等待事件发生后的50s延迟呢?

        A:信号在响应时,尽管存在有阻塞态的进程,但操作系统为了调用信号处理器,会强制唤醒进行阻塞态(sleep中的)进程,并且该进程一旦被唤醒,将不会在进入睡眠状态,因此在操作上,我们并不会感受到会有长达50s的延迟。

sigaction函数

        sigaction是对signal函数的升级,能够更好地支持不同的操作系统(signal函数在不同的操作系统中用法和效果可能会不同)。

        让我们来看下这个函数长什么样:

#include 

int sigaction(int signo , const struct sigaction * act , struct sigaction *
oldact) ;

// 成功时退回0,失败时退回-1。

/* 参数含义 */

// signo:与signal函数相同,传递信号信息
// act:对应第一个参数的信号处理函数
// oldact:通过此参数获取之前注册的信号处理函数指针,不需要时则传递0。

        对于sigaction,它的结构体定义如下:

 //结构体做了简化,实际上其中一些变量用宏和typedef做了进一步封装

 struct sigaction
    {
        void (*sa_handler)(int);
        sigset_t sa_mask;
        int sa_flags;
        void (*sa_restorer)(void);
    };

        参数意义参考以下: 

  • sa_handler:用以保存信号处理函数的指针值
  • sa_mask:需要滤除的附加信号集,省略
  • sa_flags:特殊标记符,省略
  • sa_restorer:欲重置的(信号)处理器,省略

        OK,让我们编写一个实例来验证这个函数的功能

sigaction.cpp

#include 
#include 
#include 

void alarmEvent(int sig)
{
    if (sig == SIGALRM)
    {
        printf("Time out!\n");
    }
    alarm(2);
}

int main(int argc, char *argv[])
{
    //声明sigaction结构体变量act
    struct sigaction act;
    //将信号处理函数存入结构体中
    act.sa_handler = alarmEvent;
    //将sa_mask成员的所有位初始化为0
    sigemptyset(&act.sa_mask);
    //将sa_flags成员同样初始化为0
    act.sa_flags = 0;
    //注册:触发条件为SIGALARM,调用act信号处理器中的信息
    sigaction(SIGALRM, &act, 0);

    alarm(2);

    for (size_t i = 0; i < 5; i++)
    {
        printf("Wait for an event to occur...\n");
        sleep(50);
    }

    return 0;
}

        运行结果:

【TCP/IP】多进程服务器的实现(进阶) - 信号处理及signal、sigaction函数_第2张图片

        结果验证了之前的想法。

用信号来处理僵尸进程

        当我们掌握signal、sigaction两个函数后,便可更好地去管控父、子进程了。在之前我们采用的是wait、waitpid函数来销毁僵尸进程。接下来,请大家尝试一下用信号来消灭“僵尸进程”吧!

        欢迎大家在评论区贴出你们的代码~

你可能感兴趣的:(#,网络编程,服务器,网络协议,网络,tcp/ip,udp)