• 本文介绍3个与网络编程有关的3个信号
一、SIGHUP信号处理
  • 信号产生的情景:
    • 1.如果终端接口检测到一个连接断开,则将此信号送给与该终端相关的控制进程(会话首进程)
      • 此信号被送给session结构中s_leader字段所指向的进程。仅当终端的CLOCAL标志没有设置时,在上述条件下才产生此信号(如果所连接的终端是本地的,则设置该终端的CLOCAL标志。它告诉终端驱动程序忽略所有调制解调器的状态行)
      • 注意:接到此信号的会话首进程可能在后台。这区别于由终端正常产生的几个信号(中断、退出和挂起),这些信号总是传递给前台进程组
    • 2.如果会话首进程终止,也产生此信号,在此情况下,此信号送给前台进程组的每一个进程
  • 程序对此信号的默认动作为终止程序
  • 通常用此信号通知守护进程再次读取它们的配置文件。选用SIGHUP的理由是,守护进程不会有控制终端,通常绝不会接收到这种信号。一个典型的例子就是xinetd超级服务程序(见下面的演示案例)

演示案例(xinetd超级服务程序)

  • xinetd程序在接收到SIGHUP信号之后将调用hard_reconfig函数,它循环读取/etc/xinetd.g/目录下的每个子配置文件,并检测其变化
  • 如果某个正在运行的子服务的配置文件被修改以停止服务,则xinetd主程序将给该子服务进程发送SIGTERM信号以结束它。如果某个子服务的配置文件被修改以开启服务,则xinetd将创建新的socket并将其绑定到该服务对应的端口上
  • 现在来分析xinetd处理SIGHUP信号的流程。下面是检测机器的环境:
    • 从ps的输出可以看出,xinetd创建子进程7442,它运行echo-stream内部服务
    • 从lsof输出来看,xinetd打开了一个管道。该管道的读端文件描述符的值是3,写端文件描述符的值是4(这个就是我们前面介绍的统一事件源)

Linux(程序设计):59---SIGHUP、SIGPIPE、SIGURG信号处理(附SIGURG信号处理普通数据与外带数据案例)_#include

  • 现在我们修改/etc/xinetd.d/目录下的部分配置文件,并给xinetd发送一个SIGHUP信号,操作如下:

Linux(程序设计):59---SIGHUP、SIGPIPE、SIGURG信号处理(附SIGURG信号处理普通数据与外带数据案例)_信号处理_02

  • 我们使用streace命令跟踪程序执行时调用的系统调用和接收到的信号。此处我们根据进程7438(即xinetd服务器程序),输出内容如下,每个部分用空行隔开,共4个部分:
    • 第一部分:描述程序接收到SIGHUP信号时,信号处理函数使用管道通知主程序该信号的到来。信号处理函数往文件描述符4(管道的写端)写入信号值1(SIGHUP信号),而主程序使用poll检测到文件描述符3(管道的读端)上有可读事件,就将管道上的数据读入
    • 第二部分:描述符了xinetd重新读取一个子配置文件的过程
    • 第三部分:描述了xinetd给子进程echo-stream(PID为7442)发送SIGTERM信号来终止该子进程,调用waitpid来等待子进程结束
    • 第四部分:描述了xinetd启动telnet服务的过程:创建一个流服务socket并将其绑定到端口23上,然后监听该端口

Linux(程序设计):59---SIGHUP、SIGPIPE、SIGURG信号处理(附SIGURG信号处理普通数据与外带数据案例)_#include_03

二、SIGPIPE信号处理
  • 信号产生的情景:
    • 如果在管道的读进程已终止时写管道,则产生此信号
    • 当类型为SOCK_STREAM的套接字已不再连接时,进程写该套接字也产生此信号
  • 程序对此信号的默认动作为终止程序
  • 引起SIGPIPE信号的写操作将设置errno为EPIPE
  • 信号处理:
    • 我们可以使用send函数的MSG_NOSIGNAL标志来禁止写操作触发SIGPIPE信号
    • 我们也可以根据errno值来判断管道或者socket连接的读端是否已经关闭
    • 我们也可以根据I/O复用系统来检测管道和socket连接的读端是否已经关闭。以poll为例,当管道的读端关闭时,写端文件描述符上的POLLHUP事件将被触发;当socket连接对对方关闭时,socket上的POLLRDHUP事件将被触发
