Qt5.4.1 局域網tcp文件傳輸工具帶界面(含源碼下載)

目錄

1.程序設計

2.程序效果

服務端

客戶端

3.代碼設計

服務端

客戶端

4.實驗可改進的地方:


點擊下載例程源碼 

1.程序設計

在同一局域網內的兩個設備,基於tcp網絡編程,實現可靠的、高速的文件傳輸,並且實時顯示傳輸進度和速度;採用客戶端、服務端形式,滿足雙向傳輸;具有可擴展性、可移植性。實測傳輸速度可達到9Mb/s。

2.程序效果

服務端

        監聽端口:當計算機網絡底層收到tcp信息時,通過端口傳遞給相應的程序進行處理,也就是說一個端口只能被一個應用程序使用,但一個應用程序可以使用多個端口。
         選擇監聽端口(爲了避免已被其他程序使用,可設大一點),點擊打開服務器,可更改接收文件的保存路徑,等到客戶端連接,客戶端連接成功後,可用鼠標拖動文件至中間空白處,即可將文件傳輸到客戶端。如下圖:

                                    

客戶端

輸入服務端的ip地址(下面的是我的ip,請根據服務端本地ip修改)和監聽的端口號,服務端的ip地址查看:右擊電腦右下角網絡圖標->打開網絡和共享中心->本地連接->詳細信息->IP4地址,正確填寫後點擊連接服務器,服務器端將顯示客戶端已連接,接下來就可以開始文件傳輸了。

                                     

程序窗口設計爲一直處於前置,以便拖拉文件;服務端、客戶端同時運行在同一電腦上時,也可以進行文件的傳輸(等於文件移動);實際的速度會存在差異,跟電腦配置和網絡狀態有關。 

3.代碼設計

設計要點:

  • QTcpSocket編程
  • QFile文件操作
  • UI界面設計

程序分爲客戶端、服務端兩部分:

服務端

1.初始化時,創建QTcpServer對象,並將綁定newConnection信號,當有客戶端連接時進入do_connect():

    tcpServer = new QTcpServer(this);
    connect(tcpServer,SIGNAL(newConnection()),this,SLOT(do_connect()));

2.點擊“打開服務器”按鈕後,獲取輸入框填寫的端口號,調用listen進行端口綁定並開始監聽客戶端的連接:

void MainWindow::on_pushButton_clicked()
{
    if(ui->pushButton->text()==QString("服務器已打開")){
    }else{
        qint32 port=ui->lineEdit_2->text().toInt(NULL,10);
        if(!tcpServer->listen(QHostAddress::Any,port))//開始監聽
        {
            QMessageBox::warning(this,tr("警告"), tcpServer->errorString());
            tcpServer->close();
            return;
        }else{
            //ui->lineEdit->setText(read_ip_address());
            ui->label_3->setText("未有客戶端連接");
            ui->pushButton->setText("服務器已打開");
            qDebug() <<"【服務端】開始監聽端口:"<<port;
        }
     }
}

 3.當有客戶端連接時進入do_connect接口,調用nexPenddingConnection獲取當前連接的客戶端的套接字,進行斷開信號和可讀信號的綁定,當連接發生斷開時進入槽函數do_disconnect,當客戶端發來信息時進入槽函數readMessage進行處理:

void MainWindow::do_connect()
{
     clientConnection = tcpServer->nextPendingConnection();
     connect(clientConnection, SIGNAL(disconnected()), this, SLOT(do_disconnect()));
     connect(clientConnection,SIGNAL(readyRead()),this,SLOT(readMessage()));
     ui->label_3->setText("客戶端已連接");
}

4.建立連接後就可以進行文件的傳輸了,將文件拖至發送區控件處,控件可獲取該文件的路徑,再判斷此時程序狀態是否滿足文件發送,滿足後調用sendfile函數,並把要發送的文件傳遞過去。

