Reputation: 39
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
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
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