JamesPoppycock
JamesPoppycock

Reputation: 39

C server client Bad file descriptor

I have been figuring out why the below code give a bad descriptor for the whole day now. Below is the server code, most of it references to Beej's guide.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

#include "CountryData.c"

//This function determine if address is IPv4 or IPv6 IP address
void *getAddr_Type(struct sockaddr *sa) {
    if (sa->sa_family == AF_INET) { //If IPv4
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    else //if IPv6
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

void sigchld_handler(int s)
{
    // waitpid() might overwrite errno, so it is stored in a variable first:
    int saved_errno = errno;

    while(waitpid(-1, NULL, WNOHANG) > 0);

    errno = saved_errno;
}


int main(void){

    readData(); //Read from CountryData.c

    int status, sockfd, client_sockfd;
    int pid; //fork return value

    char buffer[1000];
    int bytecount;

    struct addrinfo hints, *res, *serverInfo; //res points to linked list of "struct addrinfo"; serverInfo also points to linked list of "struct addrinfo" for use in for loop.
    struct sockaddr_storage client_addr; //Address information of the client
    struct sigaction sa;

    socklen_t address_size; //Initialize size of address

    char i[INET6_ADDRSTRLEN]; //INET6_ADDRSTRLEN macro is used to store maximum length of IPv6. Since IPv4 is definitely shorter than IPv6, "INET_ADDRSTRLEN" is not used.


    memset(&hints, 0, sizeof(hints));  //emptying the structure

    //Pass in value into "addrinfo" struct
    hints.ai_family = AF_INET; //Using IPv4
    hints.ai_socktype = SOCK_STREAM; //Using TCP
    hints.ai_flags = AI_PASSIVE; //AI_PASSIVE = Own IP address

    status = getaddrinfo(NULL, "8888", &hints, &serverInfo); //Initialising status return value and also passing in values to getaddrinfo().
//IP address is set to null. This will be filled in automatically by AI_PASSIVE.

    if (status != 0) {
        fprintf(stderr, "Error: %s\n", gai_strerror(status));//gai_strerror to print human readable error
        exit(1); 
    }


    //Loop through all results and bind to the first
    for (res = serverInfo; res != NULL; res = res->ai_next) {

         //(1)Initializing socket 
        if ((sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) { //Show error message if initializing socket file descriptor fails
            perror("Socket"); 
            continue;
        }

        int optValue=1;

        if ((setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optValue, sizeof(int))) == -1) {
            perror("Socket options");
            exit(1);
        } //(2)setting socket options. "SO_REUSEADDR" to prevent "Address already in use" and to allow reuse of the port

        if ((bind(sockfd, res->ai_addr, res->ai_addrlen)) == -1) { //(3) Binding to local address and port
            close(sockfd);
            perror("Bind");
            continue;
        }

    break;
    }
    freeaddrinfo(serverInfo); //Freeing "serverInfo" linked list


    //However, if linked list is still empty, print error.
    if (res == NULL) {
        fprintf(stderr, "Error! Server is unable to bind.\n");
        exit(1);
    }

    //If unable to listen, print error.
    if ((listen(sockfd, 8)) == -1) { //(4) Listen for client connections, maximum of 8 waiting in queue
        perror("Listen");
    }

    sa.sa_handler = sigchld_handler; // reap all dead processes
    sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESTART;
        if (sigaction(SIGCHLD, &sa, NULL) == -1) {
            perror("sigaction");
            exit(1);
    }


    printf("Running server program 'server' ... \n\n\nCountry Directory Server Started! PID: %d\n", getpid());



    for(;;) //infinite loop for server to wait for client requests
    { 
        memset(buffer, 0, 1000);
        address_size = sizeof(client_addr);
        client_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &address_size);

        if (client_sockfd == -1) {
            perror("Accept");
            close(client_sockfd);
            exit(1);
        }

        inet_ntop(client_addr.ss_family, getAddr_Type((struct sockaddr *)&client_addr), i, sizeof(i));//retrieving IP address. "inet_ntop" is used for IPv6 compatibility.  


        printf("-------------------------------------------------------\n");
        printf("Connection received from:       %s\n\n", i);


        if ((pid = fork()) == -1){ //Starts forking
            perror("Failed to fork");
            close(sockfd);
        }

        else if (pid == 0){ //child process

            close(sockfd);//Child doesn't need this socket
            memset(buffer, 0, 1000); //clear the buffer

            if ((bytecount = recv(client_sockfd, buffer, 1000, 0)) == -1){//Receiving Client's input
                perror("Server unable to receive");
                close(client_sockfd);
                exit(0);
            }
            else if ((strcasecmp(buffer, "END")) == 0){ //Nested If-statement; If client sends "end"
                close(client_sockfd);
                exit(0);
                break;
            }

            else if (bytecount == 0) { //If "recv" returns 0, client has closed the connection
                printf("Client (%d) has closed the connection.\n", getpid());
                close(client_sockfd);
                exit(0);
                break;
            }else {
            printf("%s", buffer);
            printf("%d", client_sockfd);
            }
        }

    close(client_sockfd);

    }   //end of infinite while loop

}//End of main function

It successfully read client's input and print it out on the screen for the first for(;;) loop. The after the 2nd iteration, it shows Bad file descriptor Below is the output in the server terminal after typing Hi in the client.

Johnny$ server
Running server program 'server' ... 


Country Directory Server Started! PID: 18386
-------------------------------------------------------
Connection received from:       127.0.0.1

Accept: Bad file descriptor
Hi4

The number 4 is printing out the return value of the child file descriptor. This means that the loop ran once, then return an error. My expected output is simply to keep listening for the client's input and the server should constantly spit out what the client typed. I am new to this server thing and am really having a major headache right getting this to work. Any help will be greatly appreciated.

Below is the client's code, if you're interested.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <arpa/inet.h>
#include <errno.h>

void welcome()
{
    printf("\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
    printf("        Welcome to the Country Info Directory Service!         \n");
    printf("       ----------------------------------------------          \n");
    printf("Usage :\n\n");
    printf("1) At the '>' prompt, type in the name of the country\n");
    printf("   you wish to search\n\n");
    printf("2) To end program, type in 'end'\n");
    printf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n");
}

//This function determine if address is IPv4 or IPv6 IP address
void *getAddr_Type(struct sockaddr *sa) {
    if (sa->sa_family == AF_INET) { //If IPv4
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    else //if IPv6
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[]) {
    int sockfd, numOfBytes;
    int retrieveInfo;
    char buf[100];
    struct addrinfo hints, *res, *serverInfo;

    char i[INET6_ADDRSTRLEN]; //INET6_ADDRSTRLEN macro is used to store maximum length of IPv6. Since IPv4 is definitely shorter than IPv6, "INET_ADDRSTRLEN" is not used.

    if (argc != 2){
        printf("Please enter in this format:\n'client':server-host-name\nFor example, if your hostname is vmwubuntu, please type:\nclient vmwubuntu [Enter]\n\n");
        exit(1);
    }

    welcome();

    memset(&hints, 0, sizeof(hints));  //emptying the structure

    //Pass in value into "addrinfo" struct
    hints.ai_family = AF_INET; //Using IPv4
    hints.ai_socktype = SOCK_STREAM; //Using TCP

    retrieveInfo = getaddrinfo(argv[1], "8888", &hints, &serverInfo); 

    if(retrieveInfo != 0) {
        printf("Fail to retrieve address!");
    }

    //Loop through all results and bind to the first
    for (res = serverInfo; res != NULL; res = res->ai_next) {

        sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //(1)Initializing socket 

        if (sockfd == -1) { //Show error message if initializing socket file descriptor fails
            perror("Socket"); 
            continue;
        }

        if (connect(sockfd, res->ai_addr, res->ai_addrlen) == -1) { //Retrieving values from "struct addrinfo" through "res" pointer
            //close(sockfd);
            perror("Connection");
            continue;
        }
        break;
    }

    if (res == NULL) {
    printf("Failed to connect to server\n");
    exit(1);
    }

    char input[1000];
    char receive[1000];

    for(;;)
    {   
        memset(input, '\0', 1000); //Initialize buffer size to store user input
        printf("Enter Country > ");
        fgets(input, 1000, stdin); //Take in user input with 1000 as the buffer size
        input[strlen(input) - 1] = '\0'; //Stripping the null terminator away

        if (strcasecmp(input, "END") == 0){ //If user enters "end"(case is ignored), close the file descriptor and exit
            close(sockfd);
            exit(0);
        }

        else {//SEND
            if((numOfBytes = send(sockfd, input, strlen(input), 0)) == -1){ //start of nested if statement
                perror("Unable to send");
                exit(1);
            }
            else if (numOfBytes != strlen(input)){ //If string is not sent in full
                perror("Send");
                close(sockfd);
                exit(1);
            }else{//for testing purposes
            printf("%d\n",strlen(input));//for testing purpose
            printf("%d\n", numOfBytes); //for testing purpose
            }//End of nested if statement

        }

    }//End of for infinite loop
} //End of main()

Upvotes: 1

Views: 907

Answers (2)

Lee Jacob
Lee Jacob

Reputation: 11

The child process in the sever side will try to accept the same fd. How about to add a infinite loop before

 else if (pid == 0){ //child process

        close(sockfd);//Child doesn't need this socket
        memset(buffer, 0, 1000); //clear the buffer

        for (;;) {
        if ((bytecount = recv(client_sockfd, buffer, 1000, 0)) == -1){//Receiving Client's input
            perror("Server unable to receive");
            close(client_sockfd);
            exit(0);
        }
        else if ((strcasecmp(buffer, "END")) == 0){ //Nested If-statement; If client sends "end"
            close(client_sockfd);
            exit(0);
            break;
        }

        else if (bytecount == 0) { //If "recv" returns 0, client has closed the connection
            printf("Client (%d) has closed the connection.\n", getpid());
            close(client_sockfd);
            exit(0);
            break;
        }else {
        printf("%s", buffer);
        printf("%d", client_sockfd);
        }

        }
    }

Upvotes: 1

Jonathon Reinhart
Jonathon Reinhart

Reputation: 137398

Your child process appears to be not exiting, and instead proceeding with the same code as the parent. Then, you try to call accept with the closed file descriptor.

This is why I always put the child code in its own function, and always call _exit() immediately following. Note that I use _exit() instead of exit() to ensure that no parent atexit handlers are executed.

Furthermore, it helps to include PIDs in your log messages. Try using something like this:

#define INFO(fmt, ...)    fprintf(stderr, "[%d] %s" fmt, getpid(), __FUNCTION__, __VA_ARGS__)

...
INFO("x=%d\n", x);

Upvotes: 3

Related Questions