简体   繁体   中英

Linux server C code stalls after receiving a signal

I have a problem with this simple server code, it works as expected until it receives a signal. For debug I print server and client file descriptors before calling select with the line:

fprintf(stderr,"server_socket_fd=%d client_socket_fd=%d fd_max=%d\n", server_socket_fd, client_socket_fd, fd_max);

When running normally it keeps printing

server_socket_fd=3 client_socket_fd=4 fd_max=4

but when it receives a signal it prints this line once

server_socket_fd=3 client_socket_fd=-1 fd_max=3

and then the program stalls.

Using GDB I put a breakpoint in the signal_handler and when it breaks I can't watch client_socket_fd variable, gdb says

No symbol "client_socket_fd" in current context.

And it doesn't return properly from the signal_handler function.. if I watch the back trace:

(gdb) bt
#0  0xb7fdccf9 in ?? ()
#1  0xb7e26af3 in __libc_start_main (main=0x8048bdd <main>, argc=1, argv=0xbfffef24, init=0x8049a00 <__libc_csu_init>, 
    fini=0x8049a70 <__libc_csu_fini>, rtld_fini=0xb7fed160 <_dl_fini>, stack_end=0xbfffef1c) at libc-start.c:287
#2  0x08048b01 in _start ()

I don't know how to debug deeper.

This is the main code:

char receive_buf[2048];
int main(int argc, char *argv[]){

    int server_socket_fd;
    int client_socket_fd = -1;
    int fd_max;

    struct sockaddr_in s_in;
    int one = 1;
    int status;

    fd_set readfds;

    int port;

    int next_option;
    const char* short_options = "hp:d:";
    const struct option long_options[] = {
        { "help",   0,  NULL,   'h'},
        { "port",   1,  NULL,   'p'},
        { "debug",   1,  NULL,   'd'},
        { NULL,     0,  NULL,   0}
    };

    program_name = argv[0];

    port = DEFAULT_PORT;
    debug = 0;

    do{
        next_option = getopt_long(argc, argv, short_options, long_options, NULL);
        switch(next_option){
            case 'h':
                print_usage(stdout, 0);
                break;
            case 'p':
                port = atoi(optarg);
                if((port < 0)||(port > 65535)){
                   fprintf(stderr, "Invalid port number (%d), using default: %d", port, DEFAULT_PORT);
                   port = DEFAULT_PORT;
                }
                break;
            case 'd':
                debug = atoi(optarg);
                if(debug < 0 || debug > 3)
                    debug = 0;
                break;
            case '?':
                print_usage(stderr, 1);
                break;
            case -1:
                break;
            default:
                abort();
        }
    }while(next_option != -1);

    /*************************  SIGNAL DEFINITIONS  ***************************/

    signal_action.sa_handler = (void *)signal_handler;
    sigemptyset(&signal_action.sa_mask);
    signal_action.sa_flags = SA_RESTART; // | SA_NOCLDSTOP;

    if(sigaction(SIGINT, &signal_action, NULL) == -1){
        fprintf(stderr, "Error setting SIGINT signal handler\n");
        exit(1);
    }

    if(sigaction(SIGTERM, &signal_action, NULL) == -1){
        fprintf(stderr, "Error setting SIGTERM signal handler\n");
        exit(1);
    }

    if(sigaction(SIGWINCH, &signal_action, NULL) == -1){
        fprintf(stderr, "Error setting SIGWINCH signal handler\n");
        exit(1);
    }

    /*  // ALSO TRIED WITH SIGNAL WITH SAME RESULT
    if(signal(SIGWINCH, signal_handler) == SIG_ERR){
        fprintf(stderr, "signal error\n");
        return 1;
    }
   */

    s_in.sin_family = PF_INET;
    s_in.sin_port = htons(port);
    s_in.sin_addr.s_addr = INADDR_ANY;

    if ((server_socket_fd = socket(s_in.sin_family, SOCK_STREAM, IPPROTO_TCP)) == -1){
        perror("Error creating socket");
        return 1;
    }


    if(setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1){
        perror("Error setting socket parameters");
        return 1;
    }

    ////////////////////////////////////////////
    int x;
    x=fcntl(server_socket_fd,F_GETFL,0);              // Get socket flags
    fcntl(server_socket_fd,F_SETFL,x | O_NONBLOCK);   // Add non-blocking flag
    ////////////////////////////////////////////

    if(bind(server_socket_fd, (struct sockaddr*) &s_in, sizeof(s_in)) == -1){
        perror("Error binding socket");
        return 1;
    }


    if(listen(server_socket_fd, 1) == -1){
        perror("Error creating listening socket");
        return 1;
    }else{
        printf("Server (%d) listening on port %d\n", server_socket_fd, port);
    }

    memset(receive_buf, '\0', sizeof(receive_buf));
    gettimeofday(&t_print, NULL);

    while(1){

        // SERVER
        FD_ZERO(&readfds);
        FD_SET(server_socket_fd, &readfds);
        fd_max = server_socket_fd;

        // ADD CLIENT IF CONNECTED
        if(client_socket_fd > 0){
            FD_SET(client_socket_fd, &readfds);
            if(client_socket_fd > server_socket_fd)
                fd_max = client_socket_fd;
        }

        // ADDED THIS FPRINTF TO CHECK VARIABLES  <----------------------------------
        fprintf(stderr,"server_socket_fd=%d client_socket_fd=%d fd_max=%d\n", server_socket_fd, client_socket_fd, fd_max);

        if(select(fd_max+1, &readfds, NULL, NULL, NULL) == -1){
            if(errno != EINTR){
                perror("select failed");
            }
        }

        // ACCEPT CLIENT
        if(FD_ISSET(server_socket_fd, &readfds)){

            struct sockaddr_in s_in;
            socklen_t len;

            len = sizeof(s_in);
            if((client_socket_fd = accept(server_socket_fd, (struct sockaddr*) &s_in, &len)) < 0){
                if(errno != EWOULDBLOCK){
                    perror("En accept");
                }
            }else
                printf("New client connected from %s\n", inet_ntoa(s_in.sin_addr));
        }

        // RECEIVE FROM CLIENT
        if(client_socket_fd > 0){
            if(FD_ISSET(client_socket_fd, &readfds)){
                handle_client(client_socket_fd);
            }
        }
    }

    return 0;
    }


