【Linux C | 网络编程】进程池大文件传输的实现详解(三)

上一篇实现了进程池的小文件传输,使用自定义的协议,数据长度+数据本身,类似小火车的形式,可以很好的解决TCP“粘包”的问题。

【Linux C | 网络编程】进程池小文件传输的实现详解(二)

当文件的内容大小少于小火车车厢的时候,上述代码的表现是非常完美的。但是如果一旦文件长度大于火车车厢大小,那么上述代码就无能为力了。
那么当传出大文件的时候有哪些办法呢?

1.使用循环来传输

最自然的思路解决大文件问题就是使用循环机制:发送方使用一个循环来读取文件内容,每当读取一定字节的数据之后,将这些数据的大小和内容填充进小火车当中;接收方就不断的使用 recv 接收小火车的火车头和车厢,先读取4 个字节的火车头,再根据车厢长度接收后续内容。

对于大文件的传输先要获取大文件的长度的信息,这里可以使用fstat()函数。

服务端发送大文件的流程:

1.获取大文件的长度信息

2.使用一个小火车先发送大文件的,文件名长度+文件名内容

3.发送大文件的内容,先发文件内容的长度信息,然后使用小火车循环发送文件的内容

#include "process_pool.h"#define FILENAME "bigfile.avi"//sendn函数可以发送确定的字节数
//sockfd:通信套接字,buff:要发送的内容,len:要发送的内容字节数
int sendn(int sockfd, const void * buff, int len)
{int left = len;const char* pbuf = buff;int ret = -1;while(left > 0) {ret = send(sockfd, pbuf, left, 0);if(ret < 0) {perror("send");return -1;}left -= ret;pbuf += ret;}return len - left;
}int transferFile(int peerfd)
{//读取本地文件int fd = open(FILENAME, O_RDONLY);ERROR_CHECK(fd, -1, "open");//获取文件的长度struct stat st;memset(&st, 0, sizeof(st));fstat(fd, &st);char buff[100] = {0};int filelength = st.st_size;        //获取文件的大小printf("filelength: %d\n", filelength);//进行发送操作//1. 发送文件名train_t t;memset(&t, 0, sizeof(t));t.len = strlen(FILENAME);strcpy(t.buf, FILENAME);sendn(peerfd, &t, 4 + t.len);//2. 再发送文件内容//2.1 发送文件的长度sendn(peerfd, &filelength, sizeof(filelength));int ret = 0;int total = 0;//2.2 再发送文件内容while(total < filelength) {memset(&t, 0, sizeof(t));ret = read(fd, t.buf, 1000);        //每次从文件读取1000个字节的内容,放到一个小火车上if(ret > 0) {t.len = ret;                    //初始化小火车的车头长度//sendn函数确保 4 + t.len 个字节的数据能正常发送ret = sendn(peerfd, &t, 4 + t.len);if(ret < 0) {printf(">> exit while not send.\n");break;//发生了错误,就退出while循环}total += (ret - 4);             //已发送内容,不包括车头}}return 0;
}

服务端接受流程:

1.先接受文件名内容

2.接受文件的内容

#include <func.h>int main()
{//创建客户端的套接字int clientfd = socket(AF_INET, SOCK_STREAM, 0);ERROR_CHECK(clientfd, -1, "socket");struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));//指定使用的是IPv4的地址类型 AF_INETserveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(8080);serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");//连接服务器int ret = connect(clientfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));ERROR_CHECK(ret, -1, "connect");printf("connect success.\n");//进行文件的接收//1. 先接收文件的名字//1.1 先接收文件名的长度int length = 0;ret = recv(clientfd, &length, sizeof(length), 0);printf("filename length: %d\n", length);//1.2 再接收文件名本身char buff[1000] = {0};ret = recv(clientfd, buff, length, 0);printf("1 recv ret: %d\n", ret);int fd = open(buff, O_CREAT|O_RDWR, 0644);ERROR_CHECK(fd, -1, "open");//2. 再接收文件的内容//2.1 先接收文件内容的长度ret = recv(clientfd, &length, sizeof(length), 0);printf("fileconent length: %d\n", length);int total = 0;int len = 0;//每一个分片的长度//2.2 再接收文件内容本身while(total < length) {recv(clientfd, &len, sizeof(len), 0);if(len != 1000) {printf("slice len: %d\n", len);//printf("total: %d bytes.\n", total);}memset(buff, 0, sizeof(buff));//recv函数无法保证每一次接收都能获取len个字节的长度//因此出现了读取长度异常的情况ret = recv(clientfd, buff, len, 0);// ret <= len//printf("slice %d bytes.\n", ret);if(ret > 0) {total += ret;write(fd, buff, ret);//写入本地文件}}close(fd);close(clientfd);return 0;
}