發送一個比較大的文件時,需要將文件數據分塊逐一發送,每次發送loadSize個字節。進入sendFile方法,打開發送文件,讀取文件名和文件大小,綁定bytesWritten信號和槽函數updataClientProgress,那麼將數據寫入clientConnection套接子後會調用槽函數updataClientProgress,該函數裏會繼續發剩下未發送的數據,這樣形成一個循環,直至數據發送爲止。在文件數據開始發送之前,先發送文件名字和文件大小,也以此作爲一個簡單的通知客戶端起始接收的“標誌”,讓客戶端對所需要接收的數據做準備:

void MainWindow::sendFile(QString filepath)
{
    localFile = new QFile(filepath);
    QFileInfo Fileinfo(*localFile);
    fileName=Fileinfo.fileName();
    if(!localFile->open((QFile::ReadOnly))){
        qDebug() <<"【服務端】無法讀取發送文件";
        return;
    }else{
        QString temp="正在發送:"+Fileinfo.fileName();
        ui->label_3->setText(temp);
        qDebug() <<"【服務端】開始發送,文件:" <<Fileinfo.fileName() <<"大小:" <<localFile->size();
    }
    connect(clientConnection,SIGNAL(bytesWritten(qint64)),this,SLOT(updateClientProgress(qint64)));
    bytesToWrite=totalBytes = localFile->size();
    status=ST_SENDING;
    time.start();
    timer->start(1000);

    char fileinfo[1024]={0};
    sprintf(fileinfo,"filename\"%s\"filesize\"%d\"",Fileinfo.fileName().toUtf8().data(),totalBytes);

    char Startframe[]={0};
    sprintf(Startframe,"@newfile\"%0.4d\"", strlen(fileinfo));//15Byte
    //先發送文件信息
    clientConnection->write(Startframe,15);
    clientConnection->write(fileinfo,strlen(fileinfo));
}

void MainWindow::updateClientProgress(qint64 numBytes) //更新進度條,實現文件的傳送
{
    bytesWritten +=numBytes;

    if(bytesToWrite > 0)
    {
        outBlock = localFile->read(qMin(bytesToWrite,loadSize));
        bytesToWrite -= (int)clientConnection->write(outBlock);
        int sentRate=((float)bytesWritten/totalBytes)*100;
        ui->progressBar->setValue(sentRate);
        outBlock.resize(0);
        #if debugenable
        qDebug() <<tr("【服務端】已發送:%0字節,剩餘:%1字節").arg(bytesWritten).arg(bytesToWrite);
        #endif
    }
    else
    {
        second_task();
        timer->stop();
        totalBytes = 0;
        bytesWritten = 0;
        bytesToWrite = 0;
        status=ST_IDLE;
        localFile->close(); //如果沒有發送任何數據,則關閉文件

        ui->progressBar->setValue(100);
        ui->label_3->setText("發送成功");
        qDebug() <<fileName <<"文件發送成功.";
        disconnect(clientConnection,SIGNAL(bytesWritten(qint64)),this,SLOT(updateClientProgress(qint64)));
    }
}

客戶端

1.初始化時,創建QTcpSocket對象,綁定readRead信號和接收槽函數readMessage,綁定disconnected信號和連接斷開處理槽函數do_disconnect,綁定SocketError信號和錯誤處理槽函數ErrorOccurred:

    tcpClient = new QTcpSocket(this);
    connect(tcpClient,SIGNAL(readyRead()),this,SLOT(readMessage()));
    connect(tcpClient,SIGNAL(disconnected()),this,SLOT(do_disconnect()));
    connect(tcpClient,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(ErrorOccurred(QAbstractSocket::SocketError)));

2.點擊“連接服務器”按鈕後,獲取輸入框輸入的ip地址和端口號,調用connectToHost接口連接到主機,如果連接失敗,過一段時間進入槽函數ErrorOccurred錯誤處理;連接成功後就可以進行文件的傳輸了:

