3

I have found this Sockets tutorial http://www.binarytides.com/socket-programming-c-linux-tutorial/ and I am having trouble with the last example. It is a threaded server using sockets and pthreads.

The code compiles fine but it does not work as expected. I suspect the pthread_join call to be the culprit, but I am not sure.

I have tried compiling and running the server and client both on Cygwin (32-bit) and Alpine Linux (32-bit) both with GCC 4.8.3 and 4.8.2. The problem is the same in both environments.

I have read the pthreads pthread_join documentation at http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_join.html but I can't seem to find anything wrong with the call to pthread_join in the server code.

Updated server, client and output.

Server code:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

void *connection_handler(void *);

int main(int argc, char *argv[])
{
    int socket_desc, new_socket, c;
    struct sockaddr_in server, client;
    char *message;

    // Create socket
    socket_desc = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_desc == -1)
    {
        printf("Could not create socket");
    }

    // Prepare the sockaddr_in structure
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(2000);

    // Bind
    if (bind(socket_desc, (struct sockaddr *) &server, sizeof(server)) < 0)
    {
        puts("bind failed");
        return 1;
    }
    puts("bind done");

    // Listen
    listen(socket_desc, 3);

    // Accept and incoming connection
    puts("Waiting for incoming connections...");
    c = sizeof(struct sockaddr_in);

    while ((new_socket = accept(socket_desc, (struct sockaddr *) &client, (socklen_t*)&c)) )
    {
        puts("Connection accepted");

        // Reply to the client
        message = "Hello Client, I have received your connection. And now I will assign a handler for you\n";
        write(new_socket, message, strlen(message));

        pthread_t sniffer_thread;

        if (pthread_create(&sniffer_thread, NULL, connection_handler, (void*) new_socket) < 0)
        {
            perror("could not create thread");
            return 1;
        }

        // Now join the thread , so that we don't terminate before the thread
        // pthread_join(sniffer_thread, NULL);
        puts("Handler assigned");
    }

    if (new_socket < 0)
    {
        perror("accept failed");
        return 1;
    }
    return 0;
}

/*
 * This will handle connection for each client
 * */
void *connection_handler(void *socket_desc)
{
    // Get socket descriptor
    int sock = (int)socket_desc;
    int read_size;
    char *message, client_message[2000]; 

    // Send some messages to the client
    message = "Greetings! I am your connection handler\n";
    write(sock, message, strlen(message) + 1);

    message = "Now type something and i shall repeat what you type\n";
    write(sock, message, strlen(message) + 1);

    // Receive a message from client
    read_size = recv(sock, client_message, 1999, 0); 

    if (read_size > 0) 
    {
        client_message[read_size] = 0; 
    }
    else
    {
        puts("recv failed");
    }

    //while(read_size > 0 )
    //{
        // Send the message back to client
        write(sock, client_message, strlen(client_message) + 1);
    //}

    if (read_size == 0)
    {
        puts("Client disconnected");
        fflush(stdout);
    }
    else if (read_size == -1)
    {
        perror("recv failed");
    }

    close(sock);

    return 0;
}

Client code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int arc, char *argv[])
{
    int socket_desc;
    struct sockaddr_in server;
    char *message, server_reply[2000];

    // Create socket
    socket_desc = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_desc == -1)
    {
        printf("Could not create socket");
    }

    server.sin_addr.s_addr = inet_addr("127.0.0.1");
    server.sin_family = AF_INET;
    server.sin_port = htons(2000);

    // Connect to remote server
    if (connect(socket_desc, (struct sockaddr *) &server, sizeof(server)) < 0)
    {
        puts("connect error");
        return 1;
    }

    puts("Connected\n");

    // Send some data
    message = "Hello server!";
    if (send(socket_desc, message, strlen(message), 0) < 0)
    {
        puts("Send failed");
        return 1;
    }
    puts("Data Send\n");

    int length = 0;
    do {
        // Receive a reply from the server
        length = recv(socket_desc, server_reply, 1999, 0); 

        if (length > 0)
        {
            server_reply[length] = 0; 
        }
        else
        {
            puts("Nothing to recieve");
            close(socket_desc);
            return 1;
        }

        puts("Reply received\n");
        puts(server_reply);

    } while (length > 0);

    return 0;
}

Output from server:

bind done
Waiting for incoming connections...
Connection accepted
Test 1
Segmentation fault (core dumped)

Output from client:

Connected
Data Send
Reply received
Hello Client , I have received your connection. And now I will assign a handler for you
recv failed

It looks like the main thread returns/finishes before the sniffer_thread.

Any help is appriciated.

sigjuice
  • 28,661
  • 12
  • 68
  • 93
