在 C 中使用多线程服务器时无法完成文件传输

Posted

技术标签:

【中文标题】在 C 中使用多线程服务器时无法完成文件传输【英文标题】:Can't finish transferring a file when using a multi-threaded server in C 【发布时间】:2015-03-17 07:37:37 【问题描述】:

我有一个多线程客户端,可以将一批文件传输到客户端自己创建的新目录。我的客户端曾经使用单线程服务器。

对于一个任务,我应该将我的单线程服务器转换为多线程服务器,为每个客户端请求创建一个新线程。我还应该对整个操作进行计时并将其输出到客户端(当服务器是单线程时我开始工作)。多线程客户端(与单线程服务器一起使用)和多线程服务器(效果不佳)的代码如下:

client.c

#include <sys/uio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <netdb.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <string.h>
#include <strings.h>
#include "Timer.h"

#define N_THREADS 10

char * files[] = 
    "/usr/share/dict/words",
    "/usr/include/sqlite3.h",
    "/usr/include/tclDecls.h",
    "/usr/include/bfd.h",
    "/usr/include/libmng.h",
    "/usr/include/elf.h",
    "/usr/include/gmpxx.h",
    "/usr/include/tkDecls.h",
    "/usr/include/H5overflow.h",
    "/usr/include/tcl.h",
    "/usr/include/gmp-x86_64.h",
    "/usr/include/curses.h",
    "/usr/include/lcms.h",
    "/usr/include/netapi.h",
    "/usr/include/gcrypt.h",
    "/usr/include/zlib.h",
    "/usr/include/ldap.h",
    "/usr/include/geos_c.h",
    "/usr/include/kdb.h",
    "/usr/include/tk.h",
    "/usr/include/yaml.h"
;

#define files_length() (sizeof files / sizeof files[0])

void error(char *msg)

    perror(msg);
    exit(-1);


struct sockaddr_in make_server_addr(char *host, short port)

    struct sockaddr_in addr;
    bzero(&addr, sizeof addr);
    struct hostent *hp = gethostbyname(host);
    if ( hp == 0 )
        error(host);
    addr.sin_family = AF_INET;
    bcopy(hp->h_addr_list[0], &addr.sin_addr, hp->h_length);
    addr.sin_port = htons(port);
    return addr;


int connect_socket(char *host, short port)

    int status;
    int tries = 3;
    struct sockaddr_in addr = make_server_addr(host, port);
    int s = socket(AF_INET, SOCK_STREAM, 0);
    if ( s == -1 )
        error("socket()");
    status = connect(s, (struct sockaddr*)&addr, sizeof addr);
    if ( status < 0 )
        error("connect refused");
    return s;


void request_file_from_server(int server_socket, char *file)

    int len = strlen(file);
    int n = write(server_socket, file, len);
    if ( n != len )
        error("short write");


void read_file_from_server(int server_socket, char *file)

    char buf[BUFSIZ];
    int n;
    mode_t mode = 0666;
    int ofd = open(file, O_WRONLY | O_CREAT, mode);
    if ( ofd == -1 )
        perror("open()");
    while ( (n = read(server_socket, buf, BUFSIZ)) > 0 )
        write(ofd, buf, n);
    close(ofd);



struct Thread_data

    int id;
    pthread_t thread_id;
    char * host;
    short port;
    char path[BUFSIZ];
;

void make_file_name(char *local_name, char *dir, char *original_path)

    char *p = rindex(original_path, '/');
    if ( !p )
        error("rindex()");
    sprintf(local_name, "%s/%s", dir, p+1);


int remote_copy(struct Thread_data * data, char * file)

    int server_socket = connect_socket(data->host, data->port);
    request_file_from_server(server_socket, file);
    char local_name[BUFSIZ];
    make_file_name(local_name, data->path, file);
    read_file_from_server(server_socket, local_name);
    close(server_socket);


void make_empty_dir_for_copies(struct Thread_data * data)

    mode_t mode = 0777;
    sprintf(data->path, "./Thread_%d", (data->id + 1));
    mkdir(data->path, mode);


#define N_FILES_TO_COPY files_length() // copy them all

void *thread_work(void *arg)

    struct Thread_data * data = (struct Thread_data *)arg;
    make_empty_dir_for_copies(data);
    for ( int i=0; i < N_FILES_TO_COPY; ++i )
        remote_copy(data, files[i]);
    pthread_exit(0);


void start_threads(char *host, short port, struct Thread_data thread_args[])

    for ( int i = 0; i < N_THREADS; ++i )
    
        struct Thread_data * t = &thread_args[i];
        t->id = i;
        t->host = host;
        t->port = port;
        pthread_create(&t->thread_id, NULL, thread_work, t);
    


void join_threads(struct Thread_data thread_args[], double *eTime)

    for ( int i=0; i < N_THREADS; i++ )
        pthread_join(thread_args[i].thread_id, NULL);
    Timer_elapsedUserTime(eTime);
    printf("Elapsed time for transferring all files: %lf\n", *eTime);
    pthread_exit(0);


int main(int argc, char *argv[])

    if ( argc != 3 )
    
        fprintf(stderr, "Usage: %s host port\n", argv[0]);
        exit(-1);
    

    struct Thread_data thread_args[N_THREADS];
    char *host = argv[1];
    short port = atoi(argv[2]);
    double eTime;
    Timer_start();
    start_threads(host,port,thread_args);
    join_threads(thread_args, &eTime);


server.c

#include <sys/types.h>
#include <signal.h>
#include <sys/uio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <pthread.h>
#include <string.h>
#include "Timer.h"

#define BACKLOG 200
// more than this in the queue, and client connect will fail
#define NUM_THREADS 200


