2016-05-28 65 views
1

我想在C中製作一個多線程的服務器 - 客戶端文件傳輸系統。有客戶端會發送或列出或做一些其他選擇(在開關的情況下,你可以看到)和一個存儲這些文件併爲很多客戶提供服務的服務器。多線程文件傳輸與套接字

就我所見,多線程意識形態真的很難。它需要太多的經驗而不是知識。我已經在這個項目上工作了一個多星期,而且我一直沒能解決問題。

有4種選擇:第一種是在客戶端的目錄中列出客戶端的本地文件,第二種是在客戶端和服務器之間傳輸的列表文件,第三種是從用戶讀取文件名並將文件複製到服務器目錄中。

我的重要問題是關於多線程。我無法連接多個客戶端。我已經閱讀了從一堆到幾堆的代碼,但是我確實無法捕捉到我的錯誤並被卡住了。

另一個問題是客戶端將在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); 
} 
+1

'PORTSIZE = 5'對於'9999'之上的端口是不夠的。 – alk

+2

不可避免地,未能正確完整地處理從recv()返回的結果。在char數組上不使用C'字符串'函數,但不能保證以null結尾,不能正確處理TCP的octet-streaming特性。 –

+0

你認爲這個'sizeof BUFSIZ'做什麼?-S – alk

回答

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

UPDATE:

我已經解決了我的連接中斷問題,但信號仍在發生。我多次發送文件發送和信號處理兩個問題

我重寫了客戶端信號處理,使其按預期工作[即打印消息並停止客戶端]。

我也修復了只能發送一個文件的問題。要理解這一點,請考慮客戶端和服務器的操作。

要發送文件,客戶端會提示輸入文件名,send會調用其中的文件名。然後它打開文件並執行讀/寫循環以將文件數據發送到服務器[然後關閉文件描述符]。

要接收文件,服務器會調用recv來獲取文件名。然後它打開文件[輸出]並執行讀/寫操作,將套接字中的數據寫入文件[然後關閉文件描述符]。

這裏的問題是:對服務器的讀/寫循環的終止條件是要等到read(connect_socket,...)調用返回0。但是,它將歸零[除非套接字已關閉。

所以,現在客戶端做了一個send調用發送第二個文件名。但是,這些數據,而不是進入服務器的recv調用,將只是read緩衝區的一部分。也就是說,第二個文件名將僅作爲數據附加到第一個文件

解決方案是讓客戶端告訴服務器文件大小是多少。因此,而不是在客戶端做filenamesend,它現在filesize,filenamesend

一個服務器現在將解碼此文件大小,並在recv緩衝分裂出去的文件名。現在,服務器的讀/寫循環將保持需要讀取多少字節的計數,並在剩餘計數爲零時停止循環。

有一個或兩個其他小錯誤。我更新了client.c和server.c以及錯誤修復和註釋

+0

我剛剛發佈了修復信號處理和多文件問題。請注意,我正在從您的更新工作到client.c,而不是您剛纔在評論中提供的鏈接。但是,我只是對他們做了差異,他們很小。我對修補程序進行了更多的修改,而不是對新代碼進行修改(例如'portCleaner')。在學習了我的新代碼之後,我強烈建議將你的新代碼添加到我的代碼中,而不是以其他方式加入。 –

+0

我非常感謝你,以後你已經救了我很多先生。現在我正在努力 – snr

+0

真的非常感謝你,埃斯泰爵士我已經完成了我的決賽,如果你沒有幫助我,我可能會失敗。如果可能的話,我希望能見到你是rara avis。問候,你的學生Soner。 – snr