Search code examples
csocketstcpnetwork-programmingclient-server

Resume file upload/download after lost connection (Socket programming) using c program


I need to RESUME the upload files from client to server after connection loss using tcp/ip socket in c program I'm not sure how to go about this.(Do not want the originally sent data to be resent) I would really appreciate it if anybody could give me suggestions as to how i should implement the file resume functionality in socket server client?


Solution

  • There is no such mechanism built into TCP or UDP. You need to make your server and client agree on specific terms. If you are thinking there is no such protocol, and you want to start it from scratch, then drop the idea. You can choose HTTP, which provides support for Partial-Content. For learning purposes, you can definitely try writing one on your own. Below I implemented one such bare minimal protocol:

    Rules:

    1) The client sends the file offset encoded as a 32-bit unsigned int in network byte order (At the start of the download, 0 is sent).

    2) The server reads the offset and converts to host byte order and sends the data from that particular offset.

    client.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <fcntl.h>
    
    int main()
    {
        /* Initialize the file */
    
        int fd = open("download", O_RDWR | O_CREAT);
    
        struct stat statbuf;
        fstat(fd, &statbuf);
    
        uint32_t cli_fsize = statbuf.st_size;
    
        /* Initialize the connection with the server */
    
        int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
    
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
        servaddr.sin_port = htons(55555);
    
        if (connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) != 0) {
            exit(1);
        }
    
        /* Write the file offset to the server */
    
        uint32_t n_cli_fsize = htonl(cli_fsize);
    
        int off_write = 0;
    
        for ( ; ; ) {
            int wr_return = write(sockfd, (void*) &n_cli_fsize + off_write, sizeof(n_cli_fsize) - off_write);
    
            if (wr_return <= 0)
                exit(1);
    
            off_write = off_write + wr_return;
    
            if (off_write == sizeof(n_cli_fsize))
                break;
        }
    
        /* Read the data from the socket and write to the file */
    
        char fbuf[100];
    
        for ( ; ; ) {
            int rd_return = read(sockfd, fbuf, sizeof(fbuf));
    
            if (rd_return < 0)
                exit(1);
            else if (rd_return == 0)
                break;
    
            int wr_status = 0;
    
            for ( ; ; ) {
                int wr_return = pwrite(fd, fbuf + wr_status, rd_return - wr_status, cli_fsize);
    
                if (wr_return < 0)
                    exit(1);
    
                wr_status = wr_status + wr_return;
                cli_fsize = cli_fsize + wr_return;
    
                if (wr_status == rd_return)
                    break;
            }
        }
    
        return 0;
    }
    

    server.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int main()
    {
        /* File initializations */
    
        FILE* fp = fopen("data.bin", "r+");
    
        if (fp == NULL)
            exit(1);
    
        struct stat statbuf;
    
        if (fstat(fileno(fp), &statbuf) != 0)
            exit(0);
    
        uint32_t fsize = statbuf.st_size;
    
        /* Initialize the server */
    
        int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), clifd, addr_len;
    
        struct sockaddr_in servaddr, cliaddr;
        memset(&servaddr, 0, sizeof(servaddr));
    
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(55555);
    
        if (bind(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) != 0) {
            exit(1);
        }
    
        listen(sockfd, 5);
    
        for ( ; ; ) {
    
            addr_len = sizeof(cliaddr);
    
            if ((clifd = accept(sockfd, (struct sockaddr*) &cliaddr, &addr_len)) < 0) {
                goto next_cli;
            }
    
            /* Read the 32 bit offset */
    
            uint32_t n_cli_fsize = 0;
            int off_read = 0;
    
            for ( ; ; ) {
                int rd_status = read(clifd, (void*) &n_cli_fsize, sizeof(n_cli_fsize) - off_read);
    
                if (rd_status == -1)
                    goto next_cli;
                else if (rd_status == 0 && off_read != sizeof(n_cli_fsize))
                    goto next_cli;
    
                off_read = off_read + rd_status;
    
                if (off_read == sizeof(n_cli_fsize))
                    break;
            }
    
            uint32_t cli_fsize = ntohl(n_cli_fsize);
    
            /* Read from the file and write to the socket */
    
            char fbuf[100];
    
            for ( ; ; ) {
                int rd_return = pread(fileno(fp), fbuf, sizeof(fbuf), cli_fsize);
    
                if (rd_return <= 0)
                    goto next_cli;
    
                cli_fsize = cli_fsize + rd_return;
    
                int wr_status = 0;
    
                for ( ; ; ) {
                    int wr_return = write(clifd, fbuf + wr_status, rd_return - wr_status);
    
                    if (wr_return <= 0)
                        goto next_cli;
    
                    wr_status = wr_status + wr_return;
    
                    if (wr_status == rd_return)
                        break;
                }
            }
    
            next_cli:
    
            if (clifd >= 0)
                close(clifd);
        }
    
        return 0;
    }
    

    data.bin

    $ du -h data.bin && md5sum data.bin 
    149M    data.bin
    6f188c0f60376fed5cfa00f55681b436  data.bin
    

    Note: The server is always running in all the terminal sessions.

    Terminal Session 1 (The client is allowed to completely download the file):

    $ du -h download 
    du: cannot access 'download': No such file or directory
    $ ./client 
    $ du -h download && md5sum download
    149M    download
    6f188c0f60376fed5cfa00f55681b436  download
    

    Terminal Session 2 (The client is interrupted in the midst of the download and resumed back):

    $ du -h download 
    du: cannot access 'download': No such file or directory
    $ ./client
    ^C
    $ du -h download && md5sum download
    80M     download
    0f77e56c7a512abc4ccafdb890f451a1  download
    $ ./client
    $ du -h download && md5sum download
    149M    download
    6f188c0f60376fed5cfa00f55681b436  download
    

    Terminal Session 3 (Running the client on a fully fetched file):

    $ du -h download  && md5sum download 
    149M    download
    6f188c0f60376fed5cfa00f55681b436  download
    $ ./client
    $ du -h download  && md5sum download 
    149M    download
    6f188c0f60376fed5cfa00f55681b436  download
    

    You can tweak around this base implementation, and add more features.