c - multi-threaded file transfer with socket -


i trying make multi-threaded server-client file transfer system in c. there clients send or list or other choice (in switch case can see) , server storing files , serving lot of clients.

multi-thread ideology difficult far can see. needs experience instead of knowledge. have been working on project more 1 week , haven't been able on top of problems.

there 4 choices: first 1 lists local files of client in directory, second 1 list files transferred between client , server, third reading filename user , copy file server's directory.

my vital issue here multi-threading. cannot connect multiple clients. have read code z heaps of times can't catch errors , stuck.

the other issue client end when sigint caught, but, instance, after choosing list files when press ctrl-c doesn't stop. same issue server file well. more troublesome compared client's catching because when server gets sigint, clients disconnected respectively server.

thanks helps!


server.c

/*  soner  receive file on socket.   saves output.tmp 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  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 ip 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 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 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);         }         {             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 file on 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: didn't provide enough space 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 = 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 ip 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);     }      /* 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 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 , client ----");                     (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 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 of bugs others have mentioned.

key points multithread/multiclient working:

eliminate mutex.

consolidate arrays indexed socket_index new "control" struct. main thread malloc struct, fills in, , passes off struct pointer thread.

remove pthread_join main thread , run threads detached. main no longer close/cleanup client thread.

client thread close/cleanup/free.

even that, server/client code still needs work, now, does work multiple simultaneous client connections believe main issue.

note: i've answered similar question before: executing commands via sockets popen() pay particular attention discussion of "flag" character.

anyway, here's code. i've cleaned it, annotated bugs , fixes , wrapped old/new code #if 0. note of "old" code isn't purely original code, interim version of mine. [please pardon gratuitous style cleanup]:


server.c:

/*  soner  receive file on socket.   saves output.tmp 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: consolidates 4 arrays indexed socket_index struct client {     socklen_t client_len;     struct sockaddr_in client_address;     int client_sockfd;     pthread_t thread; };  // note: no longer used/needed true multiclient #if 0 pthread_mutex_t mutex1 = pthread_mutex_initializer; #endif  // note/bug: didn't provide enough space 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  server !!\n"); }  int main(int argc, char **argv) {     struct addrinfo hints,     *res;     int enable = 1;     //int filefd;  // note: never initialized/used     int server_sockfd;     unsigned short server_port = 12345u;     char portnum[portsize];      // note: client related data 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 ip 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: want threads run detached don't have wait     // them cleanup -- thread own close/cleanup     pthread_attr_t attr;     pthread_attr_init(&attr);     pthread_attr_setdetachstate(&attr,1);      while (1) {         // note/bug: using fixed list, if let threads detach,         // don't know thread completes allowing control struct         // reused         // solution allocate fresh one, fill it, pass         // thread , let _thread_ closes , 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 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 threads detached , we're passing down         // information in case client loop needs #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: why couldn't multiple clients @ same         // time -- doing thread join         // _had_ because main thread didn't know when thread         // done control struct without 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: run parallel threads, prevents #if 0     pthread_mutex_lock(&mutex1); #endif      // until stop receiving go on taking information     while (recv(connect_socket, receivefilename, sizeof(receivefilename), 0)) {         // note/fix2: have client send file length         // know when stop 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 file name received? ?   =>  %s [%lld bytes]\n",             file_path,file_length);          // note: if want see _why_ sending length necessary,         // uncomment line , "unable send 2 files" bug         // 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: read we're told read         // previously, keep trying read, on _second_         // send, our read call here data _should_ have         // gone recv above         // in other words, we'd lose synchronization client         // sending [and we'd put second filename first         // file data @ bottom]         (;  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 never closed #if 1         close(filefd); #endif     }  #if 0     pthread_mutex_unlock(&mutex1); #endif      fprintf(stderr, "client dropped connection\n");      // note: client related cleanup here     // previously, main thread doing close, why had     // pthread_join     close(connect_socket);     free(ctl);      // note: needs void * value below #if 0     pthread_exit(&ret); #endif      return (void *) 0; } 

client.c:

/*  soner  send file on 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: didn't provide enough space 5 digit port + eos char #if 0 enum { portsize = 5 }; #else enum { portsize = 6 }; #endif  // note2: "volatile" attribute here critical proper operation volatile int signo_taken;  // note/bug2: don't use bufsiz when want else #define maxfiles        1000  void sig_handler(int signo) {      // note/bug2/fix: doing printf within signal handler _not_ [afaik]     // safe thing because can foul internal structure data of     // stdout if base task doing printf/puts , signal occurred     // in middle -- there number of other restrictions, such     // _no_ malloc, etc.      // so, alert base layer , let handle things when it's in     // "safe" state ...     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 = 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 ip 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);     }      /* actual connection. */     if (connect(sockfd, res->ai_addr, res->ai_addrlen) == -1) {         perror("connect");         return exit_failure;     }      // note/fix2: needs done once, since desired action     // [cleanly] stop program     signal(sigint, sig_handler);      // notes:     // (1) instead of using signo_taken done, below there alternate     //     ways handle signals sigsetjmp , siglongjmp     // (2) main reason _not_ prevent handler     //     messing file transfer     while (! signo_taken) {         puts("connected 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 bug, helps user output 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: should check after _any_ call 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 , client ----");                 (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 file _before_ sending request server                 // , [now] want know file length can send                 // server know when stop receiving #if 1                 filefd = open(file_path, o_rdonly);                 if (filefd == -1) {                     perror("open send file");                     // exit(exit_failure);                     break;                 }                  // file's byte length                 if (fstat(filefd,&st) < 0) {                     perror("stat send file");                     // exit(exit_failure);                     close(filefd);                     break;                 } #endif                  // send file name 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: should done above _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, must save                 // 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: output here when it's save     if (signo_taken)         printf("!!  ouch,  ctrl - c received on client  !!\n");      free(user_input);     free(server_reply);     exit(exit_success); } 

update:

i have solved connection-interruption problem signal still occurring. left 2 problems more times file sending , signal handling

i have reworked client signal handling works expected [which print message , stop client].

i have fixed problem 1 file sent. understand this, consider actions of both client , server.

to send file, client prompts filename, send call filename in it. opens file , read/write loop send file data server [and closes file descriptor].

to receive file, server recv call filename. opens file [for output] , read/write write data socket file [and closes file descriptor].

here problem: termination condition server's read/write loop wait until read(connect_socket,...) call returns 0. but, not return 0 [unless socket has been closed].

so, client send call send second filename. but, data this, instead of going server's recv call, merely part of read buffer. is, second filename appended first file data.

the solution have client tell server file size is. so, instead of client doing send of filename, send of filesize,filename

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

there 1 or 2 other minor bugs. i've updated both client.c , server.c bug fixes , annotations


Comments

Popular posts from this blog

ios - RestKit 0.20 — CoreData: error: Failed to call designated initializer on NSManagedObject class (again) -

java - Digest auth with Spring Security using javaconfig -

laravel - PDOException in Connector.php line 55: SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: YES) -