1. 概念

  • TCP(Transmission Control Protocol, 传输控制协议) 是一个用于数据传输的底层网络协议。
  • TCP 是一个面向数据流和连接的可靠的传输协议。

2. 服务器

在这里插入图片描述

  • tcpserver.pro
QT       += network
复制
  • server.h
#ifndef SERVER_H
#define SERVER_H

#include <QDialog>

class QTcpServer;

namespace Ui {
class Server;
}

class Server : public QDialog
{
    Q_OBJECT

public:
    explicit Server(QWidget *parent = 0);
    ~Server();

private:
    Ui::Server *ui;

    QTcpServer * tcpServer;

private slots:
    void sendMessage();
};

#endif // SERVER_H
  • server.cpp
#include "server.h"
#include "ui_server.h"
#include <QtNetwork>
#include <QDebug>

Server::Server(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Server)
{
    ui->setupUi(this);

    tcpServer = new QTcpServer(this);
    //监听连接
    //使用了IPv4的本地主机地址,等价于 QHostAddress("127.0.0.1")
    if(!tcpServer->listen(QHostAddress::LocalHost,6666))
    {
        qDebug()<<tcpServer->errorString();
        close();
    }
    connect(tcpServer,&QTcpServer::newConnection,this,&Server::sendMessage);
}

Server::~Server()
{
    delete ui;
}

void Server::sendMessage()
{
    //用于暂存要发送的数据
    QByteArray block;
    QDataStream out(&block,QIODevice::WriteOnly);
    //设置数据流的版本,客户端和服务器端使用的版本要相同
    out.setVersion(QDataStream::Qt_5_6);
    out<<(quint16)0;
    out<<tr("HelloTCP!");
    //跳转到数据块开头,然后将获取的大小信息填写到前面空留的两个字节处
    out.device()->seek(0);
    out<<(quint16)(block.size()-sizeof(quint16));
    //获取已经建立的连接的套接字
    QTcpSocket * clientConnection = tcpServer->nextPendingConnection();
    connect(clientConnection,&QTcpSocket::disconnected,
            clientConnection,&QTcpSocket::deleteLater);
    //发送数据
    clientConnection->write(block);
    clientConnection->disconnectFromHost();
    ui->label->setText(tr("发送数据成功"));
}

3. 客户端

在这里插入图片描述

  • tcpclient.pro
QT       += network
  • client.h
#ifndef CLIENT_H
#define CLIENT_H

#include <QDialog>
#include <QAbstractSocket>

class QTcpSocket;
namespace Ui {
class Client;
}

class Client : public QDialog
{
    Q_OBJECT

public:
    explicit Client(QWidget *parent = 0);
    ~Client();

private:
    Ui::Client *ui;
    
    QTcpSocket * tcpSocket;
    QString message;
    //存放数据的大小信息
    quint16 blockSize;
    
private slots:
    void newConnect();
    void readMessage();
    void displayError(QAbstractSocket::SocketError);  
    void on_pushButtonConnect_clicked();
};

#endif // CLIENT_H

  • client.cpp
#include "client.h"
#include "ui_client.h"
#include <QtNetwork>

Client::Client(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Client)
{
    ui->setupUi(this);
    
    tcpSocket = new QTcpSocket(this);
    //这里关联了两个信号到自定义槽上,当有可读的数据时,会发射readyRead()信号
    //当发生错误时,会发射error()信号
    connect(tcpSocket,&QTcpSocket::readyRead,this,&Client::readMessage);
    connect(tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),
            this,SLOT(displayError(QAbstractSocket::SocketError)));
}

Client::~Client()
{
    delete ui;
}

void Client::newConnect()
{
    blockSize = 0;
    tcpSocket->abort();
    tcpSocket->connectToHost(ui->lineEditHost->text(),
                             ui->lineEditPort->text().toInt());
}

void Client::readMessage()
{
    QDataStream in(tcpSocket);
    //设置数据流版本,这里要和服务器端相同
    in.setVersion(QDataStream::Qt_5_6);
    //如果是刚开始接收数据
    if(blockSize == 0)
    {
        //判断接受的数据是否大于两字节,也就是文件的大小信息所占的空间
        //如果是则保存到blockSize变量中,否则直接返回,继续接收数据
        if(tcpSocket->bytesAvailable()<(int)sizeof(quint16))
            return;
        in>>blockSize;
    }
    //如果没有得到全部的数据,则返回,继续接收数据
    if(tcpSocket->bytesAvailable()<blockSize)
        return;
    //将接受到的数据放到变量中
    in>>message;
    //显示接收到的数据
    ui->labelMessage->setText(message);
}

void Client::displayError(QAbstractSocket::SocketError)
{
    qDebug()<<tcpSocket->errorString();
}

void Client::on_pushButtonConnect_clicked()
{
    newConnect();
}

4. TCP服务器和客户端互传文件

在这里插入图片描述

  • client.h
#ifndef CLIENT_H
#define CLIENT_H

