C++使用TCP SOCKET发送超大文件(超过2G)

本文介绍了一种使用 C++ 和 TCP Socket 进行超大文件(超过2GB)传输的方法。首先发送文件大小和文件名给客户端,然后逐块发送文件数据。客户端接收这些信息并保存文件。

C++使用TCP SOCKET发送超大文件(超过2G)

前几天有一个网友提出问题,如何使用socket传输超大文件。
之前虽然知道理论上该怎么处理,但并未在实际工作中使用过,毕竟现成的工具实在是太多了,没有自己开发的必要。但是想着既然给他回复了一些文字,何不写个demo让他看,不是更加直观吗?说干就干。

首先是服务端的开发。
1.首先要让客户端知道我们要发送的文件是多大,这就要在服务建立连接的时候,先将文件大小通过socket发送给客户端。
2.其次需要让客户端知道文件名是什么,这样客户端在保存的时候可以动态保存,而不是写死一个文件名。
3.最后就是文件内容的发送了,当然选择读取文件二进制,客户端使用二进制方式再保存最为稳妥。

服务端代码如下,windows下开发,包含了WinSock2.h头文件。

#include <iostream>
#include <stdio.h>
#include <WinSock2.h>


using namespace std;

//缓存大小设置不能超过2M
#define BUFF_SIZE (1024 * 1024)
#define FILE_NAME_LENGTH 1024


int s;                     /* socket for accepting connections    */
int ns;                    /* socket connected to client          */

int exitFunc() {
    closesocket(s);
    closesocket(ns);
}

off64_t getFileSize(char *filePath) {
    FILE *f;
    f = fopen(filePath, "rb");
    if (NULL == f) {
        printf("getFileSize fopen error\n");
        return -1;
    }

    if (0 != fseeko64(f, 0, SEEK_END)) {
        printf("getFileSize fseek error\n");
        return -1;
    }

    off64_t fileSize = ftello64(f);
    if (fileSize < 0) {
        printf("ftell error\n");
    }
    printf("fileSize:%lld\n", fileSize);
    fclose(f);
    return fileSize;
}

char *getFileName(char *filePath) {
    bool bFound = false;
    char *buff = new char[1024];
    memset(buff, 0, 1024);
    while (!bFound) {
        int lastIndex = 0;
        for (int i = 0; i < strlen(filePath); ++i) {
            if (filePath[i] == '\\' || filePath[i] == '/') {
                lastIndex = i;
            }
        }
        for (int i = lastIndex + 1; i < strlen(filePath); ++i) {
            buff[i - lastIndex - 1] = filePath[i];
        }
        bFound = true;
    }
    return buff;
}

int main(int argc, char **argv)
{
    _onexit(exitFunc);
    unsigned short port;       /* port server binds to                */
    char buff[BUFF_SIZE];              /* buffer for sending & receiving data */
    struct sockaddr_in client; /* client address information          */
    struct sockaddr_in server; /* server address information          */
    int namelen;               /* length of client name               */
    char *filePath = new char[FILE_NAME_LENGTH];

    //检查是否传入端口参数
    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s port\n", argv[0]);
        exit(1);
    }

    //第一个参数是端口号
    port = (unsigned short) atoi(argv[1]);
    //如果有第二个参数 第二个参数需要是文件的详细路径 否则需要自己指定路径
    if (argc > 2) {
        filePath = argv[2];
        printf("filePath from arg:%s\n", filePath);
    } else {
        //char *filePath = "D:\\Download\\qt-opensource-windows-x86-5.12.5.exe";
        //char *filePath = "D:\\Download\\ideaIC-2019.3.3.exe";
        filePath = "D:\\Download\\settings.xml";
    }

    off64_t fileSize = getFileSize(filePath);
    printf("fileSize:%lld\n", fileSize);
    char *fileName = getFileName(filePath);
    printf("fileName:%s\n", fileName);

    WSADATA wsadata;
    WSAStartup(0x202, &wsadata);

    //创建socket服务
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("socket error\n");
        exit(2);
    }

    //socket和服务地址绑定
    server.sin_family = AF_INET;
    server.sin_port   = htons(port);
    server.sin_addr.s_addr = INADDR_ANY;

    if (bind(s, (struct sockaddr *)&server, sizeof(server)) < 0)
    {
        printf("bind error\n");
        exit(3);
    }

    //监听服务,只允许一个客户端连接
    if (listen(s, 1) != 0)
    {
        printf("listen error\n");
        exit(4);
    }

    //等待连接
    namelen = sizeof(client);
    while (true) {
        //循环 一直等待客户端的连接
        if ((ns = accept(s, (struct sockaddr *)&client, &namelen)) == -1)
        {
            printf("accept error\n");
            exit(5);
        }

        //有客户端连接过来之后 将指定文件发送给客户端
        FILE *f;
        f = fopen(filePath, "rb");
        if (f == NULL) {
            printf("file:%s doesn't exist\n", filePath);
            exit(6);
        }

        off64_t sendSize = 0;
        //先将文件大小的数据发送给客户端
        lltoa(fileSize, buff, 10);
        if (send(ns, buff, sizeof(buff), 0) < 0) {
            printf("send fileSize to client error\n");
            exit(7);
        }
        //再将文件名发送给客户端
        printf("sizeof:%d strlen:%d\n", sizeof(fileName), strlen(fileName));
        if (send(ns, fileName, strlen(fileName), 0) < 0) {
            printf("send fileName to client error\n");
            exit(7);
        }
        while (sendSize < fileSize) {
            memset(buff, 0, 1024 * 1024);
            size_t iread = fread(buff, sizeof(char), BUFF_SIZE, f);
            printf("iread:%d\n", iread);
            if (iread < 0) {
                printf("fread error\n");
                fclose(f);
                break;
            }
            int iSend = send(ns, buff, iread, 0);
            if (iSend < 0) {
                printf("send error\n");
                fclose(f);
                break;
            }
            sendSize += iSend;
            printf("fileSize:%lld iSend:%d sendSize:%lld\n", fileSize, iSend, sendSize);
            fseeko64(f, sendSize, SEEK_SET);
        }
        fclose(f);
    }

    printf("Server ended successfully\n");
    exit(0);
}

