使用套接字传输文件服务器/客户端 linux C

Posted

技术标签:

【中文标题】使用套接字传输文件服务器/客户端 linux C【英文标题】:transfer files server/client linux C with socket 【发布时间】:2013-06-17 13:47:02 【问题描述】:

伙计们,我需要一些帮助。我必须实现一个服务器/客户端项目来将文件从客户端传输到服务器。服务器接收 N 个套接字,每个服务器进程一个套接字,使用 fork(),每个套接字(线程)超过 1 个客户端。客户端将文件发送到服务器,服务器必须对其进行备份并显示 TX 速度和字节数。我解决了许多服务器进程的 TX 问题,但是当我尝试在同一个套接字上应用线程进行并发时,它不起作用,没有收到消息和文件。

服务器使用 1 个参数:number_of_sockets

客户端使用3个参数:server_ip server_port file_name

server.c

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<sys/resource.h>
#include<sys/wait.h>
#include<signal.h>
#include<unistd.h>
#include<time.h>

// accepting connections
short int aceptar_conexiones = 1;

//struct for threads
typedef struct
    int fd_socket;
    struct sockaddr_in address;
    socklen_t sock_length;
thread_args;

//struct for associate server-process and sockets
typedef struct
    int fd_socket;
    pid_t pid_proceso;
fd_socket_proceso;

//max sockets open in server
unsigned int max_sockets;
//max server-process in server
unsigned int cant_procesos;
//dynamic array for associated struct
fd_socket_proceso *fdsp;
//mutex for sync threads
pthread_mutex_t mutex_thread;

#define TAM_BUFFER 1024

//handler for threads
//void *connection_handler(void *);
void connection_handler(void *);
void finaliza_sockets();
void finaliza_procesos();
void error(const char *);

int main(int argc, char *argv[]) 
    int fd_listen_socket, fd_communication_socket, i;
    max_sockets = atoi(argv[1]);
    struct sockaddr_in listen_address, connection_address;
    socklen_t con_addr_len = sizeof(connection_address);

    //allocating memory for dynamic array
    fdsp = (fd_socket_proceso *)malloc(sizeof(fd_socket_proceso)*max_sockets);

    //create and open sockets, grabbing it on array, it starts on 1024 and on, max 5 clients per socket
    for(i=0 ; i<max_sockets ; i++)
        fd_listen_socket = socket(AF_INET, SOCK_STREAM, 0);
        if(fd_listen_socket < 0) error("No se pudo crear el socket.\n");
        bzero(&listen_address, sizeof(struct sockaddr_in));
        listen_address.sin_family = AF_INET;
        listen_address.sin_port = htons(1024+i); // Puede utilizarse cualquier puerto
        listen_address.sin_addr.s_addr = htonl(INADDR_ANY); // Cualquier direccion propia
        if(bind(fd_listen_socket, (struct sockaddr *)&listen_address, sizeof(struct sockaddr)) < 0) error("No se puede enlazar el socket.\n");
        printf("Servidor escuchando en puerto: %d\n",ntohs(listen_address.sin_port));
        listen(fd_listen_socket, 5);
        fdsp[i].fd_socket = fd_listen_socket;
    

    printf("Comenzamos a escuchar conexiones...\n");
    fflush(stdout);

    //fork per socket
    for(i=0 ; i<max_sockets ; i++ , cant_procesos++)
        if(!(fdsp[i].pid_proceso=fork()))
            while(aceptar_conexiones)
                bzero(&connection_address, sizeof(connection_address));
                if((fd_communication_socket = accept(fdsp[i].fd_socket, (struct sockaddr *)&connection_address, &con_addr_len))==-1)
                    finaliza_sockets();
                    finaliza_procesos();
                    perror("Error en la comunicacion con el socket");
                    exit(EXIT_FAILURE);
                
                pthread_t thread_cliente;
                pthread_attr_t thread_attr;
                thread_args t_args;
                pthread_attr_init(&thread_attr);
                pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
                t_args.address = connection_address;
                t_args.fd_socket = fd_communication_socket;
                t_args.sock_length = con_addr_len;
                //pthread_create(&thread_cliente, NULL, connection_handler, (void *)&t_args);
                connection_handler((void *)&t_args);
            
        
    
    finaliza_sockets();
    finaliza_procesos();
    printf("Server offline.\n");