三、SIGURG信号处理
  • 信号产生的情景:此信号通知进程已经发生了一个紧急情况。在网络连接上接到带外的数据时,可选择的产生此信号
  • 程序对此信号的默认动作为忽略此信号
  • 处理带外数据:在前面文章中(javascript:void(0)),我们介绍了select在接收到带外数据时将返回,并向应用程序报告socket上的异常事件。此处我们可以使用SIGURG信号处理带外数据

演示案例(处理带外数据)

//sigurg.cpp
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <libgen.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

#define LISTEN_NUM  5
#define BUFFER_SIZE 1024

static int client_fd; //客户端的fd

void add_signal(int signal_no,void (*sig_handler)(int signal_no));

void sig_handler(int signal_no);

int main(int argc,char* argv[])
{    
    if(argc!=3){
        printf("usage:./%s [server ip] [server port]\n",basename(argv[1]));
        exit(EXIT_FAILURE);
    }

    const char* ip;
    int server_fd,port;

    ip=argv[1];
    port=atoi(argv[2]);

    //创建套接字
    if((server_fd=socket(AF_INET,SOCK_STREAM,0))==-1){
        perror("socket");
        exit(EXIT_FAILURE);
    }
    
    //初始化服务器地址
    struct sockaddr_in server_address;
    bzero(&server_address,sizeof(server_address));
    server_address.sin_family=AF_INET;
    server_address.sin_port=htons(port);
    if(inet_pton(AF_INET,ip,&server_address.sin_addr)==-1){
        perror("inet_pton");
        exit(EXIT_FAILURE);
    }

    //绑定服务端地址
    if(bind(server_fd,(struct sockaddr*)&server_address,sizeof(server_address))==-1){
        perror("bind");
        exit(EXIT_FAILURE);
    }
    
    //开启监听
    if(listen(server_fd,LISTEN_NUM)==-1){
        perror("listen");
        exit(EXIT_FAILURE);
    }

    //接受客户端连接
    struct sockaddr_in client_address;
    socklen_t client_address_len=sizeof(client_address);
    bzero(&client_address,sizeof(client_address));
 
    if((client_fd=accept(server_fd,(struct sockaddr*)&client_address,&client_address_len))==-1){
        perror("accept");
        exit(EXIT_FAILURE);
    }
    else{
        //添加SIGURG信号处理函数
        add_signal(SIGURG,sig_handler);

        //使用SIGURG之前,必须设置socket的宿主进程或进程组
        fcntl(client_fd,F_SETOWN,getpid());

        int recv_ret_value;
        char recv_buffer[BUFFER_SIZE];
        //接收普通数据
        while(1)
        {
            bzero(recv_buffer,sizeof(recv_buffer));
            //接收数据
            recv_ret_value=recv(client_fd,recv_buffer,sizeof(recv_buffer-1),0);
            if(recv_ret_value<0){
                perror("recv");
                close(client_fd);
                close(server_fd);
                exit(EXIT_FAILURE);
            }
            else if(recv_ret_value==0){
                break;
            }
            else{
                printf("recv normal data:%s ,%d bytes\n",recv_buffer,recv_ret_value);
            }
        }
        close(client_fd);
    }

    
    close(server_fd);
    exit(EXIT_SUCCESS);
}

void add_signal(int signal_no,void (*sig_handler)(int signal_no))
{
    struct sigaction act;
    act.sa_handler=sig_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags=0;
    
    //对信号进行处理
    sigaction(signal_no,&act,NULL);
}

void sig_handler(int signal_no)
{
    //在这个信号处理函数中,我们接收带外数据
    int save_errno=errno;

    int recv_ret_value;
    char recv_buffer[BUFFER_SIZE];
    bzero(recv_buffer,sizeof(recv_buffer));
    //接收带外数据
    recv_ret_value=recv(client_fd,recv_buffer,sizeof(recv_buffer-1),0);
    printf("recv oob data:%s ,%d bytes\n",recv_buffer,recv_ret_value);

    errno=save_errno;
}
  • 测试程序:接下来我们用前面文章介绍过的一个可以发送带外数据的客户端程序(javascript:void(0))向这个服务端程序发送带外数据,然后测试是否是否可以收到带外数据,检测成功

Linux(程序设计):59---SIGHUP、SIGPIPE、SIGURG信号处理(附SIGURG信号处理普通数据与外带数据案例)_带外数据_04