使用md5算法计算哈希值验证文件的正确性:

# client
$md5sum file2
# 计算md5码需要等待一段时间
8e9d11a16f03372c82c5134278a0bd7d file2
# server
$md5sum file2
8e9d11a16f03372c82c5134278a0bd7d file2

存在问题:

一般情况下上述方法确实可以传输完整的文件,但是存在一个大bug:recv函数无法保证每一次接收都能获取len个字节的长度,因此出现了读取长度异常的情况。

比如:内容只传输了一半,后续的数据就直接被当成长度了 出现了长度的偏差,导致传输出现问题,下一次循环开始时,本来希望读取的是长度信息,但其实读取的是内容,从而导致长度数据出现问题。

原因是:TCP是一种流式协议,它只能负责每个报文可靠有序地发送和接收,但是并不能保证传输到网络缓冲区当中的就是完整的一个小火车。这样就有可能会到导致数据读取问题,下面就举一个例子:假设发送方需要传输两个小火车,其中每个 车厢都是1000个字节,那么自然火车头都是4个字节,里面各自存储了1000 (当然是二进制形式),当 两个小火车发送到socket的时候,由于TCP是流式协议,所以小火车与小火车之间边界就不见了,到了 接收方这边, recv可能会先收到4个字节确定第一个小火车的车厢长度,再收到800字节,此时继续再 recv就会从第一个火车车厢中继续取出4个字节,那这4个字节显然就不是第二个小火车的车厢长度 了。

有以下解决方案:

1.1使用MSG_WAITALL(接收完整的长度数据)

recv函数用于从套接字接收数据。它的第四个参数是一个标志,用来控制接收操作的行为。

  • 如果将第四个参数设置为0或者使用MSG_WAITALL标志,recv函数会一直阻塞,直到接收到指定长度的数据。
  • 如果接收到的数据长度小于请求的长度,recv函数会一直阻塞直到接收完指定长度的数据或者发生错误。
#include <func.h>int main()
{//创建客户端的套接字int clientfd = socket(AF_INET, SOCK_STREAM, 0);ERROR_CHECK(clientfd, -1, "socket");struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));//指定使用的是IPv4的地址类型 AF_INETserveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(8080);serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");//连接服务器int ret = connect(clientfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));ERROR_CHECK(ret, -1, "connect");printf("connect success.\n");//进行文件的接收//1. 先接收文件的名字//1.1 先接收文件名的长度int length = 0;ret = recv(clientfd, &length, sizeof(length), 0);printf("filename length: %d\n", length);//1.2 再接收文件名本身char buff[1000] = {0};ret = recv(clientfd, buff, length, 0);printf("1 recv ret: %d\n", ret);int fd = open(buff, O_CREAT|O_RDWR, 0644);ERROR_CHECK(fd, -1, "open");//2. 再接收文件的内容//2.1 先接收文件内容的长度ret = recv(clientfd, &length, sizeof(length), 0);printf("fileconent length: %d\n", length);int total = 0;int len = 0;//每一个分片的长度//2.2 再接收文件内容本身while(total < length) {recv(clientfd, &len, sizeof(len), MSG_WAITALL);if(len != 1000) {printf("slice len: %d\n", len);//printf("total: %d bytes.\n", total);}memset(buff, 0, sizeof(buff));//将recv函数的第四个参数设置为MSG_WAITALL之后,//表示必须要接收len个字节的数据之后,才会返回ret = recv(clientfd, buff, len, MSG_WAITALL);// ret <= len//printf("slice %d bytes.\n", ret);if(ret > 0) {total += ret;write(fd, buff, ret);//写入本地文件}}close(fd);close(clientfd);return 0;
}

1.2每次循环发送和接受指定长度的数据

服务端发来多少客户端就接受多少,服务端封装一个发送指定大小数据的函数,客户端封装一个接收指定大小数据的函数。