客户端的开发
按照服务端的开发思路,客户端需要先接收文件大小,再接收文件名,最后接收文件内容。
代码如下:

#include <iostream>

#include <stdio.h>
#include <WinSock2.h>
#include <time.h>


using namespace std;

//缓存大小设置不能超过2M
#define BUFF_SIZE (1024 * 1024)

#define FILE_NAME_LENGTH 1024

int s;                     /* client socket                       */
int exitFunc() {
    closesocket(s);
    return 0;
}

/*
 * Client Main.
 */
int main(int argc, char** argv)
{
    _onexit(exitFunc);

    WSADATA wsadata;
    WSAStartup(0x202, &wsadata);
    printf("start...\n");

    unsigned short port;       //服务端口
    char buf[BUFF_SIZE];       //缓存
    struct hostent *hostnm;    //服务地址信息
    struct sockaddr_in server; //服务sockaddr信息

    //传入两个参数,顺序是服务器地址和端口
    if (argc != 3)
    {
        fprintf(stderr, "Usage: %s hostname port\n", argv[0]);
        exit(1);
    }

    //第一个参数是服务器地址
    hostnm = gethostbyname(argv[1]);
    if (hostnm == (struct hostent *) 0)
    {
        fprintf(stderr, "Gethostbyname failed\n");
        exit(2);
    }

    //第二个参数是端口号
    port = (unsigned short) atoi(argv[2]);

    //put the server information into the server structure.
    //The port must be put into network byte order.
    server.sin_family      = AF_INET;
    server.sin_port        = htons(port);
    server.sin_addr.s_addr = *((unsigned long *)hostnm->h_addr);

    //创建socket
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("Socket error\n");
        exit(3);
    }

    //准备连接服务端
    printf("ready to connet to server ...\n");
    if (connect(s, (struct sockaddr *)&server, sizeof(server)) < 0)
    {
        printf("Connect error\n");
        exit(4);
    }

    //先接收文件大小
    int iRecv = 0;
    memset(buf, 0, BUFF_SIZE);
    iRecv = recv(s, buf, BUFF_SIZE, 0);
    if (iRecv < 0) {
        printf("recv fileSize error\n");
        exit(5);
    }
    off64_t totalFileSize = atoll(buf);
    printf("totalFileSize:%lld\n", totalFileSize);
    //再接收文件名
    memset(buf, 0, BUFF_SIZE);
    iRecv = recv(s, buf, BUFF_SIZE, 0);
    if (iRecv < 0) {
        printf("recv fileName error\n");
        exit(5);
    }
    char fileName[FILE_NAME_LENGTH];
    memset(fileName, 0, FILE_NAME_LENGTH);
    memcpy(fileName, buf, strlen(buf));
    printf("recv fileName:%s\n", fileName);

    //接收文件 将文件保存到指定位置
    char *filePath = new char[FILE_NAME_LENGTH];
    memset(filePath, 0, FILE_NAME_LENGTH);
    char *basePath = "D:\\client\\";
    memcpy(filePath, basePath, strlen(basePath));
    strcat(filePath, fileName);
    printf("filePath:%s\n", filePath);

    FILE *f = NULL;
    f = fopen(filePath, "wb");
    if (f == NULL) {
        printf("file:%s doesn't exist and failed to create\n", filePath);
        exit(5);
    }

    off64_t fileRecv = 0;
    time_t start;
    start = time(NULL);

    while (fileRecv < totalFileSize) {
        memset(buf, 0, BUFF_SIZE);
        iRecv = recv(s, buf, BUFF_SIZE, 0);
        if (iRecv < 0)
        {
            printf("Recv error\n");
            exit(6);
        }
        if (iRecv == 0) {
            break;
        }
        fileRecv += iRecv;
        time_t end = time(NULL);
        time_t cost = end - start;
        //动态计算出传输完需要用时多久
        time_t totalTime = 0;
        //计算出剩余时间
        time_t leftTime = 0;
        if (cost != 0) {
            totalTime = totalFileSize / (fileRecv / cost);
            leftTime = (totalFileSize - fileRecv) / (fileRecv / cost);
        }
        printf("totalFileSize:%lld recv file size:%lld, totalTime:%d 's, leftTime:%d 's\n", totalFileSize, fileRecv, totalTime, leftTime);
        fwrite(buf, sizeof(char), iRecv, f);
    }
    fclose(f);

    printf("Client Ended Successfully\n");
    exit(0);
}

