C TCP-сокеты, эхо-сервер с отправкой файлов, зависает после отправки файла

Я хотел написать простое приложение TCP echo server. Мне удалось выполнить часть эха, но у меня возникли проблемы с отправкой файлов между клиентом и сервером. Идея проста: несмотря на отправку обычных сообщений, клиент может отправить специальную команду на сервер (\ SENDFILE filename.txt), и после получения такой команды сервер должен запросить клиента для этого файла и получить файл с клиента. (Далее я хотел бы получить файл от одного клиента, а затем отправить его другому).

Я думаю, что "протокол" здесь прост, однако после ввода \SENDFILE на стороне клиента клиент зависает и не получает никаких дополнительных сообщений с сервера. Более того (сервер и клиент находятся в разных каталогах) на стороне сервера есть только пустой файл от клиента, без содержимого внутри.

Любые идеи, что здесь может быть неправильным?

client.c

#include<stdio.h> //printf
#include<string.h>    //
#include <sys/stat.h>
#include<sys/socket.h>    //socket
#include<arpa/inet.h> //inet_addr
#include <fcntl.h>
#define SERVER_PORT     9034
#define BUFF_SIZE       2000

int sendall(int s, char *buf, int len)
{
    int total = 0;
    int bytesleft = len;
    int n;

    while(total < len)
    {
        n = send(s, buf+total, bytesleft, 0);
        if (n == -1)
            break;
        total += n;
        bytesleft -= n;
    }

    return n==-1?-1:0;
}

void SendMsgToSender(char *msg, int connfd)
{
    write(connfd, msg, strlen(msg));
    memset(msg, 0, BUFF_SIZE);
}

int main(int argc , char *argv[])
{
    int sock;
    struct sockaddr_in server;
    char bufferOUT[BUFF_SIZE] , bufferIN[BUFF_SIZE];
    struct stat file_stat;

    memset(bufferOUT, 0, BUFF_SIZE);
    memset(bufferIN, 0, BUFF_SIZE);

    //Create socket
    sock = socket(AF_INET , SOCK_STREAM , 0);
    if (sock == -1)
    {
        printf("Could not create socket");
    }
    //  puts("Socket created");

    server.sin_addr.s_addr = inet_addr("127.0.0.1");
    server.sin_family = AF_INET;
    server.sin_port = htons( SERVER_PORT );

    //Connect to remote server
    if (connect(sock , (struct sockaddr *)&server , sizeof(server)) < 0)
    {
        perror("Connect failed. Error");
        return 1;
    }

    // puts("Connected\n");
    int read_size = 10;

    //keep communicating with server
    while(1)
    {
        printf("> ");
        fgets(bufferOUT, BUFF_SIZE, stdin);

        //Send some data
        if( send(sock , bufferOUT , BUFF_SIZE , 0) < 0)
        {
            perror("Send failed");
            return 1;
        }

        //Receive a reply from the server
        if( (read_size = recv(sock , bufferIN , BUFF_SIZE , 0)) < 0)
        {
            perror("Recv failed");
            break;
        }

        if(read_size == 0)
            break;

        if(bufferIN[0] == '\\')
        {
            char tmp[BUFF_SIZE], filename[BUFF_SIZE], *param;
            memset(filename, BUFF_SIZE, 0);
            strcpy(tmp, bufferIN);

            param = strtok(tmp, " ");
            if(param != NULL)
            {
                if(!strcmp(param, "\\GIVEMEFILE"))
                {
                    param = strtok(NULL, " ");
                    if(param != NULL)
                    {
                        strcpy(filename, param);
                        FILE * fp;
                        int nBytes;
                        char buffer[BUFF_SIZE], *s;
                        memset(buffer, 0, BUFF_SIZE);

                        fp = fopen(filename, "r");
                        if(fp == NULL)
                        {
                            perror("fopen");
                            fflush(stdout);
                            break;
                        }

                        int remain_data = file_stat.st_size;

                        do
                        {
                            s = fgets(buffer, BUFF_SIZE, fp);
                            if(s != NULL && buffer[0] != EOF)
                            {
                                nBytes = sendall(sock, buffer, BUFF_SIZE);
                                remain_data -= nBytes;
                            }
                            else
                                break;
                        }
                        while((s != NULL) && (nBytes > 0) && (remain_data > 0));
                        fclose(fp);

                        memset(bufferOUT, 0, BUFF_SIZE);
                        memset(bufferIN, 0, BUFF_SIZE);

                        continue;
                    }
                }
            }
        }
        else
        {
            printf("%s\n", bufferIN);
            fflush(stdout);
        }

        memset(bufferOUT, 0, BUFF_SIZE);
        memset(bufferIN, 0, BUFF_SIZE);
    }

    close(sock);
    return 0;
}

