multithreading文件传输与套接字

我想在C中创build一个multithreading的服务器 – 客户端文件传输系统。有客户端会发送或列出或做其他select(在交换机的情况下,你可以看到)和服务器存储的文件和服务很多的客户。

就我所知,multithreading的意识形态是非常困难的。 它需要太多的经验而不是知识。 我已经在这个项目上工作了一个多星期,而且我一直没能解决问题。

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

我的重要问题是关于multithreading。 我无法连接多个客户端。 我已经阅读了代码从一堆到几堆,但我真的不能抓住我的错误,卡住了。

另一个问题是客户端将在SIGINT被捕获时结束,但是,例如,在按ctrl-cselect列表文件后,它不会停止。 服务器文件也是同样的问题。 与客户端捕获相比,更麻烦,因为当服务器获取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); } 

Solutions Collecting From Web of "multithreading文件传输与套接字"

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

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

消除互斥。

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

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

客户端线程现在执行close / cleanup / free。

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

注意:我之前已经回答了类似的问题: 使用popen()通过套接字执行命令请特别注意“flag”字符的讨论。

无论如何,这是代码。 我已经清理了它,注释了这些错误并修复了错误,并用#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 (eg 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); } 

更新:

我已经解决了我的连接中断问题,但信号仍在发生。 我更多地留下了两个问题文件发送和信号处理

我重写了客户端信号处理,以便按预期工作[即打印消息并停止客户端]。

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

要发送一个文件,客户端会提示输入文件名,发送一个带有文件名的电话。 然后打开文件并执行读/写循环,将文件数据发送到服务器[然后关闭文件描述符]。

要接收文件,服务器会进行recv调用以获取文件名。 然后打开文件[输出]并执行读/写操作,将数据从套接字写入文件[然后关闭文件描述符]。

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

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

解决办法是让客户端告诉服务器文件大小是多少。 所以,而不是客户端send filename ,它现在send filesize,filename

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

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