//void *connection_handler(void *t_args)
void connection_handler(void *t_args)

    thread_args *t = (thread_args *)t_args;
    char buffer[TAM_BUFFER];
    char *buffer2;
    FILE *fp;
    char *datos[2];
    char file_name[TAM_BUFFER];
    char *ip_cliente = inet_ntoa(t->address.sin_addr);
    int port_cliente = ntohs(t->address.sin_port);
    long int file_size, bytes_restantes;
    ssize_t recv_size;
    int t_inicio ,t_fin , t_transferencia;
    int i;

    bzero(buffer, sizeof(buffer));
    //1st msg recieve file_size concat using char '°' with file_name
    recv_size = recvfrom(t->fd_socket, buffer, sizeof(buffer), 0, (struct sockaddr *)&(t->address), &(t->sock_length));

    //parse file_size and file_name
    buffer2=strtok(buffer,"°");
    for(i=0 ; buffer2 ; i++)
        datos[i]=buffer2;
        buffer2=strtok(NULL,"°");
    
    file_size = atoi(datos[0]);
    strcpy(file_name, datos[1]);

    //concat file_name for backup
    strcat(file_name, ".bkp");
    if(fp=fopen(file_name, "r"))
        printf("\nError, el fichero %s provisto por el cliente %s ya existe! Este sera omitido por el servidor.\n",file_name, ip_cliente);
        fflush(stdout);
        close(t->fd_socket);
        return;
    else
        if((fp=fopen(file_name,"w"))==NULL)
            close(t->fd_socket);
            printf("\nError en la creacion del fichero %s provisto por el cliente %s.\n",file_name, ip_cliente);
            fflush(stdout);
            exit(errno);
        else
            bytes_restantes = file_size;
            bzero(buffer, sizeof(buffer));
            //calculate begin TX
            t_inicio = clock();
            //recieve file from sendfile() from client
            recv_size = recv(t->fd_socket, buffer, sizeof(buffer), 0);
            printf("llego, recv: %ld\n",recv_size);
            while(recv_size > 0 && bytes_restantes > 0)
                //calculate finish TX
                t_fin = clock();
                bytes_restantes -= recv_size;
                //write on file descriptor
                fwrite(buffer, sizeof(char), recv_size, fp);
                //calculate TX speed
                t_transferencia = t_fin-t_inicio/CLOCKS_PER_SEC;
                //print data about TX
                printf("Cliente:%s:%d, Fichero:%*s, Bytes recibidos:%*ld, Bytes restantes:%*ld, Velocidad TX:%d (bytes/seg)\r",
                    ip_cliente, port_cliente, strlen(file_name)*1L, file_name ,sizeof(long int), file_size-bytes_restantes, sizeof(long int), 
                    bytes_restantes, t_transferencia);
                fflush(stdout);
                bzero(buffer, sizeof(buffer));
                //recieve file from sendfile() from client
                recv_size = recv(t->fd_socket, buffer, sizeof(buffer), 0);
                t_inicio = clock();
            
        
    
    printf("\nCliente:%s:%d\tFichero:%s\tTransferencia finalizada.\n",ip_cliente, port_cliente, file_name);
    fflush(stdout);
    fclose(fp);
    close(t->fd_socket);
    //pthread_exit(0);

//close sockets
void finaliza_sockets()
    int i;
    for(i=0 ; i<max_sockets ; i++)
        close(fdsp[i].fd_socket);
    free(fdsp);

//wait for all server-process
void finaliza_procesos()
    int i;
    for(i=0 ; i<cant_procesos ; i++)
        wait(0);

void error(const char *msg)
    perror(msg);
    exit(errno);

client.c

#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<signal.h>
#include<time.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/sendfile.h>