#include <QDialog>
#include <QAbstractSocket>
class QTcpSocket;
class QFile;

namespace Ui {
class Client;
}

class Client : public QDialog
{
    Q_OBJECT

public:
    explicit Client(QWidget *parent = 0);
    ~Client();

private:
    Ui::Client *ui;

    QTcpSocket *tcpClient;
    QFile *localFile;     // 要发送的文件
    qint64 totalBytes;    // 发送数据的总大小
    qint64 bytesWritten;  // 已经发送数据大小
    qint64 bytesToWrite;  // 剩余数据大小
    qint64 payloadSize;   // 每次发送数据的大小
    QString fileName;     // 保存文件路径
    QByteArray outBlock;  // 数据缓冲区,即存放每次要发送的数据块

private slots:
    void openFile();
    void send();
    void startTransfer();
    void updateClientProgress(qint64);
    void displayError(QAbstractSocket::SocketError);


    void on_openButton_clicked();
    void on_sendButton_clicked();
};

#endif // CLIENT_H
复制
  • client.cpp
#include "client.h"
#include "ui_client.h"
#include <QtNetwork>
#include <QFileDialog>

Client::Client(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Client)
{
    ui->setupUi(this);

    payloadSize = 64*1024; // 64KB
    totalBytes = 0;
    bytesWritten = 0;
    bytesToWrite = 0;
    tcpClient = new QTcpSocket(this);

    // 当连接服务器成功时,发出connected()信号,开始传送文件
    connect(tcpClient, SIGNAL(connected()), this, SLOT(startTransfer()));
    connect(tcpClient, SIGNAL(bytesWritten(qint64)),
            this, SLOT(updateClientProgress(qint64)));
    connect(tcpClient, SIGNAL(error(QAbstractSocket::SocketError)),
            this, SLOT(displayError(QAbstractSocket::SocketError)));
    ui->sendButton->setEnabled(false);
}

Client::~Client()
{
    delete ui;
}

void Client::openFile()
{
    fileName = QFileDialog::getOpenFileName(this);
    if (!fileName.isEmpty()) {
        ui->sendButton->setEnabled(true);
        ui->clientStatusLabel->setText(tr("打开文件 %1 成功!").arg(fileName));
    }
}

void Client::send()
{
    ui->sendButton->setEnabled(false);

    // 初始化已发送字节为0
    bytesWritten = 0;
    ui->clientStatusLabel->setText(tr("连接中…"));
    tcpClient->connectToHost(ui->hostLineEdit->text(),
                             ui->portLineEdit->text().toInt());
}


void Client::startTransfer()
{
    localFile = new QFile(fileName);
    if (!localFile->open(QFile::ReadOnly)) {
        qDebug() << "client: open file error!";
        return;
    }
    // 获取文件大小
    totalBytes = localFile->size();

    QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
    sendOut.setVersion(QDataStream::Qt_4_0);
    QString currentFileName = fileName.right(fileName.size()
                                             - fileName.lastIndexOf('/')-1);
    // 保留总大小信息空间、文件名大小信息空间,然后输入文件名
    sendOut << qint64(0) << qint64(0) << currentFileName;

    // 这里的总大小是总大小信息、文件名大小信息、文件名和实际文件大小的总和
    totalBytes += outBlock.size();
    sendOut.device()->seek(0);

    // 返回outBolock的开始,用实际的大小信息代替两个qint64(0)空间
    sendOut << totalBytes << qint64((outBlock.size() - sizeof(qint64)*2));

    // 发送完文件头结构后剩余数据的大小
    bytesToWrite = totalBytes - tcpClient->write(outBlock);

    ui->clientStatusLabel->setText(tr("已连接"));
    outBlock.resize(0);
}

void Client::updateClientProgress(qint64 numBytes)
{
    // 已经发送数据的大小
    bytesWritten += (int)numBytes;

    // 如果已经发送了数据
    if (bytesToWrite > 0) {
        // 每次发送payloadSize大小的数据,这里设置为64KB,如果剩余的数据不足64KB,
        // 就发送剩余数据的大小
        outBlock = localFile->read(qMin(bytesToWrite, payloadSize));

        // 发送完一次数据后还剩余数据的大小
        bytesToWrite -= (int)tcpClient->write(outBlock);

        // 清空发送缓冲区
        outBlock.resize(0);
    } else { // 如果没有发送任何数据,则关闭文件
        localFile->close();
    }
    // 更新进度条
    ui->clientProgressBar->setMaximum(totalBytes);
    ui->clientProgressBar->setValue(bytesWritten);
    // 如果发送完毕
    if(bytesWritten == totalBytes) {
        ui->clientStatusLabel->setText(tr("传送文件 %1 成功").arg(fileName));
        localFile->close();
        tcpClient->close();
    }
}

void Client::displayError(QAbstractSocket::SocketError)
{
    qDebug() << tcpClient->errorString();
    tcpClient->close();
    ui->clientProgressBar->reset();
    ui->clientStatusLabel->setText(tr("客户端就绪"));
    ui->sendButton->setEnabled(true);
}