void error(char *msg)

    fprintf(stderr, "%s\n", msg);
    exit(-1);



struct sockaddr_in make_server_addr(short port)

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    return addr;


int create_server_socket(short port)

    int s = socket(AF_INET, SOCK_STREAM, 0);
    int optval = 1;
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
    struct sockaddr_in my_addr = make_server_addr(port);
    if ( s == -1 )
        error("socket()");
    bind(s, (struct sockaddr*)&my_addr, sizeof my_addr);
    listen(s, BACKLOG);
    return s;


void get_file_request(int socket, char *fileName)

    char buf[BUFSIZ];
    int n = read(socket, buf, BUFSIZ);
    if ( n < 0 )
        error("read from socket");
    buf[n] = '\0';
    strcpy(fileName, buf);
    printf("Server got file name of '%s'\n", fileName);


void write_file_to_client_socket(char *file, int socket)

    char buf[BUFSIZ];
    int n;
    int ifd = open(file, O_RDONLY);
    if ( ifd == -1 )
        error("open()");
    while ( (n = read(ifd, buf, BUFSIZ)) > 0 )
        write(socket, buf, n);
    close(ifd);


void * handle_request(void * c_socket)

    int *client_socket = (int*)c_socket;
    char fileName[BUFSIZ];
    get_file_request(*client_socket, fileName);
    write_file_to_client_socket(fileName, *client_socket);
    close(*client_socket);
    pthread_exit(0);
    return NULL;


void time_out(int arg)

    fprintf(stderr,  "Server timed out\n");
    exit(0);


void set_time_out(int seconds)

    struct itimerval value = 0;
    // bzero(&value, sizeof value);
    /* timerclear(&value.it_interval); timerclear(&value.it_value); */
    value.it_value.tv_sec = seconds;
    setitimer(ITIMER_REAL, &value, NULL);
    signal(SIGALRM, time_out);


void accept_client_requests(int server_socket)

    pthread_t threads;
    int client_socket;
    struct sockaddr_in client_addr;
    socklen_t sin_size = sizeof client_addr;
    set_time_out(10);
    while ( (client_socket =
            accept(server_socket, (struct sockaddr*)&client_addr, &sin_size)) )
    
        set_time_out(10);
        pthread_create(&threads,0, handle_request,&client_socket);
    


int main(int argc, char *argv[])

    if ( argc != 2 )
        error("usage: server port");
    short port = atoi(argv[1]);
    int server_socket = create_server_socket(port);
    accept_client_requests(server_socket);
    shutdown(server_socket, 2);
    return 0;

当我使用accept 并创建一个新的pthread 时,使用handle_request 时会出现此问题。无论最后一个文件是什么(在本例中为/usr/include/yaml.h),它都会挂起,然后超时。如果没有超时,它将无限期地挂起。

我对使用 pthread 的多线程了解不多,所以我只是脱离了我的教授的说明,基本上说创建线程并像在单线程服务器中一样处理请求。在我的单线程服务器中,handle_request 以 int 形式传递(现在已转换)。

有谁知道为什么我的服务器会一直挂在最后传输的文件上直到超时?

【问题讨论】:

【参考方案1】:

accept_client_requests 函数存在缺陷。你有一个变量

int client_socket;

那个变量的地址被传递给pthread_create

pthread_create(&threads,0, handle_request,&client_socket);

pthread_create 将指针传递给 handle_request,后者将其存储为本地指针

int *client_socket = (int *)c_socket;

问题是指针仍然指向client_socket 函数中的client_socket 变量。所以当accept_client_requests 得到另一个连接时,client_socket 会发生变化,并且当前运行的每个线程的client_socket 都会发生变化,这应该会引起各种混乱。

解决方案是mallocint 来保存client_socket,然后将该地址传递给线程。

int *temp = malloc( sizeof(int) );
*temp = client_socket;
pthread_create(&threads, 0, handle_request, temp);
pthread_detach(threads);

当线程结束时,它应该free 内存。


accept_client_requests 函数还应该在它创建的每个线程上调用pthread_detach,以便在线程完成时可以回收资源。

如果没有pthread_detach,系统会在清理线程之前看到pthread_join

【讨论】:

这确实有道理,它解决了我试图弄清楚如何不只是将指针传递给 handle_requests 函数的问题。但是当我尝试这个解决方案(并在handle_requests中的while循环之外释放内存)时,它只创建了几个目录,其中只有一个文件,并在崩溃之前给了我一堆连接被拒绝的错误消息。 @Alex,我忘了提pthread_detach,更新了答案。 pthread_detach(pthread_self());使用 pthread_create 后确实创建了 10 个目录,但它似乎只复制了一个文件名,其中没有任何内容,并声明连接被拒绝。我应该在句柄请求中使用 pthread_detach 吗? @Alex 我会将pthread_detach 放在pthread_create 之后(请参阅更新的答案)。请注意pthread_detach 只是告诉操作系统您不会在该线程上调用pthread_join,因此操作系统可以在线程完成后立即清理。 非常感谢您帮助我,实际上我现在正在学习很多关于这些东西的知识。但是运行服务器然后客户端给了我这个:对于服务器:read from socket, read from socket, read from socket,从客户端:Connection refused。它似乎仍然不想复制足够多的目录,并且仍然只复制一个文件。 :((

以上是关于在 C 中使用多线程服务器时无法完成文件传输的主要内容,如果未能解决你的问题,请参考以下文章

java socket多文件传输问题

FTP传文件弊端多,更好用的解决方案来了!

linux rz命令无法传输文件

手机蓝牙连接其他手机,传输文件时,怎么找到对方设备

基于socketserver开发多线程ftp

java中文件大小超过多大需要断点续传