Thread: Socket file descriptor valid and then not valid

  1. #1
    Registered User
    Join Date
    Jul 2008
    Posts
    38

    Socket file descriptor valid and then not valid

    Hello,

    I am currently working on a homework assignment for which i have to create a server program, which simulates sensors. The client is created in Java, but i have not gotten that far yet.

    Simulating the sensors was easy and is working fine (so far). However, i am having difficulties with the networking side of this assignment. The server accepts the client nicely. it sets the client socket nicely to nonblocking. it assigns the client fd along with some other data to a server_client_argument_t variable used in a call to pthread_create. Up to this point everything is going great, but as soon as the server_client_routine (the created thread) uses the fd in any fd related function, it complains about a (EBADF) bad descriptor.

    First thing on my mind was that the fd was not passed correctly to the thread, and server_client_routine was using a garbage value instead. But the fd used by server_client_routine is perfectly valid.

    Then i thought about a problem the use of pthreads, but the pthread man page states that file descriptors are process wide.

    I tried to create a smaller, simpler program to see if it caused any problems. It worked, the only difference is that it does not uses threads.

    The client code does not exist yet. I am expecting the program to pretty much wait for data continuously for now. I am using Python to test the server.

    server.h file:
    Code:
    #ifndef _SERVER_H
    #define	_SERVER_H
    
    #include <pthread.h>
    
    #include "sensor.h"
    
    #ifdef	__cplusplus
    extern "C" {
    #endif
    
        struct server_client {
            pthread_t thread;
            pthread_mutex_t lock;
            sensor_t* sensor_alarm;
            int sock_desc;
            int cont;
    
            struct server_client* next;
        };
    
        typedef struct server_client server_client_t;
    
        struct server_client_list {
            server_client_t* root;
            pthread_mutex_t lock;
        };
    
        typedef struct server_client_list server_client_list_t;
    
        struct server_argument {
            server_client_list_t* clients;
            sensor_array_t* sensors;
            int cont;
        };
    
        typedef struct server_argument server_argument_t;
    
        struct server_client_argument {
            server_client_list_t* clients;
            server_client_t* client;
        };
    
        typedef struct server_client_argument server_client_argument_t;
    
        void* server_routine(void* args);
    
        server_client_list_t* server_client_array_init(int size);
        void server_client_array_free(server_client_list_t* array, int free_clients);
        void server_client_array_append(server_client_list_t* array, server_client_t* client);
        void server_client_array_remove(server_client_list_t* array, server_client_t* client);
    
        server_client_t* server_client_init(int sock_desc);
        void server_client_free(server_client_t* client);
    
    #ifdef	__cplusplus
    }
    #endif
    
    #endif	/* _SERVER_H */
    server.c file
    Code:
    #include "server.h"
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netdb.h>
    #include <memory.h>
    #include <fcntl.h>
    #include <errno.h>
    
    // Code shamefully stolen from Python's internal socket code.
    void setblocking(int sockfd, int block) {
        int delay_flag = fcntl(sockfd, F_GETFL, 0);
        if (block)
            delay_flag &= (~O_NONBLOCK);
        else
            delay_flag |= O_NONBLOCK;
        if (fcntl(sockfd, F_SETFL, delay_flag) == -1) {
            perror("setblocking(): Setting to nonblock failed");
            exit(1);
        }
    }
    
    void* server_client_routine(void* args) {
        server_client_argument_t* client_args = args;
        server_client_t* client = client_args->client;
        server_client_list_t* clients = client_args->clients;
    
        // Blocking is done after accepted, in server_routine below:
        //setblocking(client->sock_desc, 0);
    
        printf("Going in to while loop.\n");
    
        int received = 0;
        int loops = 0;
    
        while (client->cont) {
            const int NETWORK_BUFFER_SIZE = 2048;
            
            char* buf = malloc(NETWORK_BUFFER_SIZE);
            for (int i = 0; i < NETWORK_BUFFER_SIZE; i++) buf[i] = 0;
    
            pthread_mutex_lock(&client->lock);
    
            printf("Im going for another loop.\n");
            
            if ((received = recv(client->sock_desc, buf, NETWORK_BUFFER_SIZE, 0)) > 0) {
                printf("I have received (%d bytes): %s\n", received, buf);
            }
            else if (received == 0) {
                printf("Client shutdown gracefully.\n");
                client->cont = 0;
            }
            else if (received == - 1) {
                int l_errno = errno;
                switch (l_errno) {
                    case EBADF:
                        printf("Dropping client because of bad descriptor (%d) (disconnect likely).\n", client->sock_desc);
                        printf("Looped trough EAGAIN %d times.\n", loops);
                        client->cont = 0;
                        break;
                    case EAGAIN: // EWOULDBLOCK is duplicate value.
                        loops++;
                        //printf("Going through with fd: %d.\n", client->sock_desc);
                        break;
                    default:
                        printf("%d means %s\n", errno, strerror(errno));
                        break;
                }
            }
            pthread_mutex_unlock(&client->lock);
    
            free(buf);
        }
    
        pthread_mutex_lock(&client->lock);
        close(client_args->client->sock_desc);
        pthread_mutex_unlock(&client->lock);
    
        printf("Server client thread returning.\n");
        free(client_args);
        return NULL;
    }
    
    void* server_routine(void* args) {
        server_argument_t* server_arg = args;
        struct addrinfo hints, *res;
    
        printf("The server was started.\n");
    
        memset(&hints, 0, sizeof hints);
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_PASSIVE;
    
        getaddrinfo(NULL, "5000", &hints, &res);
    
        int sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    
        int yes = 1;
    
        // lose the pesky "Address already in use" error message
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
            perror("setsockopt failed, address in use");
            exit(1);
        }
    
        setblocking(sockfd, 0);
    
        bind(sockfd, res->ai_addr, res->ai_addrlen);
    
        listen(sockfd, 10);
    
        while (server_arg->cont) {
            struct sockaddr_storage client_addr;
            socklen_t client_addr_size = sizeof client_addr;
    
            int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_addr_size);
    
            if (clientfd == -1 && errno == EAGAIN) {
                continue;
            }
    
            pthread_mutex_lock(&server_arg->clients->lock);
    
            printf("Someone has connected!\n");
    
            setblocking(clientfd, 0);
    
            server_client_t* client = server_client_init(clientfd);
            server_client_argument_t* client_args = malloc(sizeof(server_client_argument_t));
            client_args->client = client;
            client_args->clients = server_arg->clients;
    
            server_client_array_append(server_arg->clients, client);
    
            sleep(2);
    
            printf("server_routine(): client_fd: %d\n", clientfd);
            pthread_create(&client->thread, NULL, server_client_routine, client_args);
    
            pthread_mutex_unlock(&server_arg->clients->lock);
    
            close(clientfd);
        }
    
        printf("Server thread returning.\n");
    
        freeaddrinfo(res);
    
        return NULL;
    }
    
    server_client_list_t* server_client_array_init(int size) {
        server_client_list_t* client_array = malloc(sizeof(server_client_list_t));
    
        pthread_mutex_init(&client_array->lock, NULL);
    
        client_array->root = NULL;
    
        return client_array;
    }
    
    void server_client_array_free(server_client_list_t* client_array, int free_clients) {
        if (client_array != NULL) {
            pthread_mutex_destroy(&client_array->lock);
    
            if (client_array->root != NULL && free_clients == 1) {
                server_client_t* iter = client_array->root;
                for (iter = client_array->root; iter != NULL; iter = iter->next) {
                    server_client_free(iter);
                }
            }
    
            free(client_array);
        }
    }
    
    void server_client_array_append(server_client_list_t* client_array, server_client_t* client) {
        if (client_array != NULL && client != NULL) {
            if (client_array->root != NULL) {
                server_client_t* iter = client_array->root;
                for (iter = client_array->root; iter->next != NULL; iter = iter->next);
                iter->next = client;
            }
            else {
                client_array->root = client;
            }
        }
    }
    
    void server_client_array_remove(server_client_list_t* client_array, server_client_t* client) {
        if (client_array != NULL && client != NULL) {
            if (client_array->root == client) {
                client_array->root == NULL;
            }
            else {
                server_client_t* iter = client_array->root;
    
                while (iter) {
                    server_client_t* prev;
                    if ((iter = (prev = iter->next)) == client) {
                        prev->next = iter->next;
                    }
                }
            }
        }
    }
    
    server_client_t* server_client_init(int sock_desc) {
        server_client_t* client = malloc(sizeof(server_client_t));
    
        pthread_mutex_init(&client->lock, NULL);
    
        client->sensor_alarm = NULL;
        client->sock_desc = sock_desc;
        client->next = NULL;
        client->cont = 1;
    
        return client;
    }
    
    void server_client_free(server_client_t* client) {
        printf("I am freeing a client!\n");
    
        client->cont = 0;
        
        pthread_join(client->thread, NULL);
        pthread_mutex_destroy(&client->lock);
    
        free(client);
    }
    And the couple of lines used to test the server using Python:
    Code:
    import socket
    
    def getsocket():
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        return s
    
    def connect_tcp(host, port, s):
        if s == 0: s = getsocket()
        s.connect((host, port))
        return s
    The above is only the server code, and does not compile standalone.

    This is a link to the entire program, compilable in Linux: http://download.florianh.nl/via.tar.gz

    (If you need any more source code posted, please tell me)

  2. #2
    Registered User
    Join Date
    Jul 2008
    Posts
    38
    So i managed to create a much smaller program with threads and works just fine.

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <pthread.h>
    
    const char* message = "Hello, World!\n";
    int message_length = 14; // I counted.
    
    void* file_handler(void* args) {
        int* fd = args;
        
        puts("We are now in the thread.");
        
        printf("File descriptor is: %d\n", *fd);
        printf("Size of message is: %d\n", message_length);
        
        if (*fd > -1) {
            puts("Writing to the file...");
        
            if (write(*fd, message, message_length) == -1) {
                perror("Could not write the message to file");
            }
            
            close(*fd);
        }
        else {
            puts("File descriptor seems invalid.");
        }
    } 
    
    int main (int argc, char* argv[]) {
        puts("Opening the file...");
    
        int fd = open("test.txt", O_WRONLY | O_CREAT);
    
        printf("File descriptor is: %d\n", fd);
    
        if (fd != -1) {
            int ret = 0;
            if ((ret = write(3, message, message_length)) == -1) {
                perror("Could not write the message to file");
            }
            else {
                printf("Wrote %d bytes to file.\n", ret);
            }
        }
        else {
            perror("Could not open file");
        }
    
        pthread_t file_handler_thread;
        
        puts("Creating thread...");
        
        pthread_create(&file_handler_thread, 0, file_handler, &fd);
    
        puts("Waiting for thread to terminate...");
    
        pthread_join(file_handler_thread, 0);
    
        return 0;
    }
    Compiled with:
    Code:
    gcc -g -std=c99 -D_POSIX_C_SOURCE=200112L main.c -lpthread -o main

    Output of the program:
    Code:
    Opening the file...
    File descriptor is: 3
    Wrote 14 bytes to file.
    Creating thread...
    We are now in the thread.
    File descriptor is: 3
    Size of message is: 14
    Writing to the file...
    Waiting for thread to terminate...
    test.txt:
    Code:
    Hello, World!
    Hello, World!
    So i am still stumped about what the problem could be.

  3. #3
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    In the created thread, you do this when you're done
    close(client_args->client->sock_desc);


    In the calling thread, you do this just after creating the child thread.
    close(clientfd);

    The connection is closed as soon as the parent returns from spawning a new thread!
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  4. #4
    Registered User
    Join Date
    Jul 2008
    Posts
    38
    Quote Originally Posted by Salem View Post
    In the created thread, you do this when you're done
    close(client_args->client->sock_desc);


    In the calling thread, you do this just after creating the child thread.
    close(clientfd);

    The connection is closed as soon as the parent returns from spawning a new thread!
    Well... i... uhm...

    *reasserts himself*

    Thank you Salem, i will examine my code closer from now on, and no longer bother the good people of this board with my shameful mistake.

Popular pages Recent additions subscribe to a feed