JamesPoppycock JamesPoppycock - 3 months ago 8
C Question

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()

Answer

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);