server.c

    #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <fcntl.h>

#define SERVER_PORT     9034
#define BUFF_SIZE       2000

void StripNewline(char *s)
{
    while(*s != '\0')
    {
        if(*s == '\r' || *s == '\n')
        {
            *s = '\0';
        }
        s++;
    }
}

void SendMsgToSender(char *msg, int connfd)
{
    write(connfd, msg, strlen(msg));
    memset(msg, 0, BUFF_SIZE);
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET)
    {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int GetFileFromClient(int connfd, char *filename)
{
    FILE * fp = NULL;
    int bytes;
    char buffer[BUFF_SIZE];
    memset(buffer, 0, BUFF_SIZE);

    fp = fopen(filename, "w");
    if(fp == NULL)
        return 0;

    memset(buffer, 0, BUFF_SIZE);
    sprintf(buffer, "\\GIVEMEFILE %s \r\n", filename);
    SendMsgToSender(buffer, connfd);

    while(1)
    {
        memset(buffer ,0 , BUFF_SIZE);
        if((bytes =  recv(connfd , buffer , BUFF_SIZE , 0) ) <= 0)
            return 0;
        else
            fprintf(fp, "%s\n", buffer);
    }

    fclose(fp);
    sleep(1);

    memset(buffer, 0, BUFF_SIZE);
    sprintf(buffer, "\r\n");
    SendMsgToSender(buffer, connfd);

    return 1;
}

int main(void)
{
    fd_set master;
    fd_set read_fds;
    int fdmax;

    int listener;
    int client_sock;
    struct sockaddr_storage remoteaddr;
    socklen_t addrlen;

    char bufferIN[BUFF_SIZE], bufferOUT[BUFF_SIZE], tmp[BUFF_SIZE], *datetime;
    int nbytes;

    char remoteIP[INET6_ADDRSTRLEN];

    int yes=1;
    int i, j, rv;

    struct addrinfo hints, *ai, *p;

    FD_ZERO(&master);
    FD_ZERO(&read_fds);

    memset(bufferIN, 0, BUFF_SIZE);
    memset(bufferOUT, 0, BUFF_SIZE);
    memset(tmp, 0, BUFF_SIZE);

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    char port[16] = "9034";
    if (getaddrinfo(NULL, port, &hints, &ai) < 0)
    {
        fprintf(stderr, "selectserver: %s\n", gai_strerror(rv));
        exit(1);
    }

    for(p = ai; p != NULL; p = p->ai_next)
    {
        listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (listener < 0)
        {
            continue;
        }

        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

        if (bind(listener, p->ai_addr, p->ai_addrlen) < 0)
            continue;

        break;
    }

    if (p == NULL)
        exit(2);

    freeaddrinfo(ai);

    if (listen(listener, 10) == -1)
    {
        perror("listen");
        exit(3);
    }

    FD_SET(listener, &master);
    fdmax = listener;

    printf("Server is running ...\n\n");

    for(;;)
    {
        read_fds = master;
        if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1)
        {
            perror("select");
            exit(4);
        }

        for(i = 0; i <= fdmax; i++)
        {
            if (FD_ISSET(i, &read_fds))
            {
                if (i == listener)
                {
                    addrlen = sizeof remoteaddr;
                    client_sock = accept(listener,
                                         (struct sockaddr *)&remoteaddr,
                                         &addrlen);

                    if (client_sock == -1)
                    {
                        perror("accept");
                    }
                    else
                    {
                        FD_SET(client_sock, &master);
                        if (client_sock > fdmax)
                            fdmax = client_sock;
                    }
                }
                else
                {
                    if ((nbytes = recv(i, bufferIN, BUFF_SIZE, 0)) <= 0)
                    {
                        if (nbytes == 0)
                            close(i);

                        else if(nbytes == -1)
                        {
                            perror("recv");
                            fflush(stdout);
                        }

                        close(i);
                        FD_CLR(i, &master);
                    }
                    else
                    {
                        bufferIN[nbytes-1] = '\0';
                        StripNewline(bufferIN);
                        strcpy(tmp, bufferIN);

                        if(bufferIN[0] == '\\')
                        {
                            char *command, *param;
                            command = strtok(bufferIN, " ");

                            if(!strcmp(command, "\\QUIT"))
                            {
                                close(i);
                                FD_CLR(i, &master);
                                break;
                            }

                            else if(!strcmp(command, "\\SENDFILE"))
                            {
                                param = strtok(tmp, " ");
                                if(param != NULL)
                                {
                                    param = strtok(NULL, " ");
                                    if(param != NULL)
                                    {
                                        printf("Client is sending me a file '%s'...\n", param);
                                        GetFileFromClient(i, param);
                                    }
                                }
                            }
                            else
                            {
                                SendMsgToSender(bufferIN, i);
                            }

                            memset(bufferIN, 0, BUFF_SIZE);
                            memset(bufferOUT, 0, BUFF_SIZE);
                        }
                        else
                        {
                            SendMsgToSender(bufferIN, i);
                        }
                    }
                } // END handle data from client
            } // END got new incoming connection
        } // END looping through file descriptors
    } // END for(;;)

    memset(bufferIN, 0, BUFF_SIZE);
    memset(bufferOUT, 0, BUFF_SIZE);

    return 0;
}

