C++|TCP 客户端中发送文件
正常情况下,谁会用 TCP 传送一个文件呢?
一开始写我就觉得这个功能特别鸡肋,TCP 传送文件,没办法需要这样的功能,只要硬着头皮上了,如果不是有这个需求,我肯定会骂人,真不是多余嘛!
好了言归正传,直接说在 TCP 通讯中文件发送是如何应用的。
需要的功能:客户端 -->> 服务器 发送文件
开发环境:VS2017 + QT5.14.2
开发语言:C++
实现这个功能,我们会遇到哪些主要难解决的问题呢?
文件过大怎么办?
传输中断怎么解决?
传输过程中,界面卡死怎么处理?
文件发送过去,服务端如何处理呢?
以上是我在实现发送文件功能时遇到的问题,在后续文章也会对此一一解答。如果还有其他不懂的问题可以留言告诉我,一同探讨解决。
1:定义通讯协议
TCP 通讯中,连接服务器与客户端的桥梁便是通讯协议。定义一个简单明了的通信协议对我们实现功能来说也是事半功倍。
说到了发送文件,那么我们可以假设发送文件的通讯命令字是 A1。
首先,在发送文件内容之前,我们需要将文件的名称、大小发给服务器。
我们可以定义一个 json 结构发送
上述结构体中,fileName 存储了需要发送的文件名称,使用 string 类型表示;fileSize 存储了需要发送文件的大小。
其次,后续直接发送带有命令字 A1 的实际文件内容。为了快速传送,假设一次最多可发送 40960 个真实内容。
以上,我们就将发送文件的通讯指令建立了。下面开始直接使用
2:给服务器发送文件信息
2.1、读取需要发送文件的大小。
读文件的方式有很多种,例如说:QFile、ifstream、或者是 FILE 等等。
不知道大家在写程序的时候是个什么规范,就我个人而言,对于一些通讯类来说,绝大多数会采用纯 C++标准的方式,这样无论是使用 MFC 框架以及 QT 框架,甚至是 WIN32 程序都是可以兼容的。
2.2、将有效数据组成 json 结构体字符串数据
json 数据序列化以及反序列化的功能,大家应该都不陌生,这里我也写出来了。
在使用 json 功能之前,记得要添加 #include "json.h"
写法很简单,不用我再过多说明了。
但是需要注意一点的是:通讯过程中必须要使用 utf8 格式的编号。大家可以用 C++方法也可以用 QT 方法,QT 的方法会更快捷一些。毕竟在这里我用的是 VS+QT 的开发环境。
2.3、给服务器发送信息
以上代码是将 json 数据转成 char 数据数据进行发送的过程。
InitHeaderData():这里存放的是 20 位预留字节,是在开放过程中防止后续有添加,根据个人需求设置。
24、25、26 三个字节存放文件的大小,为了防止传送较大的文件。
对于我的理解来说,难点在于如何将 json 字符串数据赋值到 char 数组中,其他的都是很简单的。
3:给服务器发送文件内容
这一步骤是比较难理解的,涉及到了页面显示效果。
当我们发送一个很小的文件时,消息很快就发送了,几乎不会造成页面卡顿。但是在发送一个大一些的文件时,就会出现这个问题。
第 2 阶段实现完成后,我们需要进行以下操作向服务器发送有效文件内容。
3.1、我们需要将文件指针恢复到头位置
原因:是因为在读取整个文件大小时,文件指针的位置已经到了文件的末尾。发送实际内容时需要从开始发送
3.2、定义文件发送时需要的变量
在发送文件时,我们都需要知道哪些呢?
文件的总体大小 nTotalSize、记录每次读取的文件内容 chFileBuffer、文件指针移动的位置以及是否成功标识帮助我们跳出循环。
第 1 阶段已经说明了我们在每次读取时最多取 40960 个有效内容,这里的 g_nMaxSendByte = 40960;
3.3、读取文件并发送
读取文件之前我们需要将 chFileBuffer 中的旧数据置空,好习惯一定要养成。
获取读取当前发送的实际大小。
这是一个三目运算符操作,主要是我太懒了,不想写 if、else 语句,哈哈。
意思就是说:
当文件的总大小 < 40960 个长度时,读取的文件内容是文件的总大小,反之就是 40960 个长度
记录需要移动的文件指针大小
从文件中读取刚刚计算的内容
将读取的文件内容(chFileBuffer)发送给服务端。
发送方法和发送 json 数据的格式差不多,最主要的差别在于,这里是发送的是 char 数据。那么我就把如何将 char 数组赋值到 char*数据这部分说明下
发送成功文件后,记得从总文件大小中减去已经发送的大小。当文件大小为负数时,说明文件已经发送完成
将文件的指针进行偏移
在正常情况下,while 循环发送的主要内容就这些了。
为什么说是正常情况呢?万一文件传输过程中,与服务器断开连接了呢?这时,我们就需要判断 socket 是否有有效数据,如果 socket == INVALID_SOCKET,也需要跳出循环
接下来展示整体的循环发送文件内容
4:界面卡死处理
当我们使用上述方式发送一个较大的文档时,肯定会出现界面卡死的现象。这也是 C++在程序处理过程中最头疼的一部分了。
开线程,这是我第一次在写这个功能时第一时间想到的解决方法。
但是,我又想到假设我写的这个程序中有很多加载数据慢的地方,也不一定都是传送文件,可能是其他的功能。我还有线程满天飞吗?
仔细想想我都头疼。后来我采用的是"QtConcurrent::run"的方法,这属于 QT 中快速建立线程的方式,但是使用这种方式的时候,所调用的函数的返回值一定是 True,否则就陷入了死循环中。
在发送文件时,一遍加载等待页面一遍发送文件内容,使用进程的方式处理,比开线程简单多了。
使用 TCP 客户端发送数据就讲解完成了~
我是中国好公民 st,一名 C++开发程序媛~
版权声明: 本文为 InfoQ 作者【中国好公民st】的原创文章。
原文链接:【http://xie.infoq.cn/article/656ab7cbb151412ab20282409】。文章转载请联系作者。
评论