TCP的理論知識
TCP的特點:
- TCP是面向連接的運輸層協(xié)議。應(yīng)用程序在使用TCP協(xié)議之前,必須先建立TCP連接。在傳送數(shù)據(jù)完畢后,必須釋放已經(jīng)建立的TCP連接。
- 每一條TCP連接只能有兩個端點,每一條TCP連接只能是點對點的(一對一)。
- TCP提供可靠交付的服務(wù)。通過TCP 連接傳送的數(shù)據(jù),無差錯、不丟失、不重復(fù),并且按序到達(dá)。
- TCP提供全雙工通信。TCP允許通信雙方的應(yīng)用進(jìn)程在任何時候都能發(fā)送數(shù)據(jù)。TCP連接的兩端都設(shè)有發(fā)送緩存和接受緩存,用來臨時存放雙向通信的數(shù)據(jù)。
- 面向字節(jié)流。TCP中的"流"指的是流入到進(jìn)程或從進(jìn)程流出的字節(jié)序列。
滿足這些特點的規(guī)定
- 數(shù)據(jù)分片:在發(fā)送端對用戶數(shù)據(jù)進(jìn)行分片,在接收端進(jìn)行重組,由TCP確定分片的大小并控制分片和重組;
- 到達(dá)確認(rèn):接收端接收到分片數(shù)據(jù)時,根據(jù)分片數(shù)據(jù)序號向發(fā)送端發(fā)送一個確認(rèn);
- 超時重發(fā):發(fā)送方在發(fā)送分片時啟動超時定時器,如果在定時器超時之后沒有收到相應(yīng)的確認(rèn),重發(fā)分片;
- 滑動窗口:TCP連接每一方的接收緩沖空間大小都固定,接收端只允許另一端發(fā)送接收端緩沖區(qū)所能接納的數(shù)據(jù),TCP在滑動窗口的基礎(chǔ)上提供流量控制,防止較快主機(jī)致使較慢主機(jī)的緩沖區(qū)溢出;
- 失序處理:作為IP數(shù)據(jù)報來傳輸?shù)腡CP分片到達(dá)時可能會失序,TCP將對收到的數(shù)據(jù)進(jìn)行重新排序,將收到的數(shù)據(jù)以正確的順序交給應(yīng)用層;
- 重復(fù)處理:作為IP數(shù)據(jù)報來傳輸?shù)腡CP分片會發(fā)生重復(fù),TCP的接收端必須丟棄重復(fù)的數(shù)據(jù);
- 數(shù)據(jù)校驗:TCP將保持它首部和數(shù)據(jù)的檢驗和,這是一個端到端的檢驗和,目的是檢測數(shù)據(jù)在傳輸過程中的任何變化。如果收到分片的檢驗和有差錯,TCP將丟棄這個分片,并不確認(rèn)收到此報文段導(dǎo)致對端超時并重發(fā)。
多線程的知識點
多線程(multithreading),是指從軟件或者硬件上實現(xiàn)多個線程并發(fā)執(zhí)行的技術(shù)。具有多線程能力的計算機(jī)因有硬件支持而能夠在同一時間執(zhí)行多于一個線程,進(jìn)而提升整體處理性能。具有這種能力的系統(tǒng)包括對稱多處理機(jī)、多核心處理器以及芯片級多處理或同時多線程處理器。在一個程序中,這些獨立運行的程序片段叫作“線程”(Thread),利用它編程的概念就叫作“多線程處理”[1]
優(yōu)點
- 使用線程可以把占據(jù)時間長的程序中的任務(wù)放到后臺去處理 [2]
- 用戶界面可以更加吸引人,這樣比如用戶點擊了一個按鈕去觸發(fā)某些事件的處理,可以彈出一個進(jìn)度條來顯示處理的進(jìn)度 [2]
- 程序的運行速度可能加快 [2]
- 在一些等待的任務(wù)實現(xiàn)上如用戶輸入、文件讀寫和網(wǎng)絡(luò)收發(fā)數(shù)據(jù)等,線程就比較有用了。在這種情況下可以釋放一些珍貴的資源如內(nèi)存占用等 [2]
- 多線程技術(shù)在IOS軟件開發(fā)中也有舉足輕重的作用 [2]
缺點
- 如果有大量的線程,會影響性能,因為操作系統(tǒng)需要在它們之間切換 [2]
- 更多的線程需要更多的內(nèi)存空間 [2]
- 線程可能會給程序帶來更多“bug”,因此要小心使用 [2]
- 線程的中止需要考慮其對程序運行的影響 [2]
- 通常塊模型數(shù)據(jù)是在多個線程間共享的,需要防止線程死鎖情況的發(fā)生 [2]
創(chuàng)建工程
需要在工程文件pro中添加network庫
QT += core gui network
要使用lambda函數(shù)的話還需在工程文件在加入
CONFIG += C++11
TCP服務(wù)器
創(chuàng)建一個工作類繼承QObject類
接收文件的.h文件
#ifndef RECVFILE_H
#define RECVFILE_H
#include <QObject>
#include <QTcpSocket>
#include <QTcpServer>
#include <QFile>
class RecvFile : public QObject
{
Q_OBJECT
public:
explicit RecvFile(QObject *parent = nullptr);
//接收文件函數(shù)
void recvFile();
//啟動監(jiān)聽
void startListen(unsigned short port);
signals:
//接收完畢
void recvOver();
//通知主線程發(fā)送文件進(jìn)度百分比
void curPercent(int num);
public slots:
private:
QTcpServer *tcpServer;//監(jiān)聽套接字
QTcpSocket *tcpSocket;
QFile file;
QString filename;//文件名稱
qint16 filesize;//文件大小
qint16 recvfilesize;//已接收大小
bool isStart;//接收頭部標(biāo)記
};
#endif // RECVFILE_H
接收文件的.cpp文件
#include "recvfile.h"
#include <QFile>
#include <QDebug>
RecvFile::RecvFile(QObject *parent) : QObject(parent)
{
}
void RecvFile::startListen(unsigned short port)
{
qDebug()<<port;
//分配內(nèi)存空間
tcpServer = new QTcpServer(this);
tcpServer->listen(QHostAddress::Any,port);
connect(tcpServer,&QTcpServer::newConnection,[=]()
{
//取出建立好連接的套接字
tcpSocket = tcpServer->nextPendingConnection();
isStart = true;
connect(tcpSocket,&QTcpSocket::readyRead,this,&RecvFile::recvFile);
connect(tcpSocket,&QTcpSocket::disconnected,this,[=]()
{
//斷開連接
tcpSocket->close();
tcpSocket->deleteLater();
emit recvOver();
});
});
}
void RecvFile::recvFile()
{
//取出接收的內(nèi)容
QByteArray buf = tcpSocket->readAll();
if(true == isStart)
{
//接收頭部
isStart = false;
//解析頭部
filename = QString(buf).section("##",0,0);
filesize = QString(buf).section("##",1,1).toInt();
recvfilesize = 0;
//打開文件
file.setFileName(filename);
bool isOk = file.open(QIODevice::WriteOnly);
if(false == isOk)
{
return;
}
}
else
{
qint64 len = file.write(buf);
if(len>0)
{
recvfilesize += len;//累計接收大小
int percent = (static_cast<int>(recvfilesize)*100)/filesize;
//發(fā)出更新進(jìn)度條的信號
emit curPercent(percent);
}
if(recvfilesize == filesize)
{
file.close();
isStart = true;
}
}
}
主窗口的.h文件
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include<QTcpServer>
namespace Ui {
class MyWidget;
}
class MyWidget : public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = 0);
~MyWidget();
signals:
void startListen(unsigned short);
private slots:
void on_buttonlisten_clicked();
private:
Ui::MyWidget *ui;
QTcpServer *tcpServer;
};
#endif // MYWIDGET_H
主窗口的.cpp文件
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>
#include <QMessageBox>
#include <QFileDialog>
#include "recvfile.h"
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyWidget)
{
ui->setupUi(this);
//設(shè)置進(jìn)度條
ui->progressBar->setRange(0,100);
ui->progressBar->setValue(0);
//創(chuàng)建線程對象
QThread *thread = new QThread;
//創(chuàng)建任務(wù)對象
RecvFile *recvWork = new RecvFile;
//將任務(wù)對象移到子線程中
recvWork->moveToThread(thread);
connect(this,&MyWidget::startListen,recvWork,&RecvFile::startListen);
//更新進(jìn)度條
connect(recvWork,&RecvFile::curPercent,ui->progressBar,&QProgressBar::setValue);
//啟動線程
thread->start();
}
MyWidget::~MyWidget()
{
delete ui;
}
void MyWidget::on_buttonlisten_clicked()
{
//獲取端口
unsigned short port = ui->lineEdport->text().toUShort();
//發(fā)出連接信號
emit startListen(port);
}
TCP客戶端
發(fā)送文件的.h文件
#ifndef SENDFILE_H
#define SENDFILE_H
#include <QObject>
#include <QTcpSocket>
#include <QFile>
#include <QTimer>
class SendFile : public QObject
{
Q_OBJECT
public:
explicit SendFile(QObject *parent = nullptr);
//連接服務(wù)器
void connectServer(unsigned short port,QString ip);
//發(fā)送文件
void sendfile(QString path);
protected:
//發(fā)送文件數(shù)據(jù)
void sendfileData();
signals:
//通知主線程連接成功
void connectOK();
//通知主線程斷開連接
void gameover();
//通知主線程發(fā)送文件進(jìn)度百分比
void curPercent(int num);
public slots:
private:
QTcpSocket *tcpSocket;
QFile file;//文件對象
QString filename;//文件名字
qint16 filesize;//文件大小
qint16 sendfilesize;//已發(fā)送大小
QTimer *timer;
};
#endif // SENDFILE_H
發(fā)送文件的.cpp文件,利用一個QTimer對象做一個延時20ms,防止黏包問題,先創(chuàng)建一個頭部信息,包含文件名和文件大小,讓接收端先接收頭部信息,20ms后再接收數(shù)據(jù)部分。
#include "sendfile.h"
#include <QFile>
#include <QHostAddress>
#include <QFileInfo>
#include <QTimer>
#include <QDebug>
SendFile::SendFile(QObject *parent) : QObject(parent)
{
}
//連接服務(wù)器
void SendFile::connectServer(unsigned short port, QString ip)
{
//分配內(nèi)存空間
tcpSocket = new QTcpSocket;
//連接服務(wù)器
tcpSocket->connectToHost(QHostAddress(ip),port);
//通知主線程連接成功
connect(tcpSocket,&QTcpSocket::connected,this,&SendFile::connectOK);
//通知主線程斷開連接
connect(tcpSocket,&QTcpSocket::disconnected,this,[=]()
{
//斷開連接,釋放資源
tcpSocket->close();
tcpSocket->deleteLater();
emit gameover();
});
timer = new QTimer(this);
connect(timer,&QTimer::timeout,[=]()
{
//關(guān)閉定時器
timer->stop();
//發(fā)送文件數(shù)據(jù)
sendfileData();
});
}
//發(fā)送文件
void SendFile::sendfile(QString path)
{
file.setFileName(path);
//獲取文件信息
QFileInfo info(path);
filesize = info.size();//獲取文件大小
filename = info.fileName();//獲取文件名
//只讀方式打開文件
bool isOk = file.open(QIODevice::ReadOnly);
if(true == isOk)
{
//創(chuàng)建文件頭信息
QString head = QString("%1##%2").arg(filename).arg(filesize);
//發(fā)送頭部信息
qint64 len = tcpSocket->write(head.toUtf8());
if(len>0)
{
//防止tcp黏包問題,延時20ms
timer->start(20);
}
else
{
//發(fā)送失敗
file.close();
}
}
else
{
return;
}
}
void SendFile::sendfileData()
{
qint64 len = 0;
//讀多少發(fā)多少
do
{
//每次發(fā)送的大小
char buf[4*1024] = {0};
//記錄每行數(shù)據(jù)
len = file.read(buf,sizeof(buf));
//計算百分比,發(fā)給主線程
sendfilesize += len;
int percent = (static_cast<int>(sendfilesize)*100)/filesize;
//發(fā)出更新進(jìn)度條的信號
emit curPercent(percent);
//發(fā)送數(shù)據(jù)
tcpSocket->write(buf,len);
}while(len>0);
if(sendfilesize == filesize)
{
file.close();
filename.clear();
filesize = 0;
}
}
主窗口的.h文件
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
namespace Ui {
class MyWidget;
}
class MyWidget : public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = 0);
~MyWidget();
signals:
//連接信號
void startConnect(unsigned short,QString);
//發(fā)送文件信號
void sendFile(QString path);
private slots:
void on_bConnect_clicked();
void on_bSelectFile_clicked();
void on_bSend_clicked();
private:
Ui::MyWidget *ui;
};
#endif // MYWIDGET_H
主窗口的.cpp文件
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>
#include <QMessageBox>
#include <QFileDialog>
#include "sendfile.h"
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyWidget)
{
ui->setupUi(this);
//設(shè)置ip和端口
ui->lineEdIP->setText("127.0.0.1");
ui->lineEdPort->setText("8888");
//設(shè)置進(jìn)度條
ui->progressBar->setRange(0,100);
ui->progressBar->setValue(0);
//創(chuàng)建線程對象
QThread *thread = new QThread;
//創(chuàng)建任務(wù)對象
SendFile *sendWork = new SendFile;
//將任務(wù)對象移到子線程中
sendWork->moveToThread(thread);
//當(dāng)發(fā)送sendFile信號時,讓任務(wù)對象啟動發(fā)文件處理函數(shù)
connect(this,&MyWidget::sendFile,sendWork,&SendFile::sendfile);
//通過信號讓任務(wù)對象連接服務(wù)器
connect(this,&MyWidget::startConnect,sendWork,&SendFile::connectServer);
//處理連接成功的信號,彈出連接成功的提示
connect(sendWork,&SendFile::connectOK,this,[=]()
{
QMessageBox::information(this,"連接服務(wù)器","成功連接了服務(wù)器。");
});
//斷開連接
connect(sendWork,&SendFile::gameover,this,[=]()
{
//釋放資源
thread->quit();
thread->wait();
sendWork->deleteLater();
thread->deleteLater();
});
//更新進(jìn)度條
connect(sendWork,&SendFile::curPercent,ui->progressBar,&QProgressBar::setValue);
//啟動線程
thread->start();
}
MyWidget::~MyWidget()
{
delete ui;
}
void MyWidget::on_bConnect_clicked()
{
//獲取ip和端口
QString ip = ui->lineEdIP->text();
unsigned short port = ui->lineEdPort->text().toUShort();
//發(fā)出連接信號
emit startConnect(port,ip);
}
void MyWidget::on_bSelectFile_clicked()
{
QString path = QFileDialog::getOpenFileName();
//判斷路徑是否為空
if(path.isEmpty())
{
QMessageBox::warning(this,"打開文件","選擇路徑不能為空");
return;
}
//將路徑顯示在文本欄中
ui->lineEdfilePath->setText(path);
}
void MyWidget::on_bSend_clicked()
{
//發(fā)送文件
emit sendFile(ui->lineEdfilePath->text());
}
界面布局
發(fā)送端界面
接收端界面
項目結(jié)構(gòu)
總結(jié)
這個簡易的文件傳輸,發(fā)送端和接收端都是將數(shù)據(jù)處理部分放在了子線程,主線程只負(fù)責(zé)界面的更新和部分信號的發(fā)送,子線程處理完數(shù)據(jù)后發(fā)出信號告知主線程,讓主線程做出相對應(yīng)的處理,子線程通過繼承QObject類的方式,利用信號與槽的方式進(jìn)行啟動子線程處理函數(shù)。
謝箭,何小群,LABVIEW實用程序設(shè)計,西南交通大學(xué)出版社,2017.07,第125頁
胡璞編著,體育裝備嵌入式技術(shù),中國地質(zhì)大學(xué)出版社,2014.09,第259頁文章來源:http://www.zghlxwxcb.cn/news/detail-534350.html
開發(fā)借鑒 Qt實現(xiàn)基于多線程的文件傳輸(服務(wù)端,客戶端)文章來源地址http://www.zghlxwxcb.cn/news/detail-534350.html
到了這里,關(guān)于Qt多線程TCP服務(wù)器客戶端傳輸文件的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!