void MainWindow::on_pushButton_clicked()
{
    if(ui->pushButton->text()== QString("斷開連接"))
    {
        do_disconnect();
    }else{//建立連接
        QHostAddress hostAddress;
        hostAddress = QHostAddress(ui->lineEdit->text());
        tcpPort=ui->lineEdit_2->text().toShort(NULL,10);

        tcpClient->abort();
        tcpClient->connectToHost(hostAddress,tcpPort);

        ui->label_4->setText("已連接服務器.");
        ui->pushButton->setText("斷開連接");
        ui->lineEdit->setDisabled(true);
        ui->lineEdit_2->setDisabled(true);
        ui->textEdit->setDisabled(false);
     }
}

3.當接收到服務端發來信息後,先接收到的是文件名和文件大小,根據文件名,在指定的保存路徑filesavepath下創建本地文件localFile,再將staus狀態轉成ST_RECING接收狀態,那麼接下來收到的數據直接寫入localFile文件,每次接收到數據就更新進度條進度,當接收的字節數bytesReceived等於文件的總字節數totalBytes時,該文件接收完成。

void MainWindow::readMessage()
{
    float useTime = time.elapsed();

    if(status==ST_IDLE){//接收開始
        char FrameHead[15]={0};
        QString fileinfo;
        QString s_filesize;
        qint32 fileinfolen;

        tcpClient->read(FrameHead,15);
        sscanf(FrameHead,"@newfile\"%d\"",&fileinfolen);

        fileinfo = QString(tcpClient->read(fileinfolen));
        fileName = fileinfo.section('"',1,1);
        s_filesize = fileinfo.section('"',3,3);

        localFile = new QFile(filesavepath+fileName);
        totalBytes = s_filesize.toInt();

        if(!localFile->open(QFile::WriteOnly)){
            qDebug()<<"【客戶端】文件創建失敗:" <<fileName;
            do_disconnect();
            return;
        }
        status=ST_RECING;
        time.start();
        timer->start(1000);
        ui->progressBar->setValue(0);
        ui->label_4->setText("正在接收:"+fileName);
        qDebug() <<"【客戶端】開始接收,文件:" <<fileName <<"大小:" <<totalBytes;
        return;
    }

    bytesReceived += tcpClient->bytesAvailable();
    inBlock = tcpClient->readAll();
    localFile->write(inBlock);
    inBlock.resize(0);

    int downedRate=((float)bytesReceived/totalBytes)*100;
    ui->progressBar->setValue(downedRate);//更新進度條
#if 0//如果每次都計算會影響傳輸速度,故利用timer定時每過1s調用second_task函數來計算並顯示,參考second_task函數
    double kbps = bytesReceived/1024;  //B/MS
    double speedks = (kbps/useTime)*1000;//k/s
    QString speed;
    if(speedks >= 1024){
        speed.sprintf("速度:%.3fM/s",speedks/1024);
    }else{
        speed.sprintf("速度:%.3fk/s",speedks);
    }
    ui->label_5->setText(speed);
#endif
    #if debugenable
    qDebug() <<tr("【客戶端】:已接收 %1B(%2\%),用時:%3").arg(bytesReceived).arg(downedRate).arg((float)useTime/1000);
    #endif
    if(bytesReceived==totalBytes){
        second_task();
        timer->stop();
        localFile->close();
        totalBytes = 0;
        bytesReceived = 0;
        status=ST_IDLE;
        ui->label_4->setText("接收完成.");
        ui->progressBar->setValue(100);
        qDebug() <<"【客戶端】接收完畢:" <<fileName;
        qDebug() <<"【客戶端】:所用時間:" <<(float)useTime/1000 <<"s";
    }
}

在源碼設計裏,服務端和客戶端都可以收發,相同部分不一一介紹,詳見源碼。

4.實驗可改進的地方:

  • 一個服務端同時連接多個客戶端,進行文件收發服務,加入線程處理,提高傳輸效率。
  • 加入應答機制,發送端發送固定字節數,需要等待接收端的應答後,才能繼續發送,保證數據發送的完整可靠、有序地傳輸。

 

水平有限,僅供參考,錯誤之處以及不足之處還望多多指教。

 

 

《睹一事於句中,反三隅於字外。 ———劉知幾》

發佈了32 篇原創文章 · 獲贊 187 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章