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

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

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

首先是服务端的开发。

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

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

#include <iostream>
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib,"WS2_32.lib")
#pragma warning(disable:4996)

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>
#pragma comment(lib,"WS2_32.lib")
#pragma warning(disable:4996)

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。

由此来看,很多时候虽然理论上知道怎么做,但实际做起来,并没有想象中的那么顺利。这在现实生活中又何尝不是呢。
————————————————
版权声明:本文为CSDN博主「codears」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/codears/article/details/111475389

  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中,可以使用原生的TCP socket来实现WebSocket通信。当接收到WebSocket消息时,可以通过读取TCP socket数据流来判断是否为分片消息。 WebSocket消息分为头部和内容两部分,头部包含了消息的一些基本信息,内容则是实际的消息数据。头部的第一个字节包含了消息的一些标识信息,其中最后一个比特位(即第8个比特位)表示该消息是否为分片消息。如果最后一个比特位为0,则表示该消息为完整消息;如果最后一个比特位为1,则表示该消息为分片消息。 以下是一个示例代码,演示了如何使用原生的TCP socket来判断分片消息: ```c++ #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> void on_message(int sockfd) { char buf[4096]; int n = read(sockfd, buf, sizeof(buf)); if (n <= 0) { // 读取失败,处理异常 return; } int fin = (buf[0] & 0x80) >> 7; if (fin == 0) { // 当前接收到的消息是分片消息 int opcode = buf[0] & 0x0F; switch (opcode) { case 0x01: // 处理文本消息 break; case 0x02: // 处理二进制消息 break; // 其他消息类型的处理 } } else { // 当前接收到的消息是完整消息 int opcode = buf[0] & 0x0F; switch (opcode) { case 0x01: // 处理文本消息 break; case 0x02: // 处理二进制消息 break; // 其他消息类型的处理 } } } int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in servaddr; bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(9002); inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); on_message(sockfd); close(sockfd); return 0; } ``` 需要注意的是,以上代码只是演示了如何判断分片消息,实际应用中还需要根据WebSocket协议规范来处理完整的消息和分片消息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值