Сервер передачи файлов/клиент, использующий сокет

Я пытаюсь сделать передачу файлов между сервером и клиентом, но работает очень плохо. В основном, что должно произойти:
1) Клиент отправляет txt файл на сервер (я назвал его "quotidiani.txt" )
2) Сервер сохраняет его в другом txt файле ( "receive.txt" )
3) Сервер запускает script на нем, который изменяет его и сохраняет его с другим именем ( "output.txt" )
4) Сервер отправляет файл обратно клиенту, который сохраняет его (в том же сокете) с именем (final.txt)

Проблема в том, что первый файл (quotidiani.txt) читается только для небольшой части, а затем есть некоторые ошибки. Я хочу, чтобы кто-то помог мне понять и исправить мои ошибки.

Здесь мой код:

client.c:

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <signal.h>
#include <ctype.h>          
#include <arpa/inet.h>
#include <netdb.h>

#define PORT 20000
#define LENGTH 512 


void error(const char *msg)
{
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[])
{
    /* Variable Definition */
    int sockfd; 
    int nsockfd;
    char revbuf[LENGTH]; 
    struct sockaddr_in remote_addr;

    /* Get the Socket file descriptor */
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        fprintf(stderr, "ERROR: Failed to obtain Socket Descriptor! (errno = %d)\n",errno);
        exit(1);
    }

    /* Fill the socket address struct */
    remote_addr.sin_family = AF_INET; 
    remote_addr.sin_port = htons(PORT); 
    inet_pton(AF_INET, "127.0.0.1", &remote_addr.sin_addr); 
    bzero(&(remote_addr.sin_zero), 8);

    /* Try to connect the remote */
    if (connect(sockfd, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) == -1)
    {
        fprintf(stderr, "ERROR: Failed to connect to the host! (errno = %d)\n",errno);
        exit(1);
    }
    else 
        printf("[Client] Connected to server at port %d...ok!\n", PORT);

    /* Send File to Server */
    //if(!fork())
    //{
        char* fs_name = "/home/aryan/Desktop/quotidiani.txt";
        char sdbuf[LENGTH]; 
        printf("[Client] Sending %s to the Server... ", fs_name);
        FILE *fs = fopen(fs_name, "r");
        if(fs == NULL)
        {
            printf("ERROR: File %s not found.\n", fs_name);
            exit(1);
        }

        bzero(sdbuf, LENGTH); 
        int fs_block_sz; 
        while((fs_block_sz = fread(sdbuf, sizeof(char), LENGTH, fs)) > 0)
        {
            if(send(sockfd, sdbuf, fs_block_sz, 0) < 0)
            {
                fprintf(stderr, "ERROR: Failed to send file %s. (errno = %d)\n", fs_name, errno);
                break;
            }
            bzero(sdbuf, LENGTH);
        }
        printf("Ok File %s from Client was Sent!\n", fs_name);
    //}

    /* Receive File from Server */
    printf("[Client] Receiveing file from Server and saving it as final.txt...");
    char* fr_name = "/home/aryan/Desktop/progetto/final.txt";
    FILE *fr = fopen(fr_name, "a");
    if(fr == NULL)
        printf("File %s Cannot be opened.\n", fr_name);
    else
    {
        bzero(revbuf, LENGTH); 
        int fr_block_sz = 0;
        while((fr_block_sz = recv(sockfd, revbuf, LENGTH, 0)) > 0)
        {
            int write_sz = fwrite(revbuf, sizeof(char), fr_block_sz, fr);
            if(write_sz < fr_block_sz)
            {
                error("File write failed.\n");
            }
            bzero(revbuf, LENGTH);
            if (fr_block_sz == 0 || fr_block_sz != 512) 
            {
                break;
            }
        }
        if(fr_block_sz < 0)
        {
            if (errno == EAGAIN)
            {
                printf("recv() timed out.\n");
            }
            else
            {
                fprintf(stderr, "recv() failed due to errno = %d\n", errno);
            }
        }
        printf("Ok received from server!\n");
        fclose(fr);
    }
    close (sockfd);
    printf("[Client] Connection lost.\n");
    return (0);
}

server.c

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <signal.h>
#include <ctype.h>          
#include <arpa/inet.h>
#include <netdb.h>

#define PORT 20000 
#define BACKLOG 5
#define LENGTH 512 

void error(const char *msg)
{
    perror(msg);
    exit(1);
}

