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
Post a Comment