Fabio Carello
Fabio Carello

Reputation: 1102

Sequential server: behavior on accept(2) call

I have a server with a TCP-socket in a non-concurrent single process implementation.

int main(int argc, char** argv) {

    int sock_ds, acc_sock_ds, opt, client_addr_l;
    unsigned short port;
    struct sockaddr_in server_addr, client_addr;


    /*Parsing command line: port-number retrieving*/
    /*...*/

    printf("Port number retrieved (%d), server is starting ...\n", port);

    /*TCP Socket creation*/
    sock_ds = socket(AF_INET, SOCK_STREAM, 0); 
    if(sock_ds == -1){
        fprintf(stderr, "Socket creation error\n");
        exit(EXIT_FAILURE);
    }

    /*Server address binding*/
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    //use setsockopt(2) with OS_REUSEADR ???
    if(bind(sock_ds, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1){
        fprintf(stderr, "Address binding error\n");
        exit(EXIT_FAILURE);
    }

    /*Server with passive socket*/
    if(listen(sock_ds, SOMAXCONN) == -1){
        fprintf(stderr, "Listen call error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    printf("Server is ready. Waiting for connections.\n");

        /*Busy-waiting server*/
while(1){

    memset(&client_addr, 0, sizeof(client_addr));
    acc_sock_ds = accept(sock_ds, &client_addr, &client_addr_l);
    /*Connect error management*/
    if(acc_sock_ds == -1){
         errsv = errno;
         if(errsv == 12 || errsv == 23 ){
            /*Fatal errors ENOMEM, ENFILE*/
            fprintf(stderr, "Fatal error on accept\n");
            exit(EXIT_FAILURE);
         }
         else if(errsv == 103 || errsv == 4 || errsv == 71 || errsv == 92 
                 || errsv == 112 || errsv == 113 || errsv == 95 || errsv == 101)
             /*ECONNABORTED, EINTR, EPROTO, ENOPROTOOPT, 
              * EHOSTDOWN, EHOSTUNREACH, EOPNOTSUPP, ENETUNREACH*/
             continue;
         else if(errsv == 100 || errsv == 64){
             /* ENETDOWN - ENONET */
             /*start timeout...*/
             continue;
         }
    }
  }
}

In this test I expected that the server will continue to make the call accept(2) also on failure. I know there are some terminal error conditions such EBADF. Should I provide for a different behavior (termination of the process) depending on the value of errno? For which values ​​the server must be stopped and for which may continue waiting?

Code edited. I never made ​​the error handling via errno. If there are errors or suggestions please notify them. I noticed that the two conditions ENETDOWN - ENONET are implied by the absence of network. Should I expect for these situations a timeout to prevent stalling?

Upvotes: 1

Views: 565

Answers (2)

wildplasser
wildplasser

Reputation: 44250

Stylistic:

  • use the symbolic constants instead of putting them in comments
  • when unexpected error at least report the error number
  • handle the default/unexpected cases, too

BTW: most of the symbolic constants below don't seem to exist.


#include <errno.h>
#include <strings.h>

 /* Busy-waiting server */
while(1){

    memset(&client_addr, 0, sizeof(client_addr));
    acc_sock_ds = accept(sock_ds, &client_addr, &client_addr_l);
    /*Connect error management*/
    if(acc_sock_ds == -1){
        switch(errsv=errno) {
            /*Fatal errors ENOMEN, ENFILE, all others*/
        default :
        case ENOMEM :
        case ENFILE :
            fprintf(stderr, "Fatal error on accept %d(%s)\n"
                , errsv, strerror(errsv)
                );
            exit(EXIT_FAILURE);
            /* normal NON-ERROR error conditions */
        case ECONNABORTED :
        case EINTR :
        case EPROTO :
        case ENOPROTOOPT :
        case EHOSTDOWN :
        case EHOSTUNREACH :
        case EOPNOTSUPP :
        case ENETUNREACH :
             continue;
        case ENETDOWN :
        case ENONET :
             /*start timeout...*/
             continue;
         }
    }
  }
}

Upvotes: 1

Alnitak
Alnitak

Reputation: 339816

Looking at the error codes documented for accept(2) on MacOS X (and which should be consistent for most any POSIX compliant system):

  • EBADF - programmer error - can't happen if the original socket call succeeded
  • ECONNABORTED - try again
  • EFAULT - programmer error - can't happen unless &client_addr is invalid
  • EINTR - call interrupted, try again
  • EINVAL - "socket is unwilling to accept connections" - I'm not sure how that would happen. It might be due to not calling listen(2) first (programmer error) although apparently on some OS it can also be caused by the namelen parameter being negative (also programmer error)
  • EMFILE - programmer error - you ran out of per-process FDs but that shouldn't happen unless you forgot to close them
  • ENFILE - the system ran out of FDs - this is probably fatal
  • ENOMEM - the system ran out of memory - this is probably fatal
  • ENOTSOCK - programmer error - can't happen unless you passed an FD that wasn't a socket
  • EOPNOTSUPP - programmer error - can't happen unless you pass a socket that isn't SOCK_STREAM
  • EWOULDBLOCK - programmer error - your socket was configured as non-blocking

In addition, it appears that Linux systems can also generate ENETDOWN, EPROTO, ENOPROTOOPT, EHOSTDOWN, ENONET, EHOSTUNREACH, EOPNOTSUPP, and ENETUNREACH. These are network errors for which you should loop.

In summary, you should probably:

  • exit the program for ENFILE or ENOMEM
  • loop on ECONNABORTED and EINTR, and the list above from Linux
  • assert() during your debugging for anything else - these runtime errors should only happen due to logic errors in the code, and not because of network related events.

Upvotes: 2

Related Questions