user1359448
  • 1,539
  • 3
  • 14
  • 23
  • 1
    First thing I do with C network code is hunt for calls to strlen(). Sure enough, 'strlen(client_message)' is called on a buffer that is not null-terminated. – Martin James Sep 30 '14 at 17:50
  • 1
    Take the value returned from the recv() call, (something you essentially ignore ATM), and use it to shove a null at the end of the received data. That will stop strlen() from segfaulting by running off the end of the buffer. That is likely not the only problem, however. – Martin James Sep 30 '14 at 17:53
  • 1
    'puts(server_reply);' has the same issue on the client - it needs a null-terminator and the automatic-storage buffer 'server_reply' is not guaranteed to have one at the end of the data. – Martin James Sep 30 '14 at 17:56
  • 1
    If you want to send the null-termiinator on 'strings', lines like 'write(sock , message , strlen(message));' will not send it - you need to add 1 to the int returned by strlen(). – Martin James Sep 30 '14 at 17:57
  • Always ever initialise all variables. Always and ever (At least during the debugging phase) check all system calls for all possible values/ranges they might return. – alk Sep 30 '14 at 17:58
  • 2
    BTW - don't feel bad about it. 99% of all C network code posted on SO has issues with strlen/printf/puts etc. null-terminations and ignoring the result of system calls, (especially recv). – Martin James Sep 30 '14 at 18:03
  • 1
    OK, I just looked at the server/client examples on the site. Remove the link from your history, bookmarks and your brain. Never look at it again. – Martin James Sep 30 '14 at 18:15
  • 1
    Oh - and get rid of the 'pthread_join' for now - just comment it out. It will stop the server accept loop from accepting any more clients. The while loop wil stop termination of the thread calling it, (ie. the one that runs main). – Martin James Sep 30 '14 at 18:18
  • Haha, noted. i have implemented the changes you suggested, but i dont understand the part about shoving a null at the end of the recieved data, recv return the length of the recieved data. – user1359448 Sep 30 '14 at 18:20
  • 1
    @user1359448 - yes, and that is the position in the buffer where the null must go:) No need to zero the whole 2K buffer first, just put a null at the end of what was received - as returned by recv, (assuming not -1, (error, in which case get errno and print it out), or 0, (connection closed by remote peer - your cue to clean up and exit the thread). – Martin James Sep 30 '14 at 18:24
  • @user but recv doesn't automatically insert '\0' into the end of the data read – 4pie0 Sep 30 '14 at 18:31
  • 1
    int length=recv(socket_desc, server_reply , 1999 , 0); if(length>0) server_reply[length]=0 else {error handling}; – Martin James Sep 30 '14 at 18:42
  • 1
    Note: '1999' to leave space for the null if 1999 chars are received. – Martin James Sep 30 '14 at 18:43
  • @Martin James Thanks for your great help, i think i have implemented all the changes to the code you have suggested, and i have updated the code and output above, but i still get the exact same behavior as before. So i must be doing something wrong. – user1359448 Sep 30 '14 at 18:58
  • 1
    OK, now for the other stuff. mallocing one byte is not enough space to store a typical socket descriptor handle/fd :'On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket'. You need 4/8 bytes to store it. Typically, a void* is the same size as an int, so you could probably just cast it: '(void*)new_socket' for the pthread_create call and just dump the malloc entirely. It the moment, it's working by UB. – Martin James Sep 30 '14 at 20:01
  • 1
    'Test 3' needs 'strlen(message)+1', just like Test 2. Also, there is no need to add the explicit '\0 at the end of the literal strings. – Martin James Sep 30 '14 at 20:05
  • 1
    In Test 4, get rid of 'strlen(client_message)' and replace it with 'read_size'. – Martin James Sep 30 '14 at 20:07
  • 1
    'free(socket_desc);' - OK, 'cos you originally malloced it, but you must close the 'sock' as well since it is an OS resource returned by the accept() call. – Martin James Sep 30 '14 at 20:11
  • 1
    OK, now for the client. It is missing a while loop so that recv() can be called more than once, so it just returns after getting the first 'Hello Client...' data. – Martin James Sep 30 '14 at 20:24
  • 1
    In 'puts("recv failed");' block, you need to close the socket_desc and break out of the loop, (the one you don't have yet:). – Martin James Sep 30 '14 at 20:34
  • @MartinJames I have implemented all the changes you suggested on the server and client, a lot of it makes perfect sense ;) But im getting a segfault now in the server. I have updated the client, server code above and the output from both. Thanks for your help – user1359448 Oct 01 '14 at 12:53
  • lol - Ok, you took on my suggestion of passing the new_socket directly in the pthread_create call by means of casting it to a void *, but you didn't fix the other end - the thread still thinks it's a malloced pointer. You need to fix the 'int sock = *(int*)socket_desc;' by simply casting back to an int: 'int sock=int(socket_desc);'. . Also, you no longer need to free it when the thread exits, just close it. – Martin James Oct 01 '14 at 13:02
  • @MartinJames I have updated the server and client code, and it seems to work now. Will you compile your comments into an answer? :) – user1359448 Oct 08 '14 at 07:16

1 Answers1

2

You experience undefined behavior because you call strlen on a string that is not terminated with '\0'. recv does not place a null terminator at the end of the string.

char *message , client_message[2000];
//...
//Receive a message from client
while( (read_size = recv(sock , client_message , 2000 , 0)) > 0 )
{
//Send the message back to client
write(sock , client_message , strlen(client_message));
                                     ^^^^^^^^^^^^^^
                                     not null terminated

You should use the return value of recv to check for size of data read.

4pie0
  • 29,204
  • 9
  • 82
  • 118
  • 1
    Not correct. The literal string does have a null-terminator. The issue is that a) it will not be sent to the peer and b) even if it is, it may not be read by a single recv() call at the other end. – Martin James Sep 30 '14 at 17:59
  • @MartinJames corrected, I meant a call to strlen(client_message) thank you – 4pie0 Sep 30 '14 at 18:14