客户端代码:

#include <func.h>//接收确定的字节数的数据
//sockfd:通信套接字,buff:接收的内容,len:接收内容的长度
int recvn(int sockfd, void * buff, int len)
{int left = len;char * pbuf = buff;int ret = -1;while(left > 0) {ret = recv(sockfd, pbuf, left, 0);if(ret == 0) {break;} else if(ret < 0) {perror("recv");return -1;}left -= ret;pbuf += ret;}return len - left;
}int main()
{//创建客户端的套接字int clientfd = socket(AF_INET, SOCK_STREAM, 0);ERROR_CHECK(clientfd, -1, "socket");struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));//指定使用的是IPv4的地址类型 AF_INETserveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(8080);serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");//连接服务器int ret = connect(clientfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));ERROR_CHECK(ret, -1, "connect");printf("connect success.\n");//进行文件的接收//1. 先接收文件的名字//1.1 先接收文件名的长度int length = 0;ret = recvn(clientfd, &length, sizeof(length));printf("filename length: %d\n", length);//1.2 再接收文件名本身char buff[1000] = {0};ret = recvn(clientfd, buff, length);printf("1 recv ret: %d\n", ret);int fd = open(buff, O_CREAT|O_RDWR, 0644);ERROR_CHECK(fd, -1, "open");//2. 再接收文件的内容//2.1 先接收文件内容的长度ret = recvn(clientfd, &length, sizeof(length));printf("fileconent length: %d\n", length);int total = 0;int len = 0;//每一个分片的长度//2.2 再接收文件内容本身while(total < length) {ret = recvn(clientfd, &len, sizeof(len));if(len != 1000) {printf("slice len: %d\n", len);//printf("total: %d bytes.\n", total);}memset(buff, 0, sizeof(buff));ret = recvn(clientfd, buff, len);if(ret != 1000) {//printf("slice %d bytes.\n", ret);}if(ret > 0) {total += ret;write(fd, buff, ret);//写入本地文件}}close(fd);close(clientfd);return 0;
}

1.3客户端断开连接   --- SIGPIPE信号的处理

现象:客户端断开连接时,导致服务器中的某一个子进程挂掉了,变成了僵尸进程,导致父子进程通信的管道被关闭了。而父进程一直监听该管道,因此epoll_wait不断返回,才有了服务器疯狂打印的情况出现。

通常情况下,如果程序向一个已经关闭写入的管道写数据,操作系统会发送 SIGPIPE 信号给进程,而默认的行为是终止该进程。但是有时候我们希望在这种情况下不让程序退出,而是希望处理其他错误或者采取其他措施。这时候就可以通过 signal(SIGPIPE, SIG_IGN); 来忽略 SIGPIPE 信号,让程序继续执行下去。

当客户端关闭时,服务器先执行第一次send操作,客户端会返回一个RST报文 当服务器的子进程再次发送第二次send操作时,会接收到SIGPIPE信号,导致子进程奔溃,从而导致子进程与父进程通信的管道也会关掉。

解决该问题:只需要让子进程忽略掉SIGPIPE信号即可。

1.4客户端打印文件传输的进度条

