使用c程序在失去连接(套接字编程)后恢复文件upload/download

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

我需要在连接丢失后使用 c 程序中的 tcp/ip 套接字恢复从客户端到服务器的上传文件我不知道该怎么做。(不希望最初发送的数据是resent) 如果有人能给我建议我应该如何在套接字服务器客户端中实现文件恢复功能,我将不胜感激?

TCP 或 UDP 中没有内置这种机制。您需要让您的服务器和客户端就特定条款达成一致。如果您认为没有这样的协议,并且想从头开始,那么请放弃这个想法。您可以选择HTTP,它提供对Partial-Content的支持。出于学习目的,您绝对可以尝试自己编写一个。下面我实现了一个这样的最小协议:

规则:

1) 客户端以网络字节顺序发送编码为32位unsigned int的文件偏移量(在下载开始时,发送0)。

2) 服务器读取偏移量并转换为主机字节顺序并从该特定偏移量发送数据。

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

注意:服务器在所有终端会话中总是运行。

终端会话1(允许客户端完整下载文件):

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

终端会话 2(客户端在下载过程中中断并恢复):

$ 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

终端会话 3(运行 客户端在完全获取的文件上):

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

您可以调整此基本实现,并添加更多功能。