zoukankan      html  css  js  c++  java
  • Linux高性能server编程——信号及应用

    

    信号

    信号是由用户、系统或者进程发送给目标进程的信息。以通知目标进程某个状态的改变或系统异常。

    Linux信号可由例如以下条件产生:

    1. 对于前台进程。用户能够通过输入特殊的终端字符来给它发送信号。比方输入Ctrl+C一般会给进程发送一个终端信号。

    2. 2.系统异常

    3. 系统状态变化

    4. 执行kill命令或调用kill函数

    Linux信号概述

    发送信号

    Linux下,一个进程给其它进程发送信号的APIkill函数。其定义例如以下:

    #include <sys/types.h>

    #include <signal.h>

    int kill(pid_t pid, int sig);

    该函数把信号sig发送给目标进程。目标进程可由pid參数指定。其可能取值及含义例如以下:

    pid參数

    含义

    pid > 0

    信号发送给PIDpid的进程

    pid = 0

    信号发送给本进程组内的其它进程

    pid = -1

    信号发送给出了init进程外的全部进程。但须要对目标进程有发送权限

    pid < -1

    发送给组IP-pid的进程组中的全部成员

    该函数成功时返回0。失败是返回-1并设置errno

    信号的处理方式

    目标进程在收到信号时,须要定义一个接收函数来处理之。

    信号处理函数的原型例如以下:

    #include <signal.h

    typedef void (* _sighandler_t)( int );

    信号处理函数仅仅带一个整形參数,该參数用来指示信号类型。

    信号处理函数应该是可重入的。否则非常easy引发一些竞态条件。所以在信号处理函数中严禁调用一些不安全的函数。

    除了用户自己定义信号处理函数外,bits/signum.h文件里还定义了两种其它处理方式——SIG_IGNSIG_DFL:

    #include <bits/signum.h>

    #define SIG_DFL ((_sighandler_t)0)

    #define SIG_IGN((_sighandler_t) 1)

    SIG_IGN表示忽略目标信号,SIG_DFL表示使用信号的默认处理方式。

    信号的默认处理方式有例如以下几种:结束进程(Term)、忽略信号(Ign)、结束进程并生成核心转储文件(Core)、暂停进程(Stop,以及继续进程(Cont)。

    Linux的可用信号都定义在bits/signum.h头文件里,当中包含标准信号和POSIX实时信号。

    终端系统调用

    假设程序在运行处于堵塞的系统调用时接收到信号,而且我们为该信号设置了信号处理函数。则默认情况下系统调用将中断。而且errno被设置为EINTR

    我们能够使用sigaction函数为信号设置SA_RESTART标志以自己主动重新启动被信号中断的系统调用。

             对于默认行为是暂停进程的信号(比方SIGSTOPSIGTTIN),假设我们没有为它们设置信号处理函数。则它们也能够中断某些系统调用。

    信号函数

    signal系统调用

    能够是使用signal系统调用为一个信号设置处理函数。

    #include <signal.h>

    typedef void (*sighandler_t)(int);

    sighandler_t signal(int signum,sighandler_t handler);

    sig參数指出要捕捉的信号类型。_handler參数是_sighandler_t类型的函数指针,用于指定信号sig的处理函数。

    signal函数成功时返回一个函数指针,该函数指针的类型也是_sighandler_t

    这个返回值是前一次调用signal函数时传入的函数指针,或者是信号sig相应的默认处理函数指针SIG_DEF(假设是第一次调用signal的话)。

    signal系统调用出错时返回SIG_ERR,并设置errno

    sigaction系统调用

    设置信号处理函数的更健壮的接口是例如以下的系统调用:

    #include <signal.h>

    int sigaction(int signum, const structsigaction *act, struct sigaction *oldact);

    signum參数之处要捕捉的信号类型,act參数指定新的信号处理方式。oldact參数则输出信号先前的处理方式(假设不为NULL的话)。actoact都是sigaction结构体类型的指针,sigaction结构体描写叙述了信号处理的细节,其定义例如以下:

    struct sigaction {

                   void     (*sa_handler)(int);

                   void     (*sa_sigaction)(int, siginfo_t *, void *);

                   sigset_t   sa_mask;

                   int        sa_flags;

                   void     (*sa_restorer)(void);

              };

    该结构体中的sa_handler成员指定信号处理函数。sa_mask成员设置进程的信号掩码(确切地说是在进程原有信号掩码的基础上添加信号掩码),以指定哪些信号不能发送给本进程。sa_mask是信号机sigset_t类型,该类型指定一组信号。sa_flag成员用于设置程序收到信号时的行为。sa_restorer成员已经过时。不在使用。

    信号集

    信号集函数

    Linux使用数据结构sigset_t来表示一组信号。其定义例如以下:

    #include <bits/sigset.h>

    #define _SIGSET_NWORDS(1024 / (8 * sizeof(unsigned long int)))

    typedef struct

    {

             unsignedlong int __val[_SIGSET_NWORDS];

    }__sigset_t;

    由定义可见,sigset_t实际上是一个长整型数组。数组的每一个元素的每一个位表示一个信号。

    这样的定义方式和文件描写叙述符集fd_set类似。Linux提供了例如以下一组函数来设置、改动、删除和查询信号集:

    #include <signal.h>

    int sigemptyset(sigset_t *set);                                    //清空信号集

    int sigfillset(sigset_t *set);                                           //在信号集中设置全部信号

    int sigaddset(sigset_t *set, int signum);                            //将信号signum加入到set信号集中

    int sigdelset(sigset_t *set, int signum);                    //删除信号

    int sigismember(const sigset_t *set, intsignum); //測试信号是否在信号集中

    进程信号掩码

    子进程将继承父进程的信号掩码。

    我们能够用sigaction结构体中的sa_mask成员来设置进程的 信号掩码。此外,一下函数也能够用于设置或查看进程的信号掩码:

    #include<signal.h>

    intsigprocmask(int how, const sigset_t *set, sigset_t *oldset);

    _set參数指定新的信号掩码,_oset參数则输出原来的信号掩码(假设不为NULL的话)。

    假设_set參数不为NULL,则_how參数指定设置进程信号掩码的方式,其可选值例如以下表所看到的:

    how參数

    含义

    SIG_BLOCK

    新的进程掩码是当前值和set指定信号集的并集

    SIG_UNBLOCK

    当前值和~set信号集的交际,因此set指定的信号将不被屏蔽

    SIG_SETMASK

    直接设置为set

    假设setNULL,则进程信号掩码不变。此时我们仍然能够利用oldset參数获得进程当前的信号掩码。

    sigprocmask成功返回0,失败返回-1并设置errno

    被挂起的信号

    设置进程掩码后。被屏蔽的信号将不能被进程接收。假设给进程发送一个被屏蔽的信号。则操作系统将该信号设置为进程的一个被挂起的信号。

    假设我们取消对被挂起信号的屏蔽,则它能马上被进程接收到。

    例如以下函数能够获得当前被挂起的信号集:

    #include<signal.h>

    intsigpending(sigset_t *set);

    set參数用于保存被挂起的信号集。

    显然,进程即使多次接收到同一个被挂起的信号,sigpending函数也仅仅能反映一次。而且,当我们使用sigprocmask使被挂起的信号不被屏蔽时,该信号的处理函数也仅仅能被触发一次。

    统一事件源

    信号是一种异步事件:信号处理函数和程序的主循环是两条不同的运行路线。非常显然。信号处理函数须要尽可能地运行完成,以确保该信号不被屏蔽(为了一些竞态条件,信号在处理期间。系统不会再触发它)太久。

    一种典型的解决方式是:把信号的主要逻辑放到程序的主循环中,当信号处理函数被触发时,它仅仅是简单的通知主循环程序接收信号,并把信号值传递给主循环,主循环再依据被接收到的信号值运行目标信号的逻辑代码。信号处理函数通经常使用使用管道来将信号“传递“给主循环:信号处理函数往管道的写端写入信号值。主循环则从管道的独断读出该信号值,这里主循环能够用I/O复用系统监听管道的读端文件描写叙述符上的刻度事件。

    如此一来。信号事件就能和其它I/O事件一样被处理,即统一事件源。

    程序清单1是统一事件源的一个简单实现。

    网络编程相关信号

    SIGHUP

    当挂起进程的控制终端时,SIGHUP信号将被触发。对于没有控制终端的网络后台程序而言,他们通常利用SIGHUP信号来强制server重读配置文件。

    SIGPIPE

    默认情况下。往一个读端关闭的管道或socket连接中写数据将引发SIGPIPE信号。我们须要在代码中捕获并处理该信号,至少不能忽略它。由于程序接收到SIGPIPE信号的默认行为是结束进程,这是我们不希望的。

    SIGURG

    SIGURG

             Linux下内核通知应用程序带外数据到达的主要有两种方法:一种是I/O复用技术,select等系统调用接收到带外数据时将返回,并向应用程序报告socket上的一场事件;还有一种方法就有事使用SIGURG信号。程序清单2使用了SIGURG检測带外数据是否到达。

             client发送带外数据部分參见http://write.blog.csdn.net/postedit/36003039

    则server上先输出例如以下:

     got 1 bytes ofoob data 'c'

    got 5 bytes ofnormal data '123ab'

    got 3 bytes ofnormal data '123'

    程序清单1:
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <assert.h>
    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <sys/epoll.h>
    #include <pthread.h>
    
    #define MAX_EVENT_NUMBER 1024
    static int pipefd[2];
    
    int setnonblocking( int fd )
    {
        int old_option = fcntl( fd, F_GETFL );
        int new_option = old_option | O_NONBLOCK;
        fcntl( fd, F_SETFL, new_option );
        return old_option;
    }
    
    void addfd( int epollfd, int fd )
    {
        epoll_event event;
        event.data.fd = fd;
        event.events = EPOLLIN | EPOLLET;
        epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
        setnonblocking( fd );
    }
    
    void sig_handler( int sig )
    {
        int save_errno = errno;
        int msg = sig;
        send( pipefd[1], ( char* )&msg, 1, 0 );
        errno = save_errno;
    }
    
    void addsig( int sig )
    {
        struct sigaction sa;
        memset( &sa, '', sizeof( sa ) );
        sa.sa_handler = sig_handler;
        sa.sa_flags |= SA_RESTART;
        sigfillset( &sa.sa_mask );
        assert( sigaction( sig, &sa, NULL ) != -1 );
    }
    
    int main( int argc, char* argv[] )
    {
        if( argc <= 2 )
        {
            printf( "usage: %s ip_address port_number
    ", basename( argv[0] ) );
            return 1;
        }
        const char* ip = argv[1];
        int port = atoi( argv[2] );
    
        int ret = 0;
        struct sockaddr_in address;
        bzero( &address, sizeof( address ) );
        address.sin_family = AF_INET;
        inet_pton( AF_INET, ip, &address.sin_addr );
        address.sin_port = htons( port );
    
        int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
        assert( listenfd >= 0 );
    
        //int nReuseAddr = 1;
        //setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &nReuseAddr, sizeof( nReuseAddr ) );
        ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
        if( ret == -1 )
        {
            printf( "errno is %d
    ", errno );
            return 1;
        }
        //assert( ret != -1 );
    
        ret = listen( listenfd, 5 );
        assert( ret != -1 );
    
        epoll_event events[ MAX_EVENT_NUMBER ];
        int epollfd = epoll_create( 5 );
        assert( epollfd != -1 );
        addfd( epollfd, listenfd );
    
        ret = socketpair( PF_UNIX, SOCK_STREAM, 0, pipefd );
        assert( ret != -1 );
        setnonblocking( pipefd[1] );
        addfd( epollfd, pipefd[0] );
    
        // add all the interesting signals here
        addsig( SIGHUP );
        addsig( SIGCHLD );
        addsig( SIGTERM );
        addsig( SIGINT );
        bool stop_server = false;
    
        while( !stop_server )
        {
            int number = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
            if ( ( number < 0 ) && ( errno != EINTR ) )
            {
                printf( "epoll failure
    " );
                break;
            }
        
            for ( int i = 0; i < number; i++ )
            {
                int sockfd = events[i].data.fd;
                if( sockfd == listenfd )
                {
                    struct sockaddr_in client_address;
                    socklen_t client_addrlength = sizeof( client_address );
                    int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
                    addfd( epollfd, connfd );
                }
                else if( ( sockfd == pipefd[0] ) && ( events[i].events & EPOLLIN ) )
                {
                    int sig;
                    char signals[1024];
                    ret = recv( pipefd[0], signals, sizeof( signals ), 0 );
                    if( ret == -1 )
                    {
                        continue;
                    }
                    else if( ret == 0 )
                    {
                        continue;
                    }
                    else
                    {
                        for( int i = 0; i < ret; ++i )
                        {
                            //printf( "I caugh the signal %d
    ", signals[i] );
                            switch( signals[i] )
                            {
                                case SIGCHLD:
                                case SIGHUP:
                                {
                                    continue;
                                }
                                case SIGTERM:
                                case SIGINT:
                                {
                                    stop_server = true;
                                }
                            }
                        }
                    }
                }
                else
                {
                }
            }
        }
    
        printf( "close fds
    " );
        close( listenfd );
        close( pipefd[1] );
        close( pipefd[0] );
        return 0;
    }
    
    程序清单2
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <assert.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <signal.h>
    #include <fcntl.h>
    
    #define BUF_SIZE 1024
    
    static int connfd;
    
    void sig_urg( int sig )
    {
        int save_errno = errno;
        
        char buffer[ BUF_SIZE ];
        memset( buffer, '', BUF_SIZE );
        int ret = recv( connfd, buffer, BUF_SIZE-1, MSG_OOB );
        printf( "got %d bytes of oob data '%s'
    ", ret, buffer );
    
        errno = save_errno;
    }
    
    void addsig( int sig, void ( *sig_handler )( int ) )
    {
        struct sigaction sa;
        memset( &sa, '', sizeof( sa ) );
        sa.sa_handler = sig_handler;
        sa.sa_flags |= SA_RESTART;
        sigfillset( &sa.sa_mask );
        assert( sigaction( sig, &sa, NULL ) != -1 );
    }
    
    int main( int argc, char* argv[] )
    {
        if( argc <= 2 )
        {
            printf( "usage: %s ip_address port_number
    ", basename( argv[0] ) );
            return 1;
        }
        const char* ip = argv[1];
        int port = atoi( argv[2] );
    
        struct sockaddr_in address;
        bzero( &address, sizeof( address ) );
        address.sin_family = AF_INET;
        inet_pton( AF_INET, ip, &address.sin_addr );
        address.sin_port = htons( port );
    
        int sock = socket( PF_INET, SOCK_STREAM, 0 );
        assert( sock >= 0 );
    
        int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
        assert( ret != -1 );
    
        ret = listen( sock, 5 );
        assert( ret != -1 );
    
        struct sockaddr_in client;
        socklen_t client_addrlength = sizeof( client );
        connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
        if ( connfd < 0 )
        {
            printf( "errno is: %d
    ", errno );
        }
        else
        {
            addsig( SIGURG, sig_urg );
            fcntl( connfd, F_SETOWN, getpid() );
    
            char buffer[ BUF_SIZE ];
            while( 1 )
            {
                memset( buffer, '', BUF_SIZE );
                ret = recv( connfd, buffer, BUF_SIZE-1, 0 );
                if( ret <= 0 )
                {
                    break;
                }
                printf( "got %d bytes of normal data '%s'
    ", ret, buffer );
            }
    
            close( connfd );
        }
    
        close( sock );
        return 0;
    }
    


  • 相关阅读:
    VS Code安装以及工作区的创建
    var let const的使用和区别
    springboot 配置mysql日期返回格式
    vue安装Node和NPM配置,路由安装。
    分组查询语句(group by函数)
    ORA-00918:未明确定义列
    内连接(inner join)
    右外连接(right join)
    左外连接(left join)
    比较oracle两表中date类型数据是否一致语句查询
  • 原文地址:https://www.cnblogs.com/zsychanpin/p/6720062.html
Copyright © 2011-2022 走看看