Ответ 1

strcpy(tmp, bufferIN);

Здесь вы предполагаете, что все, что было прочитано, было завершено с нулевой отметкой.

            param = strtok(tmp, " ");
            if(param != NULL)
            {
                if(!strcmp(param, "\\GIVEMEFILE"))

Здесь вы предполагаете, что получено целое сообщение.

                strcpy(filename, param);

То же.

                        memset(buffer, 0, BUFF_SIZE);

Бессмысленно. Удалить.

                        do
                        {
                            s = fgets(buffer, BUFF_SIZE, fp);

Здесь вы предполагаете, что файл состоит из строк.

                            if(s != NULL && buffer[0] != EOF)

Тестирование buffer[0] !=EOF бессмысленно. Если вы достигли EOF, s был бы нулевым, считая, что файл состоит из строк, но ничего не говорится о строке, в которой говорится о том, что может быть ее первым символом, кроме того, что это не ограничитель строки.

                        memset(bufferOUT, 0, BUFF_SIZE);
                        memset(bufferIN, 0, BUFF_SIZE);

Оба бессмысленных. Удалить.

        memset(bufferOUT, 0, BUFF_SIZE);
        memset(bufferIN, 0, BUFF_SIZE);

То же.

void StripNewline(char *s)

Этот метод кажется совершенно бессмысленным. Удалить.

void SendMsgToSender(char *msg, int connfd)
{
    write(connfd, msg, strlen(msg));

Здесь вы отправляете строку одноранговому узлу без конечного нуля, который сверстник ищет в strlen() выше. Подумайте о том, что в действительности использует ваш протокол приложений.

    memset(msg, 0, BUFF_SIZE);

Бессмысленно. Удалить.

int GetFileFromClient(int connfd, char *filename)
{
    FILE * fp = NULL;
    int bytes;
    char buffer[BUFF_SIZE];
    memset(buffer, 0, BUFF_SIZE);

Бессмысленно. Удалить.

    memset(buffer, 0, BUFF_SIZE);

То же.

    sprintf(buffer, "\\GIVEMEFILE %s \r\n", filename);
    SendMsgToSender(buffer, connfd);

    while(1)
    {
        memset(buffer ,0 , BUFF_SIZE);

Бессмысленно. Удалить.

        if((bytes =  recv(connfd , buffer , BUFF_SIZE , 0) ) <= 0)
            return 0;

Здесь вам нужно различать (1) bytes == 0, что означает, что одноранговое соединение отключено, и (2) byte == -1, что указывает на ошибку, которую вам нужно записать, через errno, strerror(), и друзей.

        else
            fprintf(fp, "%s\n", buffer);

Измените на fprintf(fp, "%.*s\n", bytes, buffer). Вы предполагаете, что все сообщения обнуляются по TCP. Это не так.

    sleep(1);

Бессмысленно. Удалить.

    memset(buffer, 0, BUFF_SIZE);

То же.

    sprintf(buffer, "\r\n");
    SendMsgToSender(buffer, connfd);

Отправка терминатора линии в точку доступа представляется совершенно бессмысленной.

    memset(bufferIN, 0, BUFF_SIZE);
    memset(bufferOUT, 0, BUFF_SIZE);
    memset(tmp, 0, BUFF_SIZE);

Все бессмысленно. Удалить.

        if (bind(listener, p->ai_addr, p->ai_addrlen) < 0)
            continue;

Здесь вам нужно распечатать ошибку mesage, а не просто игнорировать условие.

        if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1)

Вы не включили прослушивающий сокет в неблокирующий режим. Поэтому использование select() бессмысленно.

                        bufferIN[nbytes-1] = '\0';
                        StripNewline(bufferIN);

Почему?

                        strcpy(tmp, bufferIN);

Почему? Что случилось с продолжением использования bufferIN?

                        if(bufferIN[0] == '\\')
                        {
                            char *command, *param;
                            command = strtok(bufferIN, " ");

Здесь вы также предполагаете, что получена полная команда с завершающим нулем.

                            memset(bufferIN, 0, BUFF_SIZE);
                            memset(bufferOUT, 0, BUFF_SIZE);

Оба бессмысленных. Удалить. Это профессиональное программирование. recv() возвращает длину. Используйте его.

    memset(bufferIN, 0, BUFF_SIZE);
    memset(bufferOUT, 0, BUFF_SIZE);

То же, в пиках.

В основном у вас проблема с протоколом приложения. В частности, у вас нет протокола приложения. Просто много необоснованных предположений. Если вы хотите получить завершающий нуль, (а) отправить завершающий нуль и (b) чтение цикла, пока вы его не получите. У вас также есть предположение о содержании отправляемых файлов, что совершенно не нужно. Просто прочитайте байты из файла и отправьте их на сервер. Не требуется никаких предположений о строках или терминаторах линий. Если вы отправляете несколько файлов по одному и тому же соединению, вам нужно будет отправить размер файла перед файлом, чтобы получатель точно знал, сколько байтов будет читать и копировать в файл.

По сути, вам нужно полностью переосмыслить это.

Ответ 2

В client.c вы должны инициализировать file_stat перед тем, как получить размер файла stat(filename, &file_stat); Из-за этой ошибки remain_data всегда будет иметь неправильное значение.

В Server.c Из-за ошибки в цикле while, как указано EJP, вы переписываете файл, отправленный клиентом. По сути дела, он пуст. Откройте имя файла клиента с параметром "r". Откройте другой файл на сервере и получите данные в этот файл. Небольшой пример для получения данных из файлов в BUFF_SIZE. Вы можете использовать некоторую логику и расширить ее до больших файлов, например, сделанных в Client.c

     fd = fopen(<new_file_path>, "w");
        while(1)
        {
            memset(buffer ,0 , BUFF_SIZE);
            if((bytes =  recv(connfd , buffer , BUFF_SIZE , 0) ) == BUFF_SIZE)
                break;
        }
        fprintf(fd, "%s\n", buffer);
fclose(fd);