1

I am trying to make a multi-threaded server-client file transfer system in C. There are clients which will send or list or do some other choice (in a switch case you can see) and a server storing the files and serving a lot of clients.

Multi-thread ideology is really difficult as far as I can see. It needs too much experience instead of knowledge. I have been working on the project for more than one week and I haven't been able to get on top of the problems.

There are 4 choices: first one is lists local files of client in its directory, second one is list files which are transferred between the client and server, third reading filename from user and copy the file into server's directory.

My vital issue here is about multi-threading. I cannot connect multiple clients. I have read the code from a to z heaps of times but I really can't catch my errors and am stuck.

The other issue is that the client will end when the SIGINT is caught, but, for instance, after choosing list files when press ctrl-c it doesn't stop. Same issue for the server file as well. It is more troublesome compared to the client's catching because when server gets SIGINT, clients will be disconnected respectively from the server.

Thanks for your helps!


server.c

/*
 Soner
 Receive a file over a socket.

 Saves it to output.tmp by default.

 Interface:

 ./executable [<port>]

 Defaults:

 - output_file: output.tmp
 - port: 12345
 */

#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>

#include <pthread.h>

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;

enum { PORTSIZE = 5 };

void* forClient(void* ptr);
void sig_handler(int signo)
{
    if (signo == SIGINT)
        printf("!!  OUCH,  CTRL - C received  by server !!\n");
}