int handle_client(int cl_fd){

    int n;

    n = recv(cl_fd, receive_buf, sizeof(receive_buf) - 1, MSG_DONTWAIT);
    if(n == 0){
        fprintf(stderr,"--------------> DEBUG: handle_client:client %d closed connection\n", cl_fd);
    }else if(n < 0){
        if(errno == EAGAIN){
            return 0;
        }else{
            fprintf(stderr,"--------------> DEBUG: handle_client: recv ERROR: client %d closed connection (errno: %d : %s)\n", cl_fd, errno, strerror(errno));
            memset(receive_buf, 0, sizeof(receive_buf));
            return -1;
        }
    }else{
        receive_buf[n] = '\0';
        fprintf(stderr, "%s\n", receive_buf);
    }
        return 0;
   }
void signal_handler(int sig){

    switch(sig){

    case SIGINT:
        exit_properly(0);
        break;
    case SIGTERM:
        exit_properly(1);
        break;
    case SIGABRT:
        fprintf(stderr, "SIGABRT signal received\n");
        break;

    case SIGWINCH:
        fprintf(stderr, "\33[2J");
        fflush(stdout);
        break;

    default:
        fprintf(stderr, "Unhandled signal %d received\n",sig);
        break;
    }
}

I don't know what else can I do for debugging this issue and I am stuck. Any help will be very appreciated!

EDITED:

This is the strace output when it fails, as you can see it prints (and select uses) the right file descriptors and then, after the signal occurs, the client_socket_fd is wrong because accept fails with EAGAIN. I have commented the exit_properly calls and also the signal handling for SIGTERM and SIGINT. For the SIGWINH signal I do nothing, just return.

STRACE output:

write(2, "server_socket_fd=3 client_socket"..., 47server_socket_fd=3 client_socket_fd=4 fd_max=4
) = 47
select(5, [3 4], NULL, NULL, NULL)      = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
sigreturn() (mask [])                   = -1 EINTR (Interrupted system call)
accept(3, 0xbf981e7c, [16])             = -1 EAGAIN (Resource temporarily unavailable)
write(2, "server_socket_fd=3 client_socket"..., 48server_socket_fd=3 client_socket_fd=-1 fd_max=3
) = 48
select(4, [3], NULL, NULL, NULL)        = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
sigreturn() (mask [])                   = -1 EINTR (Interrupted system call)
accept(3, 0xbf981e7c, [16])             = -1 EAGAIN (Resource temporarily unavailable)
write(2, "server_socket_fd=3 client_socket"..., 48server_socket_fd=3 client_socket_fd=-1 fd_max=3
) = 48
select(4, [3], NULL, NULL, NULL

The signal_handler now:

void signal_handler(int sig){

    switch(sig){
    /*
    case SIGINT:
        exit_properly(0); //sigint_flag = 1;
        break;
    case SIGTERM:
        exit_properly(1); //sigterm_flag = 1;
        break;

*/
    case SIGWINCH:
        //sigwinch_flag = 1;
/*
        fprintf(stderr, "\33[2J");
        fflush(stdout);
    */
        break;

    default:
        //fprintf(stderr, "Unhandled signal %d received\n",sig);
        break;
    }
}

Also tried without the SA_RESTART flag... same result... ?: /

select() will be interrupted by a signal, return -1, and set errno to EINTR, but your code fails to handle this. Even if you install a signal handler with the SA_RESTART , there are still a number of system calls that will be interrupted, return an error condition and set errno to EINTR .

See the "Interruption of system calls and library functions by signal handlers" section at http://man7.org/linux/man-pages/man7/signal.7.html/

If select fails, your code goes on to check the readfds like this:

if(FD_ISSET(server_socket_fd, &readfds)){

However, if select() fails, the fd_set variables you pass to it are in an indeterministic state, you should not rely on their values.

Instead, if select fails you should just re-start your loop with eg a continue statement like so:

  if(select(fd_max+1, &readfds, NULL, NULL, NULL) == -1){
        if(errno != EINTR){
            perror("select failed");
           //Might be severe enough to quit your program... 
        }
       continue; 
    }

I found the solution just using a temporally variable.

I don't know why, but signals made select flags the server_socket_fd to be read (IF SOMEONE KNOWS WHY PLEASE SHARE), just like if a client would be trying to connect, and accept returned EAGAIN error, so the client_socket_fd variable was written to -1.

I solved this simply using a temp variable (ret) instead of assigning the accept result directly to client_socket_fd:

// ACCEPT CLIENT
        if(FD_ISSET(server_socket_fd, &readfds)){

            struct sockaddr_in s_in;
            socklen_t len;

            len = sizeof(s_in);
            if((ret = accept(server_socket_fd, (struct sockaddr*) &s_in, &len)) < 0){
                if(errno != EWOULDBLOCK){
                    perror("En accept");
                }
            }else{
                client_socket_fd = ret;
                printf("New client connected from %s\n", inet_ntoa(s_in.sin_addr));
            }
        }

Only update variables when you are sure they are OK... lesson learned! :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM