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

标签 c multithreading file pthreads

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

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

客户端.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);

}

服务器.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),它都会挂起然后超时。如果没有超时,它将无限期地挂起。

我对使用 pthreads 的多线程了解不多,所以我只是按照我教授的说明进行操作,这些说明基本上是说创建线程并像在单线程服务器中一样处理请求。在我的单线程服务器中,handle_request 被传入一个 int(现在被转换)。

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

最佳答案

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;

问题是指针仍然指向 accept_client_requests 函数中的 client_socket 变量。因此,当 accept_client_requests 获得另一个连接时,client_socket 发生了变化,并且当前正在运行的每个线程的 client_socket 都发生了变化,这应该会导致各种困惑.

解决方法是malloc一个int来保存client_socket,然后将该地址传递给线程。

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

线程完成后,它应该释放 内存。


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

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

关于在 C 中使用多线程服务器时无法完成文件传输,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29093617/

相关文章:

c++ - gcc 检测到 "subindex out of bound"错误

java线程: Thread.中断()不工作

file - 在 Swift 中,如何将现有二进制文件读入数组?

c - 如何检测文件中是否存在数据 C 文件处理

c - fread 函数如何工作?

c - pthread_cond_timedwait 不会在 GHC FFI 中返回

c - 如何使用函数将用户输入读取到数组中?定义错误?

java - 在Java中,如果我调用一个从另一个可运行对象扩展Thread的类,哪个Thread会执行?

Java进程间通信和线程间通信?

file - 如何计算两个license.txt文件之间的相似度?