按照惯例,先来进行复习,这也是自学巩固的一个过程

首先是在工程文件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协议传文件的例子的更多相关文章

  1. 用c++开发基于tcp协议的文件上传功能

    用c++开发基于tcp协议的文件上传功能 2005我正在一家游戏公司做程序员,当时一直在看<Windows网络编程> 这本书,把里面提到的每种IO模型都试了一次,强烈推荐学习网络编程的同学 ...

  2. 一个简单的工具开发:从学生端更新程序部署工具说起,浅谈qt中自定义控件制作和调用、TCP协议下文件的收发 、以及可执行文件的打包

    一个简单的工具开发:从学生端更新程序部署工具说起,浅谈qt中ui的使用和TCP协议下文件的收发.以及可执行文件的打包 写在前面,Qt Designer是一个非常操蛋的页面编辑器,它非常的...怎么说呢 ...

  3. 以一个上传文件的例子来说 DistributedFileSystem

    public class UploadAndDown { public static void main(String[] args) { UploadAndDown uploadAndDown = ...

  4. day29 8_8 TCP上传文件socketserver的应用

    一.文件上传 对于一些比较大的文件,当传输的数据大于内存时,显然,一次性将数据读取到内存中,在从内存传输到服务器显然时不可取的. 所以,在上传文件时,可以在with open打开文件,边读取文件边发送 ...

  5. go post 上传文件的例子

    go post 上传文件 package main import ( "bytes" "fmt" "io" "mime/multi ...

  6. Netty--使用TCP协议传输文件

    简介: 用于将文件通过TCP协议传输到另一台机器,两台机器需要通过网络互联. 实现: 使用Netty进行文件传输,服务端读取文件并将文件拆分为多个数据块发送,接收端接收数据块,并按顺序将数据写入文件. ...

  7. java 利用TCP上传文件

    从客户端上传到服务器端,其实本质上也就是复制! package july76net; //上传文件(文本) import java.io.BufferedReader; import java.io. ...

  8. Java socket Tcp协议 实现文件传输

    1.文件加密上传后发现文件已损坏: 原因:使用 read(byte[]) 方法不能够准确的获取到正确的字节数,有可能比 byte[].length 小,所以在解密的时候出现错误. 解决办法: 判断读取 ...

  9. PHP socket上传文件图片

    最近了解了下下socket方面的东西,想做一个socket上传文件的例子. 在网上搜了搜代码执行后,图片数据传输了一半,图片的下半部分是灰色的.然后就自己仿着搜来的代码和php.net 中socket ...

  10. Javaweb向服务器上传文件以及从服务器下载文件的方法

    先导入jar包 点击下载 commons-fileupload是Apache开发的一款专门用来处理上传的工具,它的作用就是可以从request对象中解析出,用户发送的请求参数和上传文件的流. comm ...

随机推荐

  1. vue中常用的插件

    1. wangeditor 富文本编辑器,https://www.wangeditor.com/

  2. mybatis自增主键的获取

    实体类 package org.example.entity; public class User { private Integer id; private String name; private ...

  3. 基础篇之Markdown基础语法

    标题 1. # + 空格 + 标题名字 → 一级标题 2. ## + 空格 + 标题名字 → 二级标题 3. ### + 空格 + 标题名字 → 三级标题 ...... 6.###### + 空格 + ...

  4. Net6的核心组件之中间件

    Net6的核心组件之中间件 What is Middleware? 1.广义上来讲:Tomcat.WebLogic.Redis.IIS:狭义上来讲,ASP.NET Core中的中间件指ASP.NET ...

  5. jQuery 获取鼠标点击的元素ID

    $(function(){ $(document).click(function(e) { // 在页面任意位置点击而触发此事件 var v_id = $(e.target).attr('id'); ...

  6. (jmeter笔记)添加正则提取器和JSON提取器

    添加正则提取器和JSON提取器 1.正则提取器: 返回的Token如下: 引用名称:可以理解为变量名 //调用表示${变量名} 正则表达式:"Token":"(.+?)& ...

  7. Ubuntu tmux使用教程

    sudo apt-get install tmux 安装tmux tmux new -s session_name 新开一个会话 tmux a -t session_name 查看指定会话 tmux ...

  8. 源码安装pg9.6

    源码安装pg9.6 添加用户 useradd pg96 下载源码包 wget https://ftp.postgresql.org/pub/source/v9.6.24/postgresql-9.6. ...

  9. SpringBoot项目启动

    SpringBoot项目与其他项目启动方式有些不同. 查看是否是SpringBoot项目,可以查看在项目的pom.xml中是否有引入SpringBoot: 上图中就是对应的spring-boot.若有 ...

  10. mysql 删除数据所有表

    SELECT CONCAT('drop table ',table_name,';') FROM information_schema.`TABLES` WHERE table_schema='car ...