13

I am trying to make a file transfer between server and client, but is working very badly. Basically what needs to happen is:
1) The client send a txt file to the server (I called it "quotidiani.txt")
2) The server saves it in another txt file ("receive.txt")
3) The server runs a script on it that modifies it and saves it with another name ("output.txt")
4) The server send the file back to the client that saves it (on the same socket) with the name (final.txt)

The problem is that the first file (quotidiani.txt) is read just for a little part, and then there are some errors. I'd like someone to help me understand and correct my errors.

Here's my code:

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);
        //}
    }
}
Yu Hao
  • 119,891
  • 44
  • 235
  • 294
AscaL
  • 183
  • 3
  • 3
  • 11

2 Answers2

8

Some comments in no particular order:

  • You're passing up the opportunity to know exact errors too often:

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

    This block should definitely include a perror("listen") or something similar. Always include perror() or strerror() in every error handling block when the error details will be reported via errno. Having exact failure reasons will save you hours when programming and will save you and your users hours when things don't work as expected in the future.

  • Your error handling needs some further standardizing:

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

    This should not return 0 because that will signal to the shell that the program ran to completion without error. You should return 1 (or use EXIT_SUCCESS and EXIT_FAILURE) to signal an abnormal exit.

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

    In this preceding block you've gotten an error condition but continue executing anyway. That's a quick way to get very undesirable behavior. This should either re-start the main server loop or exit the child process or something similar. (Depends if you keep the multi-process server.)

    if(!fork())
    {
    

    The preceding block forgot to account for fork() failing. fork() can, and does fail -- especially in shared hosting environments common at universities -- so you should be prepared for the full, complicated three possible return values from fork(): failure, child, parent.

  • It appears you're using fork() indiscriminately; your client and server are both very simple and the way they are designed to run means they cannot be used to service multiple clients simultaneously. You should probably stick to exactly one process for each, at least until the algorithm is perfectly debugged and you figure out some way to run multiple clients simultaneously. I expect this is the source of the problem you're encountering now.

  • You need to use functions to encapsulate details; write a function to connect to the server, a function to send the file, a function to write the file, etc. Write a function to handle the complicated partial writes. (I especially recommend stealing the writen function from the Advanced Programming in the Unix Environment book's source code. File lib/writen.c.) If you write the functions correctly you can re-use them in both the client and server. (Something like placing them in utils.c and compiling the programs like gcc -o server server.c utils.c.)

    Having smaller functions that each do one thing will allow you to focus on smaller amounts of code at a time and write little tests for each that will help you narrow down which sections of code still need improvement.

sarnold
  • 102,305
  • 22
  • 181
  • 238
  • Hey, thank you for the tips. I don't really like programming, that's probably why i'm not very good at it. Anyway i want to get better so i'll try to follow your tips. If you don't mind could you elaborate the part of "It appears you're using fork() indiscriminately...."? I don't think i'm serving multiple clients, i call ./client just once and it should go on by iteself. And how can i stick to one process? Thanks again! PS: It's 4 in the morning here so if you answer is possible i won't answer back, don't take it personally! – AscaL Jun 29 '12 at 02:17
  • Think about what `fork()` does -- it gives you a copy of the program. Why does your client need a copy of itself? Why does your server need a copy of itself? You could probably remove the `fork()` entirely from both programs without ill effects. (It'd make _reasoning_ about the programs far easier.) – sarnold Jun 29 '12 at 02:19
  • I'll start by reminding you that i found this code around. i didn't write it and i didn't think too much about it (this to say that what i'm saying is probably gonna be wrong), but how can i get to the child if i don't use the fork()? For what i know (and i know is not much) when the server accept a connection it creates a new socket (so a new process) to manage the client connectiong so i use the result of the fork to get to that. Or maybe i'm just mistaken, i don't really know. – AscaL Jun 29 '12 at 02:29
  • There is no need to create a new process for every socket. That neither happens automatically nor is it desirable to tie the program's life cycle to the networking. Creating a new process for every client being served _can_ be a good way to go when creating small and simple servers, but you do not need that complication for a server designed to handle exactly one client. (The program specification of running a script with exactly-named files makes servicing multiple clients impossible, as it stands.) – sarnold Jun 29 '12 at 02:37
  • I think in theory the server could handle different clients, the server just does then same for everyone, he reads a file and send back another, is up to the client (user) to specify the pathname of the file. The script just count the occurrence of different words in the file, so i think multiple people could use that (not that is interesting or anything, i mean use it for the purpose of using it). Beside that, is there any chance you could point me to some manual or something for handling the client without a fork? or point me in a good direction, because i have no idea how i could do that. – AscaL Jun 29 '12 at 02:45
  • If you wanted to extend the protocol to include filenames -- or extend the problem definition to "count the words in the file" and leave vague the details about the filenames behind the scenes, then handling multiple clients simultaneously _would_ make sense. As for handling the client without the `fork()`, just try deleting that line of code and the corresponding `}` lower in the file. Really. :) – sarnold Jun 29 '12 at 02:55
  • Haha, I'll try that tomorrow when i wake. So you think i should keep the fork() or not? If i handle the failing and father can it give me many problems? Because the scope of this program is (or should be) to handle some sort of multi user, provided all the right input it should work for two, three people simultaneously, even tho i'll be happy with one (and i don't think i can make it in time to learn to let it handle more then one). It's 5 am and i gotta go sleep. Thanks a lot for all the tips, i'll try to use them tomorrow. Have a good day! – AscaL Jun 29 '12 at 03:06
  • I'd recommend getting rid of the `fork()` until you've got a program that works completely. Once you've got that program written and working correctly, handling multiple clients simultaneously with new processes will be far easier. – sarnold Jun 29 '12 at 03:07
  • I tried without the fork but is still the same, i think there must be something when i transfer the files (still don't know if on server or client) because i discovered that it writes the file from client to the server only for 512 bytes, meaning one buffer size, then goes on with the script, and after that it sends the file from server and receive on client only 512 bytes, again. So probably my write/read/send/recv rutines are wrong. Only i can't see where T_T – AscaL Jun 29 '12 at 13:54
  • I corrected the server/client receive and now the first file get sent completely and the script works on it. The problem is that to send this first file i have to ^C (terminate) the client process... i don't know why but if i don't ^C it just keep being stuck. As soon as i do that the server starts transferring the file and running the script. If anyone has any idea why i'm welcome to suggestions! Thx! – AscaL Jun 29 '12 at 16:52
  • I don't see anything that should require a `^C` on the client -- but this line looks suspect: `if (fr_block_sz == 0 || fr_block_sz != 512)` -- the remote peer may send any amount of data it pleases -- you may only have 300 bytes available to read. I'd suggest removing the `!= 512` check. – sarnold Jun 29 '12 at 22:16
  • yeah sorry man, i finished it and it works! the client was stuck in the while and i needed that to make it exit. Basically if you can send 512 bytes (the buffer) and the last time you send any amount it must be between 1 and 512 bytes, so after it wrote them it exit. Thank you very very much for all your help! :) – AscaL Jun 29 '12 at 22:59
6

One discussion point seems was missing here, So I thought to mention it here.

Let us very quicky understand TCP's data transfer. There are three steps a)Connection Establishment, b)Data Transfer, c)Connection Termination

Now here a client is sending a file to a server, over a TCP socket.

The server does some processing on the file and sends it back to the client.

Now all the 3 steps need to done. Connection Establishment is done by calling connect. Data reading/writing is done by recv/send here and connection termination is done by close.

The server here is reading data in a loop using recv. Now when the loop will come to an end? When the recv returns 0 or may be less than 0 on error. When recv returns 0? -> When the other side has closed the connection. (When the TCP FIN segement has been recived by this side).

So in this code when the client has finished sending the file,I have used a shutdown function, which sends the FIN segement from the client side and the server's recv can now return 0 and the program continues.(Half way close, since the client also needs to read data subsequently).

(Just for understanding please note TCP's connection Establisment is a 3 way Handshake and connection termination is a 4 way handshake.)

Similarly if you forget closing the connection socket on the server side, the client's recv will also block for ever. I think that was the reason for using ctrl c to stop the client sometimes which you have mentioned.

You may pl. refer any standard Networking book or the rfc http://www.ietf.org/rfc/rfc793.txt for learning more about TCP

I have pasted the modified code and also little added some comments,

Hope this explanation will help.

Modified client code:

 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's data, what about server's 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);