int main(int argc, char **argv) {
    struct addrinfo hints, *res;
    int enable = 1;
    int filefd;
    int server_sockfd;
    unsigned short server_port = 12345u;
    char portNum[PORTSIZE];

    socklen_t client_len[BUFSIZ];
    struct sockaddr_in client_address[BUFSIZ];
    int client_sockfd[BUFSIZ];
    int socket_index = 0;

    pthread_t threads[BUFSIZ];

    if (argc != 2) {
        fprintf(stderr, "Usage   ./server  <port>\n");
        exit(EXIT_FAILURE);
    }
    server_port = strtol(argv[1], NULL, 10);

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_INET;       //ipv4
    hints.ai_socktype = SOCK_STREAM; // tcp
    hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

    sprintf(portNum, "%d", server_port);
    getaddrinfo(NULL, portNum, &hints, &res);

    server_sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (server_sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(server_sockfd, SOL_SOCKET, (SO_REUSEPORT | SO_REUSEADDR), &enable, sizeof(enable)) < 0) {
        perror("setsockopt(SO_REUSEADDR) failed");
        exit(EXIT_FAILURE);
    }

    if (bind(server_sockfd, res->ai_addr, res->ai_addrlen) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    if (listen(server_sockfd, 5) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    fprintf(stderr, "listening on port %d\n", server_port);


    while (1) {
        client_len[socket_index] = sizeof(client_address[socket_index]);
        puts("waiting for client");
        client_sockfd[socket_index] = accept(
                               server_sockfd,
                               (struct sockaddr*)&client_address[socket_index],
                               &client_len[socket_index]
                               );
        if (client_sockfd[socket_index] < 0) {
            perror("Cannot accept connection\n");
            close(server_sockfd);
            exit(EXIT_FAILURE);
        }

        pthread_create( &threads[socket_index], NULL, forClient, (void*)client_sockfd[socket_index]);

        if(BUFSIZ == socket_index) {
            socket_index = 0;
        } else {
            ++socket_index;
        }

        pthread_join(threads[socket_index], NULL);
        close(filefd);
        close(client_sockfd[socket_index]);
    }
    return EXIT_SUCCESS;
}
void* forClient(void* ptr) {
    int connect_socket = (int) ptr;
    int filefd;
    ssize_t read_return;
    char buffer[BUFSIZ];
    char *file_path;
    char receiveFileName[BUFSIZ];

    int ret = 1;
    // Thread number means client's id
    printf("Thread number %ld\n", pthread_self());
    pthread_mutex_lock( &mutex1 );

    // until stop receiving go on taking information
    while (recv(connect_socket, receiveFileName, sizeof(receiveFileName), 0)) {

        file_path = receiveFileName;

        fprintf(stderr, "is the file name received? ?   =>  %s\n", file_path);

        filefd = open(file_path,
                      O_WRONLY | O_CREAT | O_TRUNC,
                      S_IRUSR | S_IWUSR);
        if (filefd == -1) {
            perror("open");
            exit(EXIT_FAILURE);
        }
        do {
            read_return = read(connect_socket, buffer, BUFSIZ);
            if (read_return == -1) {
                perror("read");
                exit(EXIT_FAILURE);
            }
            if (write(filefd, buffer, read_return) == -1) {
                perror("write");
                exit(EXIT_FAILURE);
            }
        } while (read_return > 0);
    }

    pthread_mutex_unlock( &mutex1 );

    fprintf(stderr, "Client dropped connection\n");
    pthread_exit(&ret);
}

client.c

/*
 Soner
 Send a file over a socket.

 Interface:

 ./executable [<sever_hostname> [<port>]]

 Defaults:

 - server_hostname: 127.0.0.1
 - port: 12345
 */

#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <signal.h>

#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>                      /* getprotobyname */
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>

// NOTE/BUG: this didn't provide enough space for a 5 digit port + EOS char
#if 0
enum { PORTSIZE = 5 };
#else
enum { PORTSIZE = 6 };
#endif

void
sig_handler(int signo)
{
    if (signo == SIGINT)
        printf("!!  OUCH,  CTRL - C received on client  !!\n");
}

int
main(int argc, char **argv)
{
    struct addrinfo hints,
    *res;
    char *server_hostname = "127.0.0.1";
    char file_path[BUFSIZ];
    char *server_reply = NULL;
    char *user_input = NULL;
    char buffer[BUFSIZ];
    int filefd;
    int sockfd;
    ssize_t read_return;
    struct hostent *hostent;
    unsigned short server_port = 12345;
    char portNum[PORTSIZE];
    char remote_file[BUFSIZ];
    int select;
    char *client_server_files[BUFSIZ];
    int i = 0;
    int j;

    // char filename_to_send[BUFSIZ];

    if (argc != 3) {
        fprintf(stderr, "Usage   ./client  <ip>  <port>\n");
        exit(EXIT_FAILURE);
    }

    server_hostname = argv[1];
    server_port = strtol(argv[2], NULL, 10);

    /* Prepare hint (socket address input). */
    hostent = gethostbyname(server_hostname);
    if (hostent == NULL) {
        fprintf(stderr, "error: gethostbyname(\"%s\")\n", server_hostname);
        exit(EXIT_FAILURE);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_INET;          // ipv4
    hints.ai_socktype = SOCK_STREAM;    // tcp
    hints.ai_flags = AI_PASSIVE;        // fill in my IP for me

    sprintf(portNum, "%d", server_port);
    getaddrinfo(NULL, portNum, &hints, &res);

    sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    /* Do the actual connection. */
    if (connect(sockfd, res->ai_addr, res->ai_addrlen) == -1) {
        perror("connect");
        return EXIT_FAILURE;
    }

    while (1) {
        if (signal(SIGINT, sig_handler)) {
            break;
        }

        puts("connected to the server");
        puts("-----------------");
        puts("|1 - listLocal| \n|2 - listServer| \n|3 - sendFile| \n|4 - help| \n|5 - exit| ");
        puts("-----------------");
        while (1) {
            scanf("%d", &select);

            switch (select) {
                case 1: // list files of client's directory
                    system("find . -maxdepth 1 -type f | sort");
                    break;

                case 2: // listServer
                    puts("---- Files btw Server and the Client ----");
                    for (j = 0; j < i; ++j) {
                        puts(client_server_files[j]);
                    }
                    break;

                case 3: // send file
                    memset(file_path, 0, sizeof file_path);
                    scanf("%s", file_path);

                    memset(remote_file, 0, sizeof remote_file);
                    // send file name to server
                    sprintf(remote_file, "%s", file_path);
                    send(sockfd, remote_file, sizeof(remote_file), 0);

                    filefd = open(file_path, O_RDONLY);
                    if (filefd == -1) {
                        perror("open send file");
                        //exit(EXIT_FAILURE);
                        break;
                    }

                    while (1) {
                        read_return = read(filefd, buffer, BUFSIZ);
                        if (read_return == 0)
                            break;
                        if (read_return == -1) {
                            perror("read");
                            //exit(EXIT_FAILURE);
                            break;
                        }
                        if (write(sockfd, buffer, read_return) == -1) {
                            perror("write");
                            //exit(EXIT_FAILURE);
                            break;
                        }
                    }

                    // add files in char pointer array
                    client_server_files[i++] = file_path;

                    close(filefd);
                    break;

                case 5:
                    free(user_input);
                    free(server_reply);
                    exit(EXIT_SUCCESS);

                default:
                    puts("Wrong selection!");
                    break;
            }

        }
    }

    free(user_input);
    free(server_reply);
    exit(EXIT_SUCCESS);
}
  • 1
    `PORTSIZE = 5` is not sufficient for ports above `9999`. – alk May 28 '16 at 09:58
  • 2
    Inevitably, failure to correctly and completely handle the result returned from recv(). misuse of C 'string' functions on char arrays that are not guaranteed null-terminated, failure to correctly handle the octet-streaming nature of TCP. – Martin James May 28 '16 at 10:02
  • What do you expect this `sizeof BUFSIZ` to do?-S – alk May 28 '16 at 10:04
  • 2
    *Conceptually*, the main issue is: In the server's accept-loop the code spawns off a thread after accepting a client connection and then waits to join the thread. And only after this, the next call to accept is done. This serialises client connections, to be one after the other. Let the connection threads run detached. Also you want to fix/clean up all other larger or smaller bits and pieces being wrong and inaccurate. (See Martin's comment on some of those). – alk May 28 '16 at 10:12
  • 1
    All in all my advise would be to 1st code a single threaded server/client pair, to learn how to correctly handle sockets. Let this code be reviewed, and only then go for the definitely more complex multithreaded approach. – alk May 28 '16 at 10:18
  • The main server thread starts a new thread with `pthread_create`. *Then it waits for the thread to finish* with `pthread_join`. If you don't want to wait for the thread to finish then don't wait for the thread to finish. – user253751 May 28 '16 at 10:20
  • 1
    Why the mutex lock round almost the entire run of 'forClient()'?? It's self-defeating. Why bother with a thread is you are going to lock out all the other instabces for no reason? – Martin James May 28 '16 at 10:37

1 Answers1

1

I fixed most of the bugs that others have mentioned.

Key points to get multithread/multiclient working:

Eliminate mutex.

Consolidate all arrays previously indexed by socket_index into a new "control" struct. main thread does a malloc for the struct, fills it in, and passes off the struct pointer to the thread.

Remove pthread_join from main thread and run all threads detached. main no longer does any close/cleanup for the client thread.

client thread now does the close/cleanup/free.

Even with all that, the server/client code still needs some work, but now, it does work with multiple simultaneous client connections which I believe was the main issue.

Note: I've answered a similar question before: executing commands via sockets with popen() Pay particular attention to the discussion of the "flag" character.

Anyway, Here's the code. I've cleaned it, annotated the bugs and fixes and wrapped the old/new code with #if 0. Note that some of the "old" code isn't purely original code, but an interim version of mine. [please pardon the gratuitous style cleanup]:


server.c:

/*
 Soner
 Receive a file over a socket.

 Saves it to output.tmp by default.

 Interface:

 ./executable [<port>]

 Defaults:

 - output_file: output.tmp
 - port: 12345
 */

#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>                      /* getprotobyname */
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>

#include <pthread.h>

// NOTE: this consolidates four arrays that were indexed by socket_index
struct client {
    socklen_t client_len;
    struct sockaddr_in client_address;
    int client_sockfd;
    pthread_t thread;
};

// NOTE: no longer used/needed for true multiclient
#if 0
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
#endif

// NOTE/BUG: this didn't provide enough space for a 5 digit port + EOS char
#if 0
enum { PORTSIZE = 5 };
#else
enum { PORTSIZE = 6 };
#endif

void *forClient(void *ptr);

void
sig_handler(int signo)
{
    if (signo == SIGINT)
        printf("!!  OUCH,  CTRL - C received  by server !!\n");
}

int
main(int argc, char **argv)
{
    struct addrinfo hints,
    *res;
    int enable = 1;
    //int filefd;  // NOTE: this is never initialized/used
    int server_sockfd;
    unsigned short server_port = 12345u;
    char portNum[PORTSIZE];

    // NOTE: now all client related data is malloc'ed
#if 0
    int socket_index = 0;
#else
    struct client *ctl;
#endif

    if (argc != 2) {
        fprintf(stderr, "Usage   ./server  <port>\n");
        exit(EXIT_FAILURE);
    }
    server_port = strtol(argv[1], NULL, 10);

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_INET;          // ipv4
    hints.ai_socktype = SOCK_STREAM;    // tcp
    hints.ai_flags = AI_PASSIVE;        // fill in my IP for me

    sprintf(portNum, "%d", server_port);
    getaddrinfo(NULL, portNum, &hints, &res);

    server_sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (server_sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(server_sockfd, SOL_SOCKET, (SO_REUSEPORT | SO_REUSEADDR), &enable, sizeof(enable)) < 0) {
        perror("setsockopt(SO_REUSEADDR) failed");
        exit(EXIT_FAILURE);
    }

    if (bind(server_sockfd, res->ai_addr, res->ai_addrlen) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    if (listen(server_sockfd, 5) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    fprintf(stderr, "listening on port %d\n", server_port);

    // NOTE: we want the threads to run detached so we don't have to wait
    // for them to do cleanup -- the thread now does its own close/cleanup
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr,1);

    while (1) {
        // NOTE/BUG: using a fixed list, if you actually let threads detach,
        // you don't know which thread completes allowing its control struct
        // to be reused
        // the solution is to allocate a fresh one, fill it, pass it to the
        // thread and let the _thread_ do all the closes and cleanup
#if 0
        ctl = &control_list[socket_index];
#else
        ctl = malloc(sizeof(struct client));
        if (ctl == NULL) {
            perror("malloc");
            exit(EXIT_FAILURE);
        }
#endif

        ctl->client_len = sizeof(ctl->client_address);
        puts("waiting for client");

        ctl->client_sockfd = accept(server_sockfd,
            (struct sockaddr *) &ctl->client_address, &ctl->client_len);

        if (ctl->client_sockfd < 0) {
            perror("Cannot accept connection\n");
            close(server_sockfd);
            exit(EXIT_FAILURE);
        }

        // NOTE: we're running the threads detached now and we're passing down
        // extra information just in case the client loop needs it
#if 0
        pthread_create(&ctl->thread, NULL, forClient, ctl);
#else
        pthread_create(&ctl->thread, &attr, forClient, ctl);
#endif

#if 0
        if (BUFSIZ == socket_index) {
            socket_index = 0;
        }
        else {
            ++socket_index;
        }
#endif

        // NOTE/BUG: this is why you couldn't do multiple clients at the same
        // time -- you are doing a thread join
        // but you _had_ to because the main thread didn't know when a thread
        // was done with the control struct without the join
#if 0
        pthread_join(threads[socket_index], NULL);
        close(filefd);
        close(client_sockfd[socket_index]);
#endif
    }

    return EXIT_SUCCESS;
}

void *
forClient(void *ptr)
{
#if 0
    int connect_socket = (int) ptr;
#else
    struct client *ctl = ptr;
    int connect_socket = ctl->client_sockfd;
#endif
    int filefd;
    ssize_t read_return;
    char buffer[BUFSIZ];
    char *file_path;
    long long file_length;
    char receiveFileName[BUFSIZ];

    //int ret = 1;

    // Thread number means client's id
    printf("Thread number %ld\n", pthread_self());

    // NOTE: to run parallel threads, this prevents that
#if 0
    pthread_mutex_lock(&mutex1);
#endif

    // until stop receiving go on taking information
    while (recv(connect_socket, receiveFileName, sizeof(receiveFileName), 0)) {
        // NOTE/FIX2: now we have the client send us the file length so we
        // know when to stop the read loop below
        file_length = strtoll(receiveFileName,&file_path,10);

        if (*file_path != ',') {
            fprintf(stderr,"syntax error in request -- '%s'\n",
                receiveFileName);
            exit(EXIT_FAILURE);
        }
        file_path += 1;

        fprintf(stderr, "is the file name received? ?   =>  %s [%lld bytes]\n",
            file_path,file_length);

        // NOTE: if you want to see _why_ sending the length is necessary,
        // uncomment this line and the "unable to send two files" bug will
        // reappear
        //file_length = 1LL << 62;

        filefd = open(file_path,
            O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
        if (filefd == -1) {
            perror("open");
            exit(EXIT_FAILURE);
        }

        // NOTE/BUG2/FIX: now we only read up to what we're told to read
        // previously, we would keep trying to read, so on the _second_
        // send, our read call here would get the data that _should_ have
        // gone into the recv above
        // in other words, we'd lose synchronization with what the client
        // was sending us [and we'd put the second filename into the first
        // file as data at the bottom]
        for (;  file_length > 0;  file_length -= read_return) {
            read_return = BUFSIZ;
            if (read_return > file_length)
                read_return = file_length;

            read_return = read(connect_socket, buffer, read_return);
            if (read_return == -1) {
                perror("read");
                exit(EXIT_FAILURE);
            }
            if (read_return == 0)
                break;

            if (write(filefd, buffer, read_return) == -1) {
                perror("write");
                exit(EXIT_FAILURE);
            }
        }

        fprintf(stderr,"file complete\n");

        // NOTE/BUG: filefd was never closed
#if 1
        close(filefd);
#endif
    }

#if 0
    pthread_mutex_unlock(&mutex1);
#endif

    fprintf(stderr, "Client dropped connection\n");

    // NOTE: do all client related cleanup here
    // previously, the main thread was doing the close, which is why it had
    // to do the pthread_join
    close(connect_socket);
    free(ctl);

    // NOTE: this needs a void * value like below
#if 0
    pthread_exit(&ret);
#endif

    return (void *) 0;
}

client.c:

/*
 Soner
 Send a file over a socket.

 Interface:

 ./executable [<sever_hostname> [<port>]]

 Defaults:

 - server_hostname: 127.0.0.1
 - port: 12345
 */

#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <signal.h>

#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>                      /* getprotobyname */
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>

// NOTE/BUG: this didn't provide enough space for a 5 digit port + EOS char
#if 0
enum { PORTSIZE = 5 };
#else
enum { PORTSIZE = 6 };
#endif

// NOTE2: the "volatile" attribute here is critical to proper operation
volatile int signo_taken;

// NOTE/BUG2: don't use BUFSIZ when you really want something else
#define MAXFILES        1000

void
sig_handler(int signo)
{

    // NOTE/BUG2/FIX: doing printf within a signal handler is _not_ [AFAIK] a
    // safe thing to do because it can foul up the internal structure data of
    // stdout if the base task was doing printf/puts and the signal occurred
    // in the middle -- there are a number of other restrictions, such as
    // _no_ malloc, etc.

    // so, just alert the base layer and let it handle things when it's in a
    // "safe" state to do so ...
    signo_taken = signo;
}

int
main(int argc, char **argv)
{
    struct addrinfo hints,
    *res;
    char *server_hostname = "127.0.0.1";
    char file_path[BUFSIZ];
    char *server_reply = NULL;
    char *user_input = NULL;
    char buffer[BUFSIZ];
    int filefd;
    int sockfd;
    struct stat st;
    ssize_t read_return;
    struct hostent *hostent;
    unsigned short server_port = 12345;
    char portNum[PORTSIZE];
    char remote_file[BUFSIZ];
    int select;
    char *client_server_files[MAXFILES];
    int i = 0;
    int j;

    // char filename_to_send[BUFSIZ];

    if (argc != 3) {
        fprintf(stderr, "Usage   ./client  <ip>  <port>\n");
        exit(EXIT_FAILURE);
    }

    server_hostname = argv[1];
    server_port = strtol(argv[2], NULL, 10);

    /* Prepare hint (socket address input). */
    hostent = gethostbyname(server_hostname);
    if (hostent == NULL) {
        fprintf(stderr, "error: gethostbyname(\"%s\")\n", server_hostname);
        exit(EXIT_FAILURE);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_INET;          // ipv4
    hints.ai_socktype = SOCK_STREAM;    // tcp
    hints.ai_flags = AI_PASSIVE;        // fill in my IP for me

    sprintf(portNum, "%d", server_port);
    getaddrinfo(NULL, portNum, &hints, &res);

    sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    /* Do the actual connection. */
    if (connect(sockfd, res->ai_addr, res->ai_addrlen) == -1) {
        perror("connect");
        return EXIT_FAILURE;
    }

    // NOTE/FIX2: this only needs to be done once, since the desired action is
    // to [cleanly] stop the program
    signal(SIGINT, sig_handler);

    // NOTES:
    // (1) instead of using signo_taken as is done, below there are alternate
    //     ways to handle signals with sigsetjmp and siglongjmp
    // (2) but the main reason to _not_ do this is to prevent the handler
    //     from messing up a file transfer
    while (! signo_taken) {
        puts("connected to the server");
#if 0
        puts("-----------------");
        puts("|1 - listLocal| \n|2 - listServer| \n|3 - sendFile| \n|4 - help| \n|5 - exit| ");
        puts("-----------------");
#endif

        while (! signo_taken) {
            // NOTE: not a bug, but it helps the user to output the menu each
            // time
#if 1
            puts("-----------------");
            puts("|1 - listLocal| \n|2 - listServer| \n|3 - sendFile| \n|4 - help| \n|5 - exit| ");
            puts("-----------------");
#endif

            scanf("%d", &select);

            // NOTE: we should check this after _any_ call that requests user
            // input (e.g. scanf, fgets(...,stdin), etc.)
            if (signo_taken)
                break;

            switch (select) {
            case 1:                 // list files of client's directory
                system("find . -maxdepth 1 -type f | sort");
                break;

            case 2:                 // listServer
                puts("---- Files btw Server and the Client ----");
                for (j = 0; j < i; ++j) {
                    puts(client_server_files[j]);
                }
                break;

            case 3:                 // send file
                fputs("Enter filename: ",stdout);
                fflush(stdout);

                memset(file_path, 0, sizeof file_path);
                scanf("%s", file_path);

                if (signo_taken)
                    break;

                // NOTE/FIX: check the file _before_ sending request to server
                // and we [now] want to know the file length so we can send
                // that to the server so it will know when to stop receiving
#if 1
                filefd = open(file_path, O_RDONLY);
                if (filefd == -1) {
                    perror("open send file");
                    // exit(EXIT_FAILURE);
                    break;
                }

                // get the file's byte length
                if (fstat(filefd,&st) < 0) {
                    perror("stat send file");
                    // exit(EXIT_FAILURE);
                    close(filefd);
                    break;
                }
#endif

                // send file name to server
                memset(remote_file, 0, sizeof(remote_file));
#if 0
                sprintf(remote_file, "%s", file_path);
#else
                sprintf(remote_file, "%lld,%s",
                    (long long) st.st_size,file_path);
#endif
                send(sockfd, remote_file, sizeof(remote_file), 0);

                // NOTE/BUG2: this should be done above to _not_ confuse server
#if 0
                filefd = open(file_path, O_RDONLY);
                if (filefd == -1) {
                    perror("open send file");
                    // exit(EXIT_FAILURE);
                    break;
                }
#endif

                while (1) {
                    read_return = read(filefd, buffer, BUFSIZ);
                    if (read_return == 0)
                        break;

                    if (read_return == -1) {
                        perror("read");
                        // exit(EXIT_FAILURE);
                        break;
                    }

                    if (write(sockfd, buffer, read_return) == -1) {
                        perror("write");
                        // exit(EXIT_FAILURE);
                        break;
                    }
                }

                close(filefd);

                // add files in char pointer array
                // NOTE/BUG2: file_path gets overwritten, so we must save it
                // here
#if 0
                client_server_files[i++] = file_path;
#else
                if (i < MAXFILES)
                    client_server_files[i++] = strdup(file_path);
#endif

                puts("file complete");
                break;

            case 5:
                free(user_input);
                free(server_reply);
                exit(EXIT_SUCCESS);
                break;

            default:
                puts("Wrong selection!");
                break;
            }

        }
    }

    // NOTE/FIX2: we output this here when it's save to do so
    if (signo_taken)
        printf("!!  OUCH,  CTRL - C received on client  !!\n");

    free(user_input);
    free(server_reply);
    exit(EXIT_SUCCESS);
}

UPDATE:

I have solved my connection-interruption problem but signal is still occurring. I left two problems more times file sending and signal handling

I have reworked the client signal handling so that it works as expected [which is to print the message and stop the client].

I have also fixed the problem where only one file could be sent. To understand this, consider the actions of both client and server.

To send a file, client prompts for filename, does a send call with the filename in it. It then opens the file and does a read/write loop to send the file data to the server [and then closes the file descriptor].

To receive a file, server does a recv call to get the filename. It then opens the file [for output] and does a read/write to write the data from the socket to the file [and then closes the file descriptor].

Here is the problem: The termination condition for the server's read/write loop is to wait until the read(connect_socket,...) call returns 0. But, it will not return zero [unless the socket has been closed].

So, now the client does a send call to send the second filename. But, the data for this, instead of going into the server's recv call, will merely be part of the read buffer. That is, the second filename will just be appended to the first file as data.

The solution is to have the client tell the server what the file size is. So, instead of the client doing a send of filename, it now does a send of filesize,filename

The server will now decode this filesize and split off the filename in the recv buffer. Now, the server's read/write loop will maintain a count of how many bytes still need to be read and the loop stops when the remaining count hits zero.

There were one or two other minor bugs. I've updated both client.c and server.c with the bug fixes and annotations

Community
  • 1
  • 1
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • I just posted fixes for the signal handling and the multiple file problem. Note that I was working from your update to client.c here and not from the links you just put in your comment. But, I just did a diff on them and they're small. I made more changes for the fixes than you did for your new code (e.g. `portCleaner`). After studying my new code, I _highly_ recommend adding in your new code into mine rather than the other way around – Craig Estey May 29 '16 at 19:36