// 打开按钮
void Client::on_openButton_clicked()
{
    ui->clientProgressBar->reset();
    ui->clientStatusLabel->setText(tr("状态:等待打开文件!"));
    openFile();

}

// 发送按钮
void Client::on_sendButton_clicked()
{
    send();
}
  • server.h
#ifndef SERVER_H
#define SERVER_H

#include <QDialog>
#include <QAbstractSocket>
#include <QTcpServer>
class QTcpSocket;
class QFile;

namespace Ui {
class Server;
}

class Server : public QDialog
{
    Q_OBJECT

public:
    explicit Server(QWidget *parent = 0);
    ~Server();

private:
    Ui::Server *ui;

    QTcpServer tcpServer;
    QTcpSocket *tcpServerConnection;
    qint64 totalBytes;     // 存放总大小信息
    qint64 bytesReceived;  // 已收到数据的大小
    qint64 fileNameSize;   // 文件名的大小信息
    QString fileName;      // 存放文件名
    QFile *localFile;      // 本地文件
    QByteArray inBlock;    // 数据缓冲区

private slots:
    void start();
    void acceptConnection();
    void updateServerProgress();
    void displayError(QAbstractSocket::SocketError socketError);


    void on_startButton_clicked();
};

#endif // SERVER_H

  • server.cpp
#include "server.h"
#include "ui_server.h"
#include <QtNetwork>

Server::Server(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Server)
{
    ui->setupUi(this);

    connect(&tcpServer, SIGNAL(newConnection()),
            this, SLOT(acceptConnection()));

}

Server::~Server()
{
    delete ui;
}

void Server::start()
{
    if (!tcpServer.listen(QHostAddress::LocalHost, 6666)) {
        qDebug() << tcpServer.errorString();
        close();
        return;
    }
    ui->startButton->setEnabled(false);
    totalBytes = 0;
    bytesReceived = 0;
    fileNameSize = 0;
    ui->serverStatusLabel->setText(tr("监听"));
    ui->serverProgressBar->reset();
}

void Server::acceptConnection()
{
    tcpServerConnection = tcpServer.nextPendingConnection();
    connect(tcpServerConnection, SIGNAL(readyRead()),
            this, SLOT(updateServerProgress()));
    connect(tcpServerConnection, SIGNAL(error(QAbstractSocket::SocketError)),
            this, SLOT(displayError(QAbstractSocket::SocketError)));
    ui->serverStatusLabel->setText(tr("接受连接"));
    // 关闭服务器,不再进行监听
    tcpServer.close();
}


void Server::updateServerProgress()
{
    QDataStream in(tcpServerConnection);
    in.setVersion(QDataStream::Qt_4_0);

    // 如果接收到的数据小于16个字节,保存到来的文件头结构
    if (bytesReceived <= sizeof(qint64)*2) {
        if((tcpServerConnection->bytesAvailable() >= sizeof(qint64)*2)
                && (fileNameSize == 0)) {
            // 接收数据总大小信息和文件名大小信息
            in >> totalBytes >> fileNameSize;
            bytesReceived += sizeof(qint64) * 2;
        }
        if((tcpServerConnection->bytesAvailable() >= fileNameSize)
                && (fileNameSize != 0)) {
            // 接收文件名,并建立文件
            in >> fileName;
            ui->serverStatusLabel->setText(tr("接收文件 %1 …")
                                           .arg(fileName));
            bytesReceived += fileNameSize;
            localFile = new QFile(fileName);
            if (!localFile->open(QFile::WriteOnly)) {
                qDebug() << "server: open file error!";
                return;
            }
        } else {
            return;
        }
    }
    // 如果接收的数据小于总数据,那么写入文件
    if (bytesReceived < totalBytes) {
        bytesReceived += tcpServerConnection->bytesAvailable();
        inBlock = tcpServerConnection->readAll();
        localFile->write(inBlock);
        inBlock.resize(0);
    }
    ui->serverProgressBar->setMaximum(totalBytes);
    ui->serverProgressBar->setValue(bytesReceived);

    // 接收数据完成时
    if (bytesReceived == totalBytes) {
        tcpServerConnection->close();
        localFile->close();
        ui->startButton->setEnabled(true);
        ui->serverStatusLabel->setText(tr("接收文件 %1 成功!")
                                       .arg(fileName));
    }
}

void Server::displayError(QAbstractSocket::SocketError socketError)
{
    qDebug() << tcpServerConnection->errorString();
    tcpServerConnection->close();
    ui->serverProgressBar->reset();
    ui->serverStatusLabel->setText(tr("服务端就绪"));
    ui->startButton->setEnabled(true);
}

// 开始监听按钮
void Server::on_startButton_clicked()
{
    start();
}

5. 资源下载

https://download.csdn.net/download/weixin_38566632/85187690


转载自:https://liuhui.blog.csdn.net/article/details/124289573