#include <func.h>//接收确定的字节数的数据
int recvn(int sockfd, void * buff, int len)
{int left = len;char * pbuf = buff;int ret = -1;while(left > 0) {ret = recv(sockfd, pbuf, left, 0);if(ret == 0) {break;} else if(ret < 0) {perror("recv");return -1;}left -= ret;pbuf += ret;}return len - left;
}int main()
{//创建客户端的套接字int clientfd = socket(AF_INET, SOCK_STREAM, 0);ERROR_CHECK(clientfd, -1, "socket");struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));//指定使用的是IPv4的地址类型 AF_INETserveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(8080);serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");//serveraddr.sin_addr.s_addr = inet_addr("192.168.30.129");//连接服务器int ret = connect(clientfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));ERROR_CHECK(ret, -1, "connect");printf("connect success.\n");//进行文件的接收//1. 先接收文件的名字//1.1 先接收文件名的长度int length = 0;ret = recvn(clientfd, &length, sizeof(length));printf("filename length: %d\n", length);//1.2 再接收文件名本身char buff[1000] = {0};ret = recvn(clientfd, buff, length);printf("1 recv ret: %d\n", ret);int fd = open(buff, O_CREAT|O_RDWR, 0644);ERROR_CHECK(fd, -1, "open");//2. 再接收文件的内容//2.1 先接收文件内容的长度ret = recvn(clientfd, &length, sizeof(length));printf("fileconent length: %d\n", length);int segment = length / 100;//百分之一的长度int lastSize = 0;#if 1int curSize = 0;int len = 0;//每一个分片的长度//2.2 再接收文件内容本身while(curSize < length) {ret = recvn(clientfd, &len, sizeof(len));memset(buff, 0, sizeof(buff));ret = recvn(clientfd, buff, len);if(ret > 0) {curSize += ret;write(fd, buff, ret);//写入本地文件if(curSize - lastSize > segment) {      //每百分之一打印一次//打印进度条printf("has complete %5.2f%%\r", (double)100 * curSize / length);fflush(stdout);lastSize = curSize;//更新上一次打印百分比时的长度}}}printf("has complete 100.00%%\n");
#endifclose(fd);close(clientfd);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/876311.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

html+css 动态效果

1.波浪效果 <div class"sitesingle"></div> <style>.sitesingle{display:flex;justify-content:space-between;align-items:stretch;overflow:hidden;position:relative;height: 400px;}keyframes bgRotate{0%{transform:rotate(0deg)}to{transfor…

基于关联规则的分类算法(CBA) | 项集、频繁项集、关联规则 | arulesCBA库

基于关联规则的分类算法 目前使用较多且较为简洁的关联规则分类算法是基于关联规则的分类算法&#xff08;Classification Based on Association, CBA&#xff09;&#xff0c;下面将从该算法的相关概念开始介绍。 这部分笔记参考论文&#xff1a;孙菡悦.基于多因素交互效应的…

C++的STL简介(一)

目录 1.什么是STL 2.STL的版本 3.STL的六大组件 4.string类 4.1为什么学习string类&#xff1f; 4.2string常见接口 4.2.1默认构造 ​编辑 4.2.2析构函数 Element access: 4.2.3 [] 4.2.4迭代器 ​编辑 auto 4.2.4.1 begin和end 4.2.4.2.regin和rend Capacity: 4.2.5…

repo中的default.xml文件project name为什么一样?

文章目录 default.xml文件介绍为什么 name 是一样的&#xff0c;path 不一样&#xff1f;总结 default.xml文件介绍 在 repo 工具的 default.xml 文件中&#xff0c;定义了多个 project 元素&#xff0c;每个元素都代表一个 Git 仓库。 XML 定义了多个不同的 project 元素&…

树和二叉树(不用看课程)

1. 树 1.1 树的概念与结构 树是⼀种非线性的数据结构&#xff0c;它是由 n&#xff08;n>0&#xff09; 个有限结点组成⼀个具有层次关系的集合。把它叫做树是因为它看起来像⼀棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 • 有⼀个特殊的结点&am…

GD32相较于STM32的优劣势-完全总结

优势 1.更高的主频 GD32单片机的主频可以达到108MHz&#xff0c;‌而STM32的最大主频为72MHz&#xff0c;‌这意味着GD32在代码执行速度上具有优势&#xff0c;‌适合需要快速处理数据的场景 2.更低的内核电压 GD32的内核电压为1.2V&#xff0c;‌而STM32的内核电压为1.8V。…

《系统架构设计师教程(第2版)》第12章-信息系统架构设计理论与实践-05-信息系统架构案例分析

文章目录 1. 价值驱动的体系结构——连接产品策略与体系结构1.1 价值模型1&#xff09;概述2&#xff09;价值驱动因素3&#xff09;传统方法识别价值模型的缺陷&#xff08;了解即可&#xff09; 1.2 体系结构策略&#xff08;挑战&#xff09;1&#xff09; 优先级的确定2&am…

【C++】动态内存管理与模版

目录 1、关键字new&#xff1a; 1、用法&#xff1a; 2、理解&#xff1a; 3、与malloc的相同与不同&#xff1a; 1、相同&#xff1a; 2、不同&#xff1a; 2、模版初阶&#xff1a; 1、函数模版&#xff1a; 1、概念&#xff1a; 2、关键字&#xff1a;template&…

微信公众号获取用户openid(PHP版,snsapi_base模式)

微信公众号获取用户openid的接口有2个&#xff1a;snsapi_base、snsapi_userinfo 详情见微信公众号开发文档&#xff1a;https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 本文介绍用PHP方式调用snsapi_base接口获取微信用户…

苦学Opencv的第十一天:图像的形态学操作

Python OpenCV从入门到精通学习日记&#xff1a;图像的形态学操作 前言 图像形态学是图像处理中的一个重要分支&#xff0c;主要关注图像中物体的形状和结构。通过形态学操作&#xff0c;我们可以对图像进行有效的分析和处理&#xff0c;例如图像的腐蚀与膨胀、开运算与闭运算…

ansible基础讲解和加密文件讲解

ansible最重要的三个文件 /etc/ansible/ansible.cfg #####ansible的配置文件 /etc/ansible/host ##清单文件inventory ansible-navigator.yml ####以yml结尾的文件可以理解为conf结尾的文件&#xff0c;是配置文件&#xff0c;用于设置剧本playbook playbook讲解 以.yml结…

vue3中计算属性

假如需要修改,需要使用get,set let a ref(111) import {computed} from vue let changeimg computed({get(){return a},set(val){a.value val}}) 如果不需要修改 let a ref(111) import {computed} from vue let changeimg computed(() >{return a })

135.分发糖果,遍历方向+candy选取的详解

力扣135分发糖果 题目思路代码 题目 https://leetcode.cn/problems/candy/description/ 老师想给孩子们分发糖果&#xff0c;有 N 个孩子站成了一条直线&#xff0c;老师会根据每个孩子的表现&#xff0c;预先给他们评分。 你需要按照以下要求&#xff0c;帮助老师给这些孩子…

WordPress原创插件:自定义文章标题颜色

插件设置截图 文章编辑时&#xff0c;右边会出现一个标题颜色设置&#xff0c;可以设置为任何颜色 更新记录&#xff1a;从输入颜色css代码&#xff0c;改为颜色选择器&#xff0c;更方便&#xff01; 插件免费下载 https://download.csdn.net/download/huayula/89585192…

【一图流】Git下载与安装教程

下载Git Git官网&#xff1a;https://git-scm.com/?hlzh-cn 安装Git

UE5 C++跑酷练习(Part2)

一.首先GameMode里有Actor数组&#xff0c;组装直线路&#xff0c;和左右路 #include "CoreMinimal.h" #include "GameFramework/GameModeBase.h" #include "RunGANGameMode.generated.h"UCLASS(minimalapi) class ARunGANGameMode : public AG…

揭秘企业为何钟情定制红酒:品牌形象与不同的礼品的双重魅力

在商务世界的广阔天地里&#xff0c;红酒不仅仅是一种饮品&#xff0c;更是一种传递情感、展示品味的不同媒介。近年来&#xff0c;越来越多的企业开始钟情于定制红酒&#xff0c;其中洒派红酒&#xff08;Bold & Generous&#xff09;通过其品质和个性化的定制服务&#x…

网络访问(Socket/WebSocket/HTTP)

概述 HarmonyOS为用户提供了网络连接功能&#xff0c;具体由网络管理模块负责。通过该模块&#xff0c;用户可以进行Socket网络通滚、WebSocket连接、HTTP数据请求等网络通信服务。 Socket网络通信&#xff1a;通过Socket(嵌套字)进行数据通信&#xff0c;支持的协议包括UDP核…

《追问试面试》系列开篇

我们不管做任何事情&#xff0c;都是需要个理由&#xff0c;而不是盲目去做。 为什么写这个专栏&#xff1f; 就像我们被面试八股文时&#xff0c;市面上有很多面试八股文&#xff0c;随便一个八股文都是500&#xff0c;甚至1000面试题。诸多面试题&#xff0c;难道我们需要一…

基于微信小程序+SpringBoot+Vue的资料分享系统(带1w+文档)

基于微信小程序SpringBootVue的资料分享系统(带1w文档) 基于微信小程序SpringBootVue的资料分享系统(带1w文档) 校园资料分享微信小程序可以实现论坛管理&#xff0c;教师管理&#xff0c;公告信息管理&#xff0c;文件信息管理&#xff0c;文件收藏管理等功能。该系统采用了Sp…