#define TAM_BUFFER 1024

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

    int fd_socket;
    int i;
    int tini, tfin, ttransferencia;
    struct sockaddr_in server_address;
    char *path, *buffer, *vect[30], linea[TAM_BUFFER], linea2[TAM_BUFFER], file_name[TAM_BUFFER/4];

    int fd_file;
    long int send_bytes, offset, remain_data;
    struct stat file_stat;

    //file_name , maybe use path
    path = argv[3];

    if((fd_file = open(path, O_RDONLY))==-1)
        perror("error en la apertura del archivo\n");
        exit(EXIT_FAILURE);
    
    if(fstat(fd_file, &file_stat)<0)
        perror("error fstat");
        exit(EXIT_FAILURE);
    

    //connection with server
    fd_socket = socket(AF_INET, SOCK_STREAM, 0);
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(atoi(argv[2]));
    server_address.sin_addr.s_addr = inet_addr(argv[1]);

    bzero(&(server_address.sin_zero),8);    

    signal(SIGINT,SIG_DFL);

    //establishing connection
    if(connect(fd_socket,(struct sockaddr *)&server_address,sizeof(struct sockaddr)) < -1)
        perror("error on connection with server");
        exit(errno);
    

    //parse file_name from path 
    i=0;
    buffer=strtok(path,"/");
    while(buffer != NULL)
    
        vect[i]=buffer;
        buffer=strtok(NULL,"/");
        i++;
    

    //concat file_size with char '°' and file_name
    strcpy(file_name, vect[i-1]);
    sprintf(linea,"%ld",file_stat.st_size);
    strcat(linea,"°");
    strcat(linea, file_name);

    //send concatenated char
    send_bytes = send(fd_socket,linea,strlen(linea),0);

    //send file to server
    offset = 0;
    remain_data = file_stat.st_size;
    while(((send_bytes = sendfile(fd_socket, fd_file,(off_t *) &offset, TAM_BUFFER)) > 0) && (remain_data > 0))
        printf("enviados: %*ld bytes\toffset: %*ld\tbytes restantes: %ld\r",sizeof(send_bytes), send_bytes, sizeof(offset), offset, remain_data);
        sizeof(stdout);
        remain_data -= send_bytes;
    

    close(fd_file);
    close(fd_socket);
    return 0;

正如我所说,我的问题是我需要每个套接字上的并发性。我如何管理和同步它们?我尝试了基本的东西,但它不起作用,所以我评论了 thread_createthread_exit func。

感谢您的宝贵时间。-

【问题讨论】:

我注意到您的 connection_handler 函数已将 void* 返回类型注释掉以支持 void。这与 pthread_create 一起使用是不正确的。你知道吗?您是否完全启用了编译器警告? 所以客户端应该连接到多个套接字?还是服务器应该为每个客户端有多个线程或每个客户端(多个客户端)有 1 个线程? 是的,我评论了 void *connection_handler(void *) 行和 pthread_create() 以获得一个简单的函数来调用它,这样我避免使用线程...当我反转此注释行时,我在接收 file_size 和 file_name 时遇到问题 服务器必须处理许多套接字,因为输入参数接收和来自客户端的每个套接字上的许多连接,每个套接字最多 5 个客户端 listen(fd_listen_socket, 5) "... 并且每个套接字(线程)超过 1 个客户端。" 您真的希望服务器服务于 一个 客户端以上同时每个唯一的套接字? 不幸的是我需要这种方式,服务器必须处理多个端口和每个唯一套接字的多个 (5) 客户端。里面很难装吗?应该用线程吗? 【参考方案1】:

我认为主要结构没有任何问题。您正在侦听多个端口,为每个端口生成一个进程。每个进程(应该)为每个客户端生成一个新线程。

您在服务器中确实存在带有 thread_args.address 的竞争条件(如果两个客户端快速连续连接,可能会被覆盖),但很容易避免。不要在 TCP 套接字上使用 recvfrom —— 这真的没有意义。使用 read(),甚至更好的 fread(),它可以避免 EINTR 问题。在开始时一次读取一个字节,直到遇到第一个分隔符(尽管最好将协议更改为发送 32 位字而不是 ascii 表示)。

但主要问题是您有一个大型程序,而您只是说“它不起作用”。只要您希望我们为您调试它,发布一些诊断信息怎么样?是否创建了服务器进程?打印什么?第一个客户端,第二个客户端失败还是什么?

【讨论】:

【参考方案2】:

您可以使用 fork() 创建子进程,父进程可以监听,当客户端连接时,父进程可以将该客户端移交给子进程并继续监听其他客户端。然后当另一个客户端再次连接时 fork() 并且您可以再次创建另一个子进程来处理该客户端。像这样你可以同时处理客户端。

pid = fork()

当pid = 0时为子进程,如果pid > 0则为父进程,如果pid

【讨论】:

以上是关于使用套接字传输文件服务器/客户端 linux C的主要内容,如果未能解决你的问题,请参考以下文章

使用python套接字编程将图像文件从服务器传输到客户端

如何使用套接字传输 pdf、mp3 或 mp4 文件?

在windows 与Linux间实现文件传输(C++&C实现)

分段错误(核心转储)错误 C linux 套接字

c - 具有多个客户端的 UDP 客户端-服务器

使用java中的套接字将文件从一个客户端发送到另一个客户端