带套接字的多线程文件传输 [英] multi-threaded file transfer with socket

查看:76
本文介绍了带套接字的多线程文件传输的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在C语言中创建多线程服务器-客户端文件传输系统.有些客户端将发送或列出或进行其他选择(在切换的情况下,您可以看到),而服务器则存储文件和为很多客户服务.

就我所知,多线程意识形态确实非常困难.它需要太多的经验而不是知识.我已经在这个项目上工作了一个多星期,但我一直无法解决这些问题.

有4个选择:第一个是在其目录中列出客户端的本地文件,第二个是在客户端和服务器之间传输的列表文件,第三个是从用户读取文件名并将文件复制到服务器的目录中.

我在这里的关键问题是关于多线程的问题.我无法连接多个客户端.我已经读过a到z堆的代码,但是我确实无法捕捉到我的错误而陷入困境.

另一个问题是,当捕获到SIGINT时,客户端将结束,但是,例如,在按ctrl-c选择列表文件后,它不会停止.服务器文件也有同样的问题.与客户端捕获相比,这更麻烦,因为当服务器获取SIGINT时,客户端将分别与服务器断开连接.

感谢您的帮助!


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

解决方案

我修复了其他人提到的大多数错误.

使多线程/多客户端正常工作的要点:

消除互斥锁.

将先前由socket_index索引的所有数组合并到一个新的"control"结构中.主线程为该结构执行malloc,将其填充,然后将struct指针传递给该线程.

从主线程中删除pthread_join并运行所有分离的线程. main不再对客户端线程进行任何关闭/清理.

客户端线程现在执行关闭/清理/释放.

尽管如此,服务器/客户端代码仍然需要一些工作,但是现在,它可以与多个同时的客户端连接一起工作,我认为这是主要问题.

注意:我之前已经回答过类似的问题:

无论如何,这是代码.我已经清理了它,注释了错误和修复,并用#if 0包装了旧的/新的代码.请注意,某些旧"代码并非纯粹是原始代码,而是我的过渡版本. [请原谅免费的样式清理]:


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


更新:

我已经解决了连接中断问题,但是仍然出现信号.我在文件发送和信号处理上又留下了两个问题

我已经对客户端信号处理进行了重新设计,以使其能够按预期工作(即打印消息并停止客户端).

我还解决了只能发送一个文件的问题.要了解这一点,请同时考虑客户端和服务器的操作.

要发送文件,客户端会提示输入文件名,并使用文件名进行send调用.然后,它打开文件并执行读/写循环,以将文件数据发送到服务器[然后关闭文件描述符].

要接收文件,服务器将执行recv调用以获取文件名.然后,它将打开文件(用于输出),并执行读/写操作,以将套接字中的数据写入文件中(然后关闭文件描述符).

这是问题所在:服务器的读/写循环的终止条件是等待,直到read(connect_socket,...)调用返回0.但是,它将返回零[除非套接字已关闭].

因此,现在客户端执行send调用以发送 second 文件名.但是,用于此目的的数据(而不是进入服务器的recv调用)仅是read缓冲区的一部分.也就是说,第二个文件名只会作为数据附加到第一个文件中.

解决方案是让客户端告诉服务器文件大小是多少.因此,客户端不再执行filenamesend,而是执行filesize,filename

send.

服务器现在将解码此文件大小,并在recv缓冲区中分割文件名.现在,服务器的读/写循环将保持一个计数,该计数仍然需要读取多少字节,并且当剩余计数达到零时,循环将停止.

还有一个或两个其他小错误.我已经用错误修复和注释更新了client.c和server.c

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

解决方案

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

这篇关于带套接字的多线程文件传输的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