【问题标题】:multi-threaded file transfer with socket使用套接字进行多线程文件传输
【发布时间】:2016-09-26 14:59:03
【问题描述】:

我正在尝试在 C 中创建一个多线程的服务器-客户端文件传输系统。有些客户端将发送或列出或做一些其他选择(在你可以看到的开关情况下)和一个服务器存储文件和服务很多客户。

据我所知,多线程意识形态真的很难。它需要太多的经验而不是知识。我已经在这个项目上工作了一个多星期,但我无法解决问题。

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

我的重要问题是关于多线程。我无法连接多个客户端。我已经从 a 到 z 阅读了很多次代码,但我真的无法捕捉到我的错误并被卡住了。

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

感谢您的帮助!


服务器.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);
}

【问题讨论】:

  • PORTSIZE = 5 对于9999 以上的端口是不够的。
  • 不可避免地,无法正确和完整地处理从recv()返回的结果。在不能保证空终止的 char 数组上滥用 C 'string' 函数,无法正确处理 TCP 的八位字节流性质。
  • 在概念上,主要问题是:在服务器的接受循环中,代码在接受客户端连接后生成一个线程,然后等待加入该线程。只有在此之后,才会完成下一次接受的调用。这将客户端连接序列化,一个接一个。让连接线程分离运行。此外,您还想修复/清理所有其他较大或较小的错误和不准确的部分。 (参见 Martin 对其中一些的评论)。
  • 总而言之,我的建议是第一次编写单线程服务器/客户端对,以了解如何正确处理套接字。让这段代码被审查,然后才去寻找更复杂的多线程方法。
  • 为什么互斥锁几乎围绕着 'forClient()' 的整个运行?这是弄巧成拙。你为什么要无缘无故地锁定所有其他的instabces?

标签: c linux multithreading sockets


【解决方案1】:

我修复了其他人提到的大部分错误。

让多线程/多客户端工作的关键点:

消除互斥体。

将以前由socket_index 索引的所有数组合并到一个新的“控制”结构中。主线程为结构做一个malloc,填充它,并将结构指针传递给线程。

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

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

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

注意:我之前回答过一个类似的问题:executing commands via sockets with popen() 特别注意“标志”字符的讨论。

无论如何,这是代码。我已经清理了它,注释了错误和修复,并用#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。但是,它不会返回0[除非套接字已经关闭]。

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

解决方案是让客户端告诉服务器文件大小。因此,客户端不再执行 sendfilename,而是执行 sendfilesize,filename

服务器现在将解码此文件大小并在recv 缓冲区中拆分文件名。现在,服务器的读/写循环将保持对仍需要读取多少字节的计数,当剩余计数为零时循环停止。

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

【讨论】:

  • 我刚刚发布了针对信号处理和多文件问题的修复。请注意,我是从您在此处对 client.c 的更新工作,而不是从您刚刚在评论中添加的链接。但是,我只是对它们进行了比较,它们很小。我对修复所做的更改比您对新代码所做的更改更多(例如portCleaner)。在研究了我的新代码后,我强烈建议将你的新代码添加到我的代码中,而不是相反
猜你喜欢
  • 1970-01-01
  • 2012-03-08
  • 1970-01-01
  • 1970-01-01
  • 2020-09-30
  • 1970-01-01
  • 2020-01-25
  • 1970-01-01
相关资源
最近更新 更多