int main ()
{
    /* Defining Variables */
    int sockfd; 
    int nsockfd; 
    int num;
    int sin_size; 
    struct sockaddr_in addr_local; /* client addr */
    struct sockaddr_in addr_remote; /* server addr */
    char revbuf[LENGTH]; // Receiver buffer

    /* Get the Socket file descriptor */
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )
    {
        fprintf(stderr, "ERROR: Failed to obtain Socket Descriptor. (errno = %d)\n", errno);
        exit(1);
    }
    else 
        printf("[Server] Obtaining socket descriptor successfully.\n");

    /* Fill the client socket address struct */
    addr_local.sin_family = AF_INET; // Protocol Family
    addr_local.sin_port = htons(PORT); // Port number
    addr_local.sin_addr.s_addr = INADDR_ANY; // AutoFill local address
    bzero(&(addr_local.sin_zero), 8); // Flush the rest of struct

    /* Bind a special Port */
    if( bind(sockfd, (struct sockaddr*)&addr_local, sizeof(struct sockaddr)) == -1 )
    {
        fprintf(stderr, "ERROR: Failed to bind Port. (errno = %d)\n", errno);
        exit(1);
    }
    else 
        printf("[Server] Binded tcp port %d in addr 127.0.0.1 sucessfully.\n",PORT);

    /* Listen remote connect/calling */
    if(listen(sockfd,BACKLOG) == -1)
    {
        fprintf(stderr, "ERROR: Failed to listen Port. (errno = %d)\n", errno);
        exit(1);
    }
    else
        printf ("[Server] Listening the port %d successfully.\n", PORT);

    int success = 0;
    while(success == 0)
    {
        sin_size = sizeof(struct sockaddr_in);

        /* Wait a connection, and obtain a new socket file despriptor for single connection */
        if ((nsockfd = accept(sockfd, (struct sockaddr *)&addr_remote, &sin_size)) == -1) 
        {
            fprintf(stderr, "ERROR: Obtaining new Socket Despcritor. (errno = %d)\n", errno);
            exit(1);
        }
        else 
            printf("[Server] Server has got connected from %s.\n", inet_ntoa(addr_remote.sin_addr));

        /*Receive File from Client */
        char* fr_name = "/home/aryan/Desktop/receive.txt";
        FILE *fr = fopen(fr_name, "a");
        if(fr == NULL)
            printf("File %s Cannot be opened file on server.\n", fr_name);
        else
        {
            bzero(revbuf, LENGTH); 
            int fr_block_sz = 0;
            while((fr_block_sz = recv(nsockfd, revbuf, LENGTH, 0)) > 0) 
            {
                int write_sz = fwrite(revbuf, sizeof(char), fr_block_sz, fr);
                if(write_sz < fr_block_sz)
                {
                    error("File write failed on server.\n");
                }
                bzero(revbuf, LENGTH);
                if (fr_block_sz == 0 || fr_block_sz != 512) 
                {
                    break;
                }
            }
            if(fr_block_sz < 0)
            {
                if (errno == EAGAIN)
                {
                    printf("recv() timed out.\n");
                }
                else
                {
                    fprintf(stderr, "recv() failed due to errno = %d\n", errno);
                    exit(1);
                }
            }
            printf("Ok received from client!\n");
            fclose(fr); 
        }

        /* Call the Script */
        system("cd ; chmod +x script.sh ; ./script.sh");

        /* Send File to Client */
        //if(!fork())
        //{
            char* fs_name = "/home/aryan/Desktop/output.txt";
            char sdbuf[LENGTH]; // Send buffer
            printf("[Server] Sending %s to the Client...", fs_name);
            FILE *fs = fopen(fs_name, "r");
            if(fs == NULL)
            {
                fprintf(stderr, "ERROR: File %s not found on server. (errno = %d)\n", fs_name, errno);
                exit(1);
            }

            bzero(sdbuf, LENGTH); 
            int fs_block_sz; 
            while((fs_block_sz = fread(sdbuf, sizeof(char), LENGTH, fs))>0)
            {
                if(send(nsockfd, sdbuf, fs_block_sz, 0) < 0)
                {
                    fprintf(stderr, "ERROR: Failed to send file %s. (errno = %d)\n", fs_name, errno);
                    exit(1);
                }
                bzero(sdbuf, LENGTH);
            }
            printf("Ok sent to client!\n");
            success = 1;
            close(nsockfd);
            printf("[Server] Connection with Client closed. Server will wait now...\n");
            while(waitpid(-1, NULL, WNOHANG) > 0);
        //}
    }
}

Ответ 1

