【发布时间】:2016-09-26 14:59:03
【问题描述】:
我正在尝试在 C 中创建一个多线程的服务器-客户端文件传输系统。有些客户端将发送或列出或做一些其他选择(在你可以看到的开关情况下)和一个服务器存储文件和服务很多客户。
据我所知,多线程意识形态真的很难。它需要太多的经验而不是知识。我已经在这个项目上工作了一个多星期,但我无法解决问题。
有4种选择:第一个是列出客户端在其目录中的本地文件,第二个是列出客户端和服务器之间传输的文件,第三个是从用户读取文件名并将文件复制到服务器目录中。
我的重要问题是关于多线程。我无法连接多个客户端。我已经从 a 到 z 阅读了很多次代码,但我真的无法捕捉到我的错误并被卡住了。
另一个问题是当SIGINT 被捕获时客户端将结束,但是,例如,在按下 ctrl-c 时选择列表文件后它不会停止。服务器文件也有同样的问题。相比客户端抓更麻烦,因为当服务端获取到SIGINT时,客户端会分别与服务端断开连接。
感谢您的帮助!
服务器.c
/*
Soner
Receive a file over a socket.
Saves it to output.tmp by default.
Interface:
./executable [<port>]
Defaults:
- output_file: output.tmp
- port: 12345
*/
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
enum { PORTSIZE = 5 };
void* forClient(void* ptr);
void sig_handler(int signo)
{
if (signo == SIGINT)
printf("!! OUCH, CTRL - C received by server !!\n");
}
int main(int argc, char **argv) {
struct addrinfo hints, *res;
int enable = 1;
int filefd;
int server_sockfd;
unsigned short server_port = 12345u;
char portNum[PORTSIZE];
socklen_t client_len[BUFSIZ];
struct sockaddr_in client_address[BUFSIZ];
int client_sockfd[BUFSIZ];
int socket_index = 0;
pthread_t threads[BUFSIZ];
if (argc != 2) {
fprintf(stderr, "Usage ./server <port>\n");
exit(EXIT_FAILURE);
}
server_port = strtol(argv[1], NULL, 10);
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; //ipv4
hints.ai_socktype = SOCK_STREAM; // tcp
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
sprintf(portNum, "%d", server_port);
getaddrinfo(NULL, portNum, &hints, &res);
server_sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (server_sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
if (setsockopt(server_sockfd, SOL_SOCKET, (SO_REUSEPORT | SO_REUSEADDR), &enable, sizeof(enable)) < 0) {
perror("setsockopt(SO_REUSEADDR) failed");
exit(EXIT_FAILURE);
}
if (bind(server_sockfd, res->ai_addr, res->ai_addrlen) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(server_sockfd, 5) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
fprintf(stderr, "listening on port %d\n", server_port);
while (1) {
client_len[socket_index] = sizeof(client_address[socket_index]);
puts("waiting for client");
client_sockfd[socket_index] = accept(
server_sockfd,
(struct sockaddr*)&client_address[socket_index],
&client_len[socket_index]
);
if (client_sockfd[socket_index] < 0) {
perror("Cannot accept connection\n");
close(server_sockfd);
exit(EXIT_FAILURE);
}
pthread_create( &threads[socket_index], NULL, forClient, (void*)client_sockfd[socket_index]);
if(BUFSIZ == socket_index) {
socket_index = 0;
} else {
++socket_index;
}
pthread_join(threads[socket_index], NULL);
close(filefd);
close(client_sockfd[socket_index]);
}
return EXIT_SUCCESS;
}
void* forClient(void* ptr) {
int connect_socket = (int) ptr;
int filefd;
ssize_t read_return;
char buffer[BUFSIZ];
char *file_path;
char receiveFileName[BUFSIZ];
int ret = 1;
// Thread number means client's id
printf("Thread number %ld\n", pthread_self());
pthread_mutex_lock( &mutex1 );
// until stop receiving go on taking information
while (recv(connect_socket, receiveFileName, sizeof(receiveFileName), 0)) {
file_path = receiveFileName;
fprintf(stderr, "is the file name received? ? => %s\n", file_path);
filefd = open(file_path,
O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR);
if (filefd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
do {
read_return = read(connect_socket, buffer, BUFSIZ);
if (read_return == -1) {
perror("read");
exit(EXIT_FAILURE);
}
if (write(filefd, buffer, read_return) == -1) {
perror("write");
exit(EXIT_FAILURE);
}
} while (read_return > 0);
}
pthread_mutex_unlock( &mutex1 );
fprintf(stderr, "Client dropped connection\n");
pthread_exit(&ret);
}
client.c
/*
Soner
Send a file over a socket.
Interface:
./executable [<sever_hostname> [<port>]]
Defaults:
- server_hostname: 127.0.0.1
- port: 12345
*/
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>
// NOTE/BUG: this didn't provide enough space for a 5 digit port + EOS char
#if 0
enum { PORTSIZE = 5 };
#else
enum { PORTSIZE = 6 };
#endif
void
sig_handler(int signo)
{
if (signo == SIGINT)
printf("!! OUCH, CTRL - C received on client !!\n");
}
int
main(int argc, char **argv)
{
struct addrinfo hints,
*res;
char *server_hostname = "127.0.0.1";
char file_path[BUFSIZ];
char *server_reply = NULL;
char *user_input = NULL;
char buffer[BUFSIZ];
int filefd;
int sockfd;
ssize_t read_return;
struct hostent *hostent;
unsigned short server_port = 12345;
char portNum[PORTSIZE];
char remote_file[BUFSIZ];
int select;
char *client_server_files[BUFSIZ];
int i = 0;
int j;
// char filename_to_send[BUFSIZ];
if (argc != 3) {
fprintf(stderr, "Usage ./client <ip> <port>\n");
exit(EXIT_FAILURE);
}
server_hostname = argv[1];
server_port = strtol(argv[2], NULL, 10);
/* Prepare hint (socket address input). */
hostent = gethostbyname(server_hostname);
if (hostent == NULL) {
fprintf(stderr, "error: gethostbyname(\"%s\")\n", server_hostname);
exit(EXIT_FAILURE);
}
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; // ipv4
hints.ai_socktype = SOCK_STREAM; // tcp
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
sprintf(portNum, "%d", server_port);
getaddrinfo(NULL, portNum, &hints, &res);
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/* Do the actual connection. */
if (connect(sockfd, res->ai_addr, res->ai_addrlen) == -1) {
perror("connect");
return EXIT_FAILURE;
}
while (1) {
if (signal(SIGINT, sig_handler)) {
break;
}
puts("connected to the server");
puts("-----------------");
puts("|1 - listLocal| \n|2 - listServer| \n|3 - sendFile| \n|4 - help| \n|5 - exit| ");
puts("-----------------");
while (1) {
scanf("%d", &select);
switch (select) {
case 1: // list files of client's directory
system("find . -maxdepth 1 -type f | sort");
break;
case 2: // listServer
puts("---- Files btw Server and the Client ----");
for (j = 0; j < i; ++j) {
puts(client_server_files[j]);
}
break;
case 3: // send file
memset(file_path, 0, sizeof file_path);
scanf("%s", file_path);
memset(remote_file, 0, sizeof remote_file);
// send file name to server
sprintf(remote_file, "%s", file_path);
send(sockfd, remote_file, sizeof(remote_file), 0);
filefd = open(file_path, O_RDONLY);
if (filefd == -1) {
perror("open send file");
//exit(EXIT_FAILURE);
break;
}
while (1) {
read_return = read(filefd, buffer, BUFSIZ);
if (read_return == 0)
break;
if (read_return == -1) {
perror("read");
//exit(EXIT_FAILURE);
break;
}
if (write(sockfd, buffer, read_return) == -1) {
perror("write");
//exit(EXIT_FAILURE);
break;
}
}
// add files in char pointer array
client_server_files[i++] = file_path;
close(filefd);
break;
case 5:
free(user_input);
free(server_reply);
exit(EXIT_SUCCESS);
default:
puts("Wrong selection!");
break;
}
}
}
free(user_input);
free(server_reply);
exit(EXIT_SUCCESS);
}
【问题讨论】:
-
PORTSIZE = 5对于9999以上的端口是不够的。 -
不可避免地,无法正确和完整地处理从recv()返回的结果。在不能保证空终止的 char 数组上滥用 C 'string' 函数,无法正确处理 TCP 的八位字节流性质。
-
在概念上,主要问题是:在服务器的接受循环中,代码在接受客户端连接后生成一个线程,然后等待加入该线程。只有在此之后,才会完成下一次接受的调用。这将客户端连接序列化,一个接一个。让连接线程分离运行。此外,您还想修复/清理所有其他较大或较小的错误和不准确的部分。 (参见 Martin 对其中一些的评论)。
-
总而言之,我的建议是第一次编写单线程服务器/客户端对,以了解如何正确处理套接字。让这段代码被审查,然后才去寻找更复杂的多线程方法。
-
为什么互斥锁几乎围绕着 'forClient()' 的整个运行?这是弄巧成拙。你为什么要无缘无故地锁定所有其他的instabces?
标签: c linux multithreading sockets