关于TCP协议传文件的例子
按照惯例,先来进行复习,这也是自学巩固的一个过程
首先是在工程文件PRO里,需要增加network,这个是引用TCP监听套接字和连接套接字的前提
第二部,在服务端头文件server.h进行基础的配置:
引入头文件: QTcpServer //监听套接字
QTcpSocket//通信套接字
QFile //需要操作文件,所以引入文件对象头文件
QTimer //引入定时器,为了防止在用TCP数据的时候,出现黏包情况
定义一个公共函数 sendData()//用于处理发送数据,
在私有区域,我们创建:
QTcpServer *tcpServer; //监听套接字对象
QTcpSocket *tcpSocket; //通信套接字对象
QFile file;//文件对象
QString fileName; //文件名字
qint64 fileSize; //文件大小
qint64 sendSize;//标记文件已经发送的大小
QTimer timer;//定时器对象
在客户端头文件client.h中,我们进行基础配置
引入头文件:
QTcpSocket//通信套接字
QFile //需要操作文件,所以引入文件对象头文件
在私有区域,我们创建:
QTcpSocket *tcpSocket;
QFile file;//文件对象
QString fileName; //文件名字
qint64 fileSize; //文件大小
qint64 recvSize;//标记文件已经接收的大小
bool isStart;//定义一个标志位,是否是正文
以上私有变量意后面会进行说明.在变量的后边,我做了简单的注释
首行给服务端画一个界面:
画上一个标签,一个编辑区,两个按钮,用弹簧进行共向的阻隔,然后整体采用网格的布局
server在发送文件给client的过程中,要先获取文件的信息,我们在serverWidget.cpp源文件中,我们引入头文件
#include <QFileDialog>//打开文件对框
#include <QDebug> //调试输入信息
#include <QFileInfo>//获取文件信息头文件
在主体函数中,进行一些初始化操作和动作的判断
前面写过一篇TCP学习的感想,所以第一步,先给指针型的监听对象分配动态空间,并且指定父对象,指定父对象,是为了方便程序结束的时候,进行空间回收.
//给监听套接字分配动态空间,指定父对象
tcpServer = new QTcpServer(this);
然后,在客户端没有连接前,先对选择文件和发送文件的两个按钮,先进行禁用.只有在连接成功时,才对其中的选择文件按钮进行解禁,当打开文件OK之后,再对发送文件进行解禁.
//两个按钮禁止使用
ui->buttonFile->setEnabled(false);
ui->buttonSend->setEnabled(false);
开始对所有IPV4的进行监听,服务器的IP地址为本地IP,也就是127.0.0.1,服务器的端口是8888
//监听
tcpServer->listen(QHostAddress::AnyIPv4,8888);
如果此时客户端与服务器进行连接,当连接成功的时候,监听套接字tcpServer会自动触发newConnection(),可以在这个函数下,进行一系列的判断和处理
//取出建立好的通信套接字
tcpSocket = tcpServer->nextPendingConnection();//通信套接字建立好后会返回一个值为tcpSocket
//获取对方的IP和端口
QString ip = tcpSocket->peerAddress().toString();//获取得对方的IP地址 peer对方
quint16 port = tcpSocket->peerPort();
//弹出信息,进行组包
QString str = QString("[%1:%2] 成功连接").arg(ip).arg(port);
//显示到编辑区
ui->textEdit->setText(str);
//成功连接后,才能按选择文件按钮
ui->buttonFile->setEnabled(true);
//判断客户端的文件是否接收完成
当服务端收到客户端的信息的时候,就会自动触发readyRead(),我们在这里面进行判断和处理
//取出客户端的信息
QByteArray buf = tcpSocket->readAll();//读取客户端发来的信息
//qDebug() << "返回信息:" << buf;
//判断文件有否接收完成
if (QString(buf) == "file done")
{
//文件接收已经完成
//在编辑区显示信息
ui->textEdit->append("文件发送完毕");
//关闭文件对象
file.close();
//断开与客户端连接
tcpSocket->disconnectFromHost();
//关闭通信套接字
tcpSocket->close();
}
不管是文件传送成功或者失败,在操作完成之后,我们都要对已经使用对象进行析构处理,比如关闭文件对象,关闭通信套接字.然后发送文件也就是点击发送按钮的时候,需要开启定时器,进行延时,防止TCP黏包,在这里就要进行关闭.
//关闭定时器
timer.stop();
//开始发送文件
sendData();
sendData()的功能
首先在发送文件的时候,先在编辑区内显示,正在发送文件,然后我们用循环的方式进行处理,文件被读了多少数据,就发送多少数据
do
{
//每次发送4K数据
char buff[4*1024] = {0};
len = 0;
//往文件中读数据,读了多少
len = file.read(buff, sizeof(buff));
//开始发送数据,读多少,发送多少
len = tcpSocket->write(buff,len);
//累积发送的数据,用sendSize记录
sendSize += len;
}while (len > 0);//判断每次发送的数,如果大于0,就说明数据还有,小于或等于0就是数据发送完毕
个人对于文件完整性的思考,在实际的开发中,也可以在发送前,我们调用MD5,对文件进行计算MD5的值,然后将其存直来,如果说,在客户端那里接收完成以后,再返回一个MD5的值,两值一致,就说明文件传送完成.否则就进行重传的处理.
然后就是打开文件按钮,进接转到槽函数
//获取文件路径
QString filePath = QFileDialog::getOpenFileName(this,"open","../");
//如果选择的文件有效
if (false == filePath.isEmpty())
{
//初使化信息,对文件名清空,文件大小清0
fileName.clear();
fileSize = 0;
//获取文件信息
QFileInfo info(filePath);//传入文件路径
fileName = info.fileName();//获取文件名
fileSize = info.size();//获取文件大小
sendSize = 0;//发送文件的大小
//只读方式打开
//指定文件的名字
file.setFileName(filePath);
//判断是否打开文件
bool isOk = file.open(QIODevice::ReadOnly);
if (false == isOk)
{
qDebug() << "只读方式打开文件失败! at line 114";
}
//提示打开文件的路径
ui->textEdit->append(filePath);
//文件如果成功打开,那么将打开文件按钮置为不可用,发送按钮置为可用
ui->buttonFile->setEnabled(false);
ui->buttonSend->setEnabled(true);
}
else
{
qDebug() << "选择的文件出错! at line 86";//调试输出,对文件路径进行判断
}
进qDebug()进行输出错误,这个是简易版的,QT中有专门处理错误的函数.定位到这个行,只是告诉自己,自己哪部门写的有问题,回头来可以进行排查.
然后就是发送文件,在点击了发送文件以后,不管是不是成功,都将此按钮进行禁进,以防止多次点击此按钮.
ui->buttonSend->setEnabled(false);
//先发送文件的头部信息 格式:文件名##文件大小
QString head = QString("%1##%2").arg(fileName).arg(fileSize);
//开始发送文件头部信息
//使用通信套接字发送,write(),此时会反回qint64的值,我们用一个变量接收这个信息
qint64 len = tcpSocket->write( head.toUtf8() );
if (len > 0) //头部信息接收成功
{
//发送真正的文件信息
//防止TCP黏包
//需要用定时器进行延时,不能用Sleep,延时20ms
//保证头先发
timer.start(20);
qDebug() << "Head =" << head;
}
else
{
qDebug() << "头部信息发送失败! at line 154";
//关闭文件对象
//选择文件按钮置可用,发送按钮置灰
file.close();
ui->buttonFile->setEnabled(true);
ui->buttonSend->setEnabled(false);
}
在发送文件这里,我们开启了定时器,并设定一个20ms的间隔,timer.start(20);开启定时器就是start(),关闭定时器就是stop().这样在发送了头文件以后,我们间隔一下,然后再发送后面的数据.怎么确定是头部信息,可以自行的定一个格式.格式:文件名##文件大小,这里用的是##的方式进行分隔,并且我们用一个变量进行判断,读取的头的大小是否为0
如果大于0,那么就说明我当前的头已经接收成功了,已经发过去了.
下面来画一下客户端的界面
提前在行编辑框里把服务器的地址和端口填好,这样不用每次都重新填写.
在客户端client.cpp源文件中,先引入头文件
#include <QMessageBox> //用到消息框,引入此头文件
#include <QHostAddress> //处理IP地址,IP地址都是这个类型
#include <QDebug> //输出调式信息
然后,后面的操作和在服务端差不多,先给通信套接字分配空间,TCP客户端是不需要监听套接字的,然后进标志位来确定 是否传的是正文,并且初始化一下进度条,还没有开始传文件的时候,进度条就是0
tcpSocket = new QTcpSocket(this);
//初始化标志位
isStart = true;
//当前进度条的值
ui->progressBar->setValue(0);
//连接状态
当与服务端连接时,在信号connected内判断是不是连拉成功.触发了此信号就是连接成功
//提示连接成功
ui->textEdit->clear();
ui->textEdit->setText("和服务器连接成功,等待服务器传送文件...");
//等待对方发消息,当对方发送消息时,tcpSocket会自动触发readyRead()
//取出接收内容
QByteArray buf = tcpSocket->readAll();//读取发送过来的所有信息
if (true == isStart)
{
//接收的是头部信息
isStart = false;
//解析头部信息 QString str = "hello##1024" 是这样的格式
//进行拆包
//QString str = "hello##1024";
//str.section("##",0,0); 表标拆包的间隔符为##,
//从第0段开始到第0段结束
//开始拆包
fileName = QString(buf).section("##", 0, 0);
fileSize = QString(buf).section("##", 1, 1).toInt();
recvSize = 0;//初始化已经接收到的数据大小
//打开文件
//关联文件名
file.setFileName(fileName);
//以只写方式打开
//设定一个变量来接收值判断是否打开
bool isOk = file.open(QIODevice::WriteOnly);
//判断打开文件是否成功
if (false == isOk)
{
qDebug() << "只写方式打开文件出错! at line 63";
tcpSocket->disconnectFromHost();
tcpSocket->close();
return;
}
//弹出对话框,显示接收文件的信息
//注意文件大小不要造成越界,可以对大小的值进行转换,从B转成KB
QString str = QString("接收的文件:[%1: %2kb]").arg(fileName).arg(fileSize/1024);
//QMessageBox::information(this,"接收文件信息",str);
ui->textEdit->append(str);
ui->textEdit->append("正在接收文件...");
//当接收到头文件后,对进度条进行初始化
//设置进度条
ui->progressBar->setMinimum(0);//设置最小值
ui->progressBar->setMaximum(fileSize/1024);//设置最大值
ui->progressBar->setValue(0);//设置当前值
}
else
{
//文件信息
qint64 len = file.write(buf);
if (len > 0) //接收数据大于0
{
//receiveSize获取累加len的值,表示已经接收的数据大小
recvSize += len;
qDebug() << len;
}
//组包,给服务端发送当前数据的接收大小值
// QString str = QString::number(recvSize);
// tcpSocket->write(str.toUtf8().data());
// qDebug() << "str = " << str;
//更新进度条
ui->progressBar->setValue(recvSize/1024);
//数据接收完毕
if (recvSize == fileSize)
{
//先给服务器发送传送文件完成信息
tcpSocket->write("file done");
ui->textEdit->append("文件接收完成!");
//弹出对话框,提示已经接收完成
QMessageBox::information(this,"提示","文件已经接收完成!");
//接收完成时,关闭文件对象
file.close();
//断开连接
tcpSocket->disconnectFromHost();
//关闭套接字
tcpSocket->close();
}
}
这里我是想给服务端传一个当前接收的数值,但是不知道为什么,只要这样写了,就会把filedone文件传送标志给覆盖,从而服务端迟迟收不到文件传送完的标志,一直提示文件发送中.不知道有没有看到
这篇文件的大佬给我解惑.这里打一个标记,后面我也自己思索一下原因.
连接按钮
//获取服务器的IP和端口
QString ip = ui->lineEditIp->text();
quint16 port = ui->lineEditPort->text().toInt();
这个判断是否输入了IP和端口的空值判断可能不是很规范,姑且先这样防止,没有IP和端口,我们就开点击连接了.
if (ip == NULL && port == 0)
{
QMessageBox::critical(this,"错误","IP地址和端口不能为空");
return;
}
else
{
//主动和服务器连接
tcpSocket->connectToHost(QHostAddress(ip),port);
isStart = true;
//设置进度条
ui->progressBar->setValue(0);
最后,我们还要在main.cpp入口文件用,将客户端的窗口进行实例,并进运行
至此一个简单的TCP文件传输就写好了.下面是运行的结果,
运行时
客户端和服务端进行连接
选择文件
在文件接收完成的提示框点击了OK以后,还可以做一些操作,比如初始化进度条,将编辑区内的信息进行清空.等等,后续学习完成了以后,可以考虑这个例子进行一定的功能升级.
这个例子虽然是我通过视频教程,照抄着编写的,但是其中也加入了一些自己的思考.把发送文件的流程过了一遍,而且也可以学习老师的编程习惯.对于新手,还是挺好的.
关于TCP协议传文件的例子的更多相关文章
- 用c++开发基于tcp协议的文件上传功能
用c++开发基于tcp协议的文件上传功能 2005我正在一家游戏公司做程序员,当时一直在看<Windows网络编程> 这本书,把里面提到的每种IO模型都试了一次,强烈推荐学习网络编程的同学 ...
- 一个简单的工具开发:从学生端更新程序部署工具说起,浅谈qt中自定义控件制作和调用、TCP协议下文件的收发 、以及可执行文件的打包
一个简单的工具开发:从学生端更新程序部署工具说起,浅谈qt中ui的使用和TCP协议下文件的收发.以及可执行文件的打包 写在前面,Qt Designer是一个非常操蛋的页面编辑器,它非常的...怎么说呢 ...
- 以一个上传文件的例子来说 DistributedFileSystem
public class UploadAndDown { public static void main(String[] args) { UploadAndDown uploadAndDown = ...
- day29 8_8 TCP上传文件socketserver的应用
一.文件上传 对于一些比较大的文件,当传输的数据大于内存时,显然,一次性将数据读取到内存中,在从内存传输到服务器显然时不可取的. 所以,在上传文件时,可以在with open打开文件,边读取文件边发送 ...
- go post 上传文件的例子
go post 上传文件 package main import ( "bytes" "fmt" "io" "mime/multi ...
- Netty--使用TCP协议传输文件
简介: 用于将文件通过TCP协议传输到另一台机器,两台机器需要通过网络互联. 实现: 使用Netty进行文件传输,服务端读取文件并将文件拆分为多个数据块发送,接收端接收数据块,并按顺序将数据写入文件. ...
- java 利用TCP上传文件
从客户端上传到服务器端,其实本质上也就是复制! package july76net; //上传文件(文本) import java.io.BufferedReader; import java.io. ...
- Java socket Tcp协议 实现文件传输
1.文件加密上传后发现文件已损坏: 原因:使用 read(byte[]) 方法不能够准确的获取到正确的字节数,有可能比 byte[].length 小,所以在解密的时候出现错误. 解决办法: 判断读取 ...
- PHP socket上传文件图片
最近了解了下下socket方面的东西,想做一个socket上传文件的例子. 在网上搜了搜代码执行后,图片数据传输了一半,图片的下半部分是灰色的.然后就自己仿着搜来的代码和php.net 中socket ...
- Javaweb向服务器上传文件以及从服务器下载文件的方法
先导入jar包 点击下载 commons-fileupload是Apache开发的一款专门用来处理上传的工具,它的作用就是可以从request对象中解析出,用户发送的请求参数和上传文件的流. comm ...
随机推荐
- mysql 删除商品名字重复数据,同时保留最新一条
delete goods where goods_id in (SELECT goods_id FROM goodswhere goods_name in (select goods_name fro ...
- HELM的使用
一.helm的主要功能 1.创建新的chart 2.chart打包成tgz格式 3.上传chart到chart仓库或从仓库中下载chart 4.在kubernetes集群中安装或卸载chart 5.管 ...
- 前端电商 sku 的全排列算法
需求 需求描述起来很简单,有这样三个数组: let names = ["iPhone",'iPhone xs'] let colors = ['黑色','白色'] let stor ...
- mac系统yarn使用报错:ERROR: add is not COMMAND nor fully qualified CLASSNAME.
出现错误的过程: mac 系统上使用阿里的X6(@antv/x6) x6 快速上手: 1 npm install @antv/x6 --save 2 yarn add @antv/x6 ...
- JS学习-常用的Web API
web API web-api 分类 链接 描述 操作文档的API 操作document dom,html,svg 从服务器获取数据的API XMLHttpRequest XMLHttpRequest ...
- Feign熔断
在Feign中使用 @EnableFeignClients中已经默认打开了断路器功能,所以这里的启动类上不需要再加@EnableCircuitBreaker注解 只需要在@FeignClient中为f ...
- Unity 设计模式-简单工厂模式和其他好用的通用代码块
1. 2.加法操作类 using System.Collections; using System.Collections.Generic; using UnityEngine; //加法操作类 pu ...
- 【Java】时间类型
Date 转 timeStamp long time = System.currentTimeMillis(); // 秒级 long time = System.currentTimeMillis( ...
- IDEA+SpringBoot整合Swagger2创建API文档
------------恢复内容开始------------ 1.创建SpringBoot项目 2.选择快捷方式创建springboot项目 3.工程文件树形图 4.pom.xml中导入Swagger ...
- 06 Spark SQL 及其DataFrame的基本操作
1.Spark SQL出现的 原因是什么? Spark SQL是Spark用来处理结构化数据的一个模块,它提供了一个叫作Data Frame的编程抽象结构数据模型(即带有Schema信息的RDD),S ...