运行服务端时通过参数传入端口号,也可在代码中写死端口号。

运行客户端时通过参数传入服务地址、服务端口号,也可在代码中写死。

在这个过程中遇到一个有趣事情就是,比较小的文件传输没问题,但是大小超过2G的文件刚开始使用fseek和ftell获取文件大小时获取到的文件大小为-1,后来查询文档才知道要使用其他接口,fseeko64和ftello64。

由此来看,很多时候虽然理论上知道怎么做,但实际做起来,并没有想象中的那么顺利。这在现实生活中又何尝不是呢。

下载代码方式:https://pan.quark.cn/s/dd3561eca308 在软件开发领域,面向对象编程(OOP)是一种普遍采纳的结构化方法,它使得开发者能够借助模拟现实环境中的实体和关系来构建软件系统。在本案例中,我们观察到的是一个关于借助抽象类来执行不同几何图形面积求解的实践应用。现在,让我们详细分析这一议题。 标题 "应用抽象类计算面积" 清晰地表明我们将要讨论一个抽象类,此类设定了一个用于测量图形面积的标准函数,但并未提供实际的执行过程。抽象类在诸如C#或Java等编程语言中通常借助`abstract`修饰符进行声明,它们无法直接创建对象实例,仅能作为其他类的基础模板。 描述部分提及的"图形界面应用"暗示这是一个基于视觉用户界面(GUI)的系统,可能运用了.NET Framework的Windows Forms或WPF技术,或者是Java平台的Swing或JavaFX框架。在这样环境下,用户能够通过视觉元素与这些几何体进行互动,例如输入相关尺寸并观看到计算得出的面积值。 抽象类“几何体”内嵌了“计算面积”这一抽象函数。在代码层面,这可以被表述为: ```csharp public abstract class GeometricShape { public abstract double CalculateArea(); } ``` 随后,有三个派生类:圆(Circle)、矩形(Rectangle)和三角形(Triangle),它们各自提供了这个抽象函数的具体实现。比如,圆的面积是通过π乘以半径的平方得到的,矩形的面积是长和宽的乘积,而三角形的面积可能是底乘以高再除以2的结果。这些类将提供具体实现来计算它们各自的面积: ```csharp p...
内容概要:本文系统研究了移相控制全桥LLC谐振变换器的工作特性,深入分析其在不同工作模式下的运行机理与性能表现,重点探讨了软开关实现、高效率能量转换及宽范围电压调节等关键技术优势。通过Simulink搭建精确的仿真模型,对谐振腔参数、开关频率、电压增益、系统效率等关键指标进行仿真分析,验证了理论设计的正确性。同时,详细研究了移相控制策略对系统动态响应、稳定性和轻载/重载工况适应性的影响,揭示了控制参数与电路参数之间的耦合关系,为高频高效电源设计提供了理论依据和实践指导。; 适合人群:具备电力电子技术、模拟电路及自动控制理论基础,从事开关电源、新能源变换器、电动汽车充电模块或高频电源系统研发的工程师及高校研究生。; 使用场景及目标:①掌握全桥LLC谐振变换器的拓扑结构、工作原理与关键参数设计方法;②理解移相控制在实现零电压开通(ZVS)和零电流关断(ZCS)中的作用机制;③通过Simulink仿真掌握变换器建模、参数优化与性能评估流程,服务于实际产品开发与学术课题研究。; 阅读建议:建议读者结合提供的Simulink仿真模型进行同步操作,重点关注谐振网络(Lr, Lm, Cr)参数与移相角之间的匹配设计,深入理解软开关条件的形成过程,并通过调整负载和输入电压进行多工况仿真,以全面掌握系统动态特性。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

codears

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值