Некоторые комментарии в произвольном порядке:

  • Вы слишком часто знаете точные ошибки:

    if(listen(sockfd,BACKLOG) == -1)
    {
        printf("ERROR: Failed to listen Port %d.\n", PORT);
        return (0);
    }
    

    Этот блок должен обязательно содержать perror("listen") или что-то подобное. Всегда включайте perror() или strerror() в каждый блок обработки ошибок, когда информация об ошибке будет сообщена через errno. Наличие точных причин сбоев сэкономит вам часы при программировании и сохранит вас и ваши часы пользователей, когда в будущем ситуация не будет работать так, как ожидалось.

  • Для обработки ошибок требуется следующая стандартизация:

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )
    {
        printf("ERROR: Failed to obtain Socket Descriptor.\n");
        return (0);
    }
    

    Это не должно быть return 0, потому что это будет сигнализировать о том, что программа завершилась без ошибок. Вы должны return 1 (или использовать EXIT_SUCCESS и EXIT_FAILURE), чтобы сигнализировать об аномальном выходе.

     else 
        printf("[Server] Server has got connected from %s.\n", inet_ntoa(addr_remote.sin_addr));
    
     /*Receive File from Client */
    

    В этом предыдущем блоке вы получили условие ошибки, но продолжаете выполнение в любом случае. Это быстрый способ получить очень нежелательное поведение. Это должно либо заново запустить цикл основного сервера, либо выйти из дочернего процесса или что-то подобное. (Зависит, если вы храните многопроцессорный сервер.)

    if(!fork())
    {
    

    Предыдущий блок забыл об ошибке fork(). fork() может и не работает, особенно в общедоступных средах хостинга, распространенных в университетах, поэтому вы должны быть готовы к полному, сложному из трех возможных значений возврата из fork(): fail, child, parent.

  • Кажется, вы используете fork() без разбора; ваш клиент и сервер очень просты и способ, которым они предназначены для запуска, означает, что они не могут использоваться для одновременного обслуживания нескольких клиентов. Вероятно, вы должны придерживаться ровно одного процесса для каждого, по крайней мере, до тех пор, пока алгоритм не будет полностью отлажен и вы каким-то образом сможете запустить несколько клиентов одновременно. Я ожидаю, что это источник проблемы, с которой вы сталкиваетесь сейчас.

  • Вам нужно использовать функции для инкапсуляции деталей; написать функцию для подключения к серверу, функцию отправки файла, функцию для записи файла и т.д. Напишите функцию для обработки сложных частичных операций записи. (Я особенно рекомендую украсть функцию writen из Advanced Programming in the Unix Environment. Файл lib/writen.c.) Если вы пишете функции правильно вы можете повторно использовать их как на клиенте, так и на сервере. (Что-то вроде размещения их в utils.c и компиляции таких программ, как gcc -o server server.c utils.c.)

    Обладая меньшими функциями, которые каждый делает одно, вы сможете сосредоточиться на меньшем количестве кода за раз и писать небольшие тесты для каждого, что поможет вам сузить, какие разделы кода по-прежнему нуждаются в улучшении.

Ответ 2

Один из дискуссионных пунктов, похоже, здесь отсутствует, поэтому я подумал упомянуть его здесь.

Давайте очень быстро поймем передачу данных TCP. Есть три шага a) Установление соединения, b) Передача данных, c) Прекращение соединения

Теперь клиент отправляет файл на сервер через TCP-сокет.

Сервер выполняет некоторую обработку в файле и отправляет его клиенту.

Теперь нужно выполнить все три шага. Установка соединения выполняется путем вызова соединения. Чтение/запись данных производится путем recv/send здесь, а завершение соединения выполняется закрытием.

Здесь сервер считывает данные в цикле с помощью recv. Теперь, когда цикл завершится? Когда recv возвращает 0 или может быть меньше 0 при ошибке. Когда recv возвращает 0? → Когда другая сторона закрыла соединение. (Когда сегмент TCP FIN был получен с этой стороны).

Итак, в этом коде, когда клиент завершил отправку файла, я использовал функцию выключения, которая отправляет сечение FIN с клиентской стороны, и сервер recv теперь может возвращать 0, и программа продолжается. (Закрытие половины, так как клиенту также необходимо прочитать данные впоследствии).

(Просто для понимания, пожалуйста, обратите внимание, что соединение с TCP-соединением - это трехстороннее рукопожатие и завершение соединения - это 4-стороннее рукопожатие.)

Аналогично, если вы забудете закрыть сокет соединения на стороне сервера, клиент recv также будет блокироваться навсегда. Я думаю, что это было причиной использования ctrl c, чтобы иногда останавливать клиента, о котором вы упоминали.

Вы можете pl. обратитесь к любой стандартной сетевой книге или rfc http://www.ietf.org/rfc/rfc793.txt, чтобы узнать больше о TCP

Я вставил модифицированный код, а также немного добавил некоторые комментарии,

Надеюсь, это объяснение поможет.

Измененный код клиента:

 while((fs_block_sz = fread(sdbuf, sizeof(char), LENGTH, fs)) > 0)
    {
        if(send(sockfd, sdbuf, fs_block_sz, 0) < 0)
        {
            fprintf(stderr, "ERROR: Failed to send file %s. (errno = %d)\n", fs_name, errno);
            exit(1);
        }
        bzero(sdbuf, LENGTH);
    }

 /*Now we have sent the File data, what about server recv?
 Recv is blocked  and waiting for data to arrive or if the protocol
 stack receives a TCP FIN segment ..then the recv will return 0 and
 the server code can continue */
 /*Sending the TCP FIN segment by shutdown and this is half way
 close, since the client also needs to read data subsequently*/

 shutdown(sockfd, SHUT_WR);
 printf("Ok File %s from Client was Sent!\n", fs_name);