1. 程式人生 > >Qt實現伺服器與客戶端傳輸文字和圖片(Qt②)

Qt實現伺服器與客戶端傳輸文字和圖片(Qt②)

初學者記錄學習內容,如有錯誤請各位前輩指點。
此次工程完成過程借鑑了下面得兩個帖子,附上鍊接,並致以感謝:
qt 寫的tcp客戶端程式實現簡單的連線接受和傳送訊息
qt寫的一個簡單的tcp伺服器程式,可以接受訊息傳送資料
好了閒話少說進入正題。
瞭解C語言的盆友們應該知道實現Socket程式傳遞訊息需要以下幾點:
在伺服器server端:①建立套接字SOCKET;②bind()函式繫結套接字(和IP,埠Port繫結);③Listen()進入監聽狀態;④accept()進入接收客戶端請求;⑤send()向客戶端傳送資料;⑥close()關閉套接字。
在客戶端Client端:①建立套接字SOCKET;②connect()向伺服器發起請求;③recv()接收伺服器傳回的資料;④printf()列印傳回的資料;⑤close()關閉套接字。
而在Qt實現Socket的過程中,也與此過程有很多相似之處。

傳輸文字的伺服器server實現

在QtDesigner中繪製介面:
伺服器端server介面
QDialog中兩個的PushButton分別命名為pbtnSend和stopButton,以便後面加入槽函式。
注意進行socket連線之前要在.pro中加入network

QT       += core gui network

貼入程式碼如下:
sever.h

#ifndef SERVER_H
#define SERVER_H

#include <QDialog>
#include <QTcpSocket>
#include <QTcpServer>
#include <QMessageBox> #include <QDebug> namespace Ui { class Server; } class Server : public QDialog { Q_OBJECT public: explicit Server(QWidget *parent = 0); ~Server(); private slots: void on_stopButton_clicked(); void acceptConnection(); void sendMessage(); void
displayError(QAbstractSocket::SocketError); private: Ui::Server *ui; QTcpServer *tcpServer; QTcpSocket *tcpSocketConnection; }; #endif // SERVER_H

server.cpp

#include "server.h"
#include "ui_server.h"

Server::Server(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Server)
{
    ui->setupUi(this);
    tcpServer=new QTcpServer(this);
    if (!tcpServer->listen(QHostAddress::Any, 7777)) {
            qDebug() << tcpServer->errorString();
            close();
        }
    tcpSocketConnection = NULL;
    connect(tcpServer,SIGNAL(newConnection()),
    this,SLOT(acceptConnection()));
    connect(ui->pbtnSend,SIGNAL(clicked(bool)),
    this,SLOT(sendMessage()));
}

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

void Server::acceptConnection()
{
    tcpSocketConnection = tcpServer->nextPendingConnection();
    connect(tcpSocketConnection,SIGNAL(disconnected()),this,SLOT(deleteLater()));
    connect(tcpSocketConnection,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError)));
}

void Server::on_stopButton_clicked()
{
    tcpSocketConnection->abort();
    QMessageBox::about(NULL,"Connection","Connection stoped");
}

void Server::sendMessage()
{
    if(tcpSocketConnection==NULL)
        return;
    QByteArray block;
    QDataStream out(&block,QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_5_8);
    out<<(quint16)0;
    out<<"Hello [email protected][email protected]!";
    out.device()->seek(0);
    out << (quint16)(block.size() - sizeof(quint16));
    tcpSocketConnection->write(block);
}

void Server::displayError(QAbstractSocket::SocketError)
{
     qDebug() << tcpSocketConnection->errorString();
}

現在對server.cpp進行解釋:
引用查到的一段對QTcpServer和QTcpSocket基本操作的描述,如下涉及到的幾個函式都是特別重要的。

QTcpServer的基本操作:
1、呼叫listen監聽埠。
2、連線訊號newConnection,在槽函式裡呼叫nextPendingConnection獲取連線進來的socket。
QTcpSocket的基本能操作:
1、呼叫connectToHost連線伺服器。
2、呼叫waitForConnected判斷是否連線成功。
3、連線訊號readyRead槽函式,非同步讀取資料。
4、呼叫waitForReadyRead,阻塞讀取資料。

在呼叫這幾個函式之前先在.h檔案中做宣告指標變數:

private:
    QTcpServer *tcpServer;
    QTcpSocket *tcpSocketConnection;

在.cpp檔案中

tcpServer=new QTcpServer(this);
tcpSocketConnection = NULL;

注意之後要在tcpSocketConnection的基礎上操作資料,所以初始化要保證當前無連線,即賦值為NULL。
然後在建構函式中設定IP地址和埠號:

if (!tcpServer->listen(QHostAddress::Any, 7777)) {
            qDebug() << tcpServer->errorString();
            close();
        }

QTcpServer呼叫Listen()監聽,使用了IPv4的本地主機地址,等價於QHostAddress(“127.0.0.1”),埠號設為”7777”。listen()函式返回的值是bool型,所以如果監聽失敗時,會把錯誤原因列印到控制檯,並關閉連線。

用QT實現並不需要像C語言那麼麻煩,當我們設定好IP和埠進行監聽時,用我的理解就是伺服器進入了迴圈,會不斷監聽檢查發來的連線,當有同樣的IP和埠的連線申請發來的時候,QTcpSocket會發射newConnection()訊號,在程式碼的槽函式中會觸發acceptConnection()函式。

tcpSocketConnection = tcpServer->nextPendingConnection();

當訊號發來的時候,通過呼叫QTcpServer的nextPendingConnection()函式得到的socket連線控制代碼tcpSocketConnection ,注意之後對於訊號和資料的操作都是在這個控制代碼基礎上進行的。
注意到此函式中僅有對tcpSocketConnection進行操作,但是沒有在開頭new一塊記憶體去儲存tcpSocketConnection,這是因為通過nextPendingConnection()得到控制代碼的過程就會被分配一塊記憶體去儲存,所以不用忘記了在之後釋放記憶體空間,即之後的deleteLater()函式。
之後的兩個connect語句,斷開後刪除連線,執行出錯將錯誤資訊列印到控制檯。

最後來看伺服器端最重要的一個槽函式sendMessage()。
當觸發pbtnSend按鈕的clicked()訊號,將固定訊息“Hello [email protected][email protected]!”傳送。首先連線控制代碼tcpSocketConnection是否為空。
然後用到了QByteArray,這在Qt傳輸資料時非常重要,它可以儲存raw bytes即原始位元組,將文字或者圖片轉化為raw bytes傳送,在接收端進行解析。
QDataStream則提供了一個二進位制的資料流。看程式碼:

    QByteArray block;
    QDataStream out(&block,QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_5_8);
    out<<(quint16)0;
    out<<"Hello [email protected][email protected]!";
    out.device()->seek(0);
    out << (quint16)(block.size() - sizeof(quint16));
    tcpSocketConnection->write(block);

將僅可寫入WriteOnly的資料流out與block進行繫結。
設定資料流out的版本,注意客戶端和伺服器端使用的版本要相同,這裡我們使用的是Qt5.8。然後通過C++的過載操作符<<實現對流的操作。
這裡著重說一點,使用<<的時候並不關心輸入的型別,換句話說無論是什麼型別寫入資料流,在讀出的時候都會以寫入的順序和型別讀出,而不用考慮佔幾個位元組,需要一個一個位元組取出來,高位低位組合起來等等情況。
比如此程式中,先以quint16型別(2個位元組)的輸入”0”佔位,然後寫入字串”Hello [email protected][email protected]!”。seek(0)將指標移動到0所在的那一位處,block.size()得出block的長度15,sizeof(quint16)得出一個quint16型別的長度為2,相減得13覆蓋儲存到剛才用0佔位的儲存quint16型別的記憶體中,這就是傳輸的資料的長度,與資料一起傳給伺服器用於比對資料是否傳輸完整。
注意quint16是QT軟體下的一種自定義型別,代表16位的無符號整型,可以儲存2^16個數字,就是0-65535,以此儲存傳送的文字的長度足以。但是如果需要傳輸圖片的大小就需要使用quint32型別來儲存,在後面中我們會用到。
最後是QTcpSocket的write函式,將QByteArray型別的block寫入Socket快取中,之後就是客戶端的工作了。

傳輸文字的客戶端Client實現

在QtDesigner中繪製介面:
客戶端Client介面
QDialog下兩個QLineEdit用於填入埠號和IP地址,PushButton命名為sendButton用於觸發向伺服器的連線申請。QTextEdit命名為messageShow用於顯示從伺服器傳來的字串。
貼入程式碼如下:

client.h

#ifndef CLIENT_H
#define CLIENT_H

#include <QDialog>
#include <QTcpSocket>
#include <QObject>
#include <QMessageBox>
#include <QDebug>
#include <QDateTime>


namespace Ui {
class Client;
}

class Client : public QDialog
{
    Q_OBJECT

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

private slots:
    void on_sendButton_clicked();
    void showMessage();
    void displayError(QAbstractSocket::SocketError);

private:
    Ui::Client *ui;
    QTcpSocket *tcpSocket;
    quint16 blockSize;
};

#endif // CLIENT_H

client.cpp

#include "client.h"
#include "ui_client.h"

Client::Client(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Client)
{
    ui->setupUi(this);
    tcpSocket = new QTcpSocket(this);
    connect(ui->sendButton,SIGNAL(clicked()),this,SLOT(on_sendButton_clicked()));
    connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(showMessage()));
    connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
                        this, SLOT(displayError(QAbstractSocket::SocketError)));
    blockSize = 0;
}

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

void Client::on_sendButton_clicked()
{
    if(tcpSocket->state()!=QAbstractSocket::ConnectedState)
    {
        tcpSocket->connectToHost(ui->ipLineEdit->text(),ui->portLineEdit->text().toInt());

        if(tcpSocket->waitForConnected(10000))
        {
            QMessageBox::about(NULL, "Connection", "Connection success");
        }
        else
        {
            QMessageBox::about(NULL,"Connection","Connection timed out");
        }
    }
    else
        QMessageBox::information(NULL,"","Connected!");
}

void Client::showMessage()
{
    QDataStream in(tcpSocket);
    in.setVersion(QDataStream::Qt_5_8);
    if(blockSize==0)
    {
        if(tcpSocket->bytesAvailable()<(int)sizeof(quint16))
            return;
        in >> blockSize;
    }
   if(tcpSocket->bytesAvailable()<blockSize)
        return;
    char* buf=new char[512];
    in >> buf;
    QDateTime time = QDateTime::currentDateTime();
    QString str = time.toString("yyyy-MM-dd hh:mm:ss");
    ui->messageShow->setText(buf+str);
    if(buf)
        delete buf;
}


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

現在對client.cpp進行解釋。
多餘的東西不再贅述,點選sendButton執行槽函式on_sendButton_clicked()。這裡引用一段對QAbstractSocket的描述:

QAbstractSocket都有一個狀態,而我們可以通過呼叫成員函式state返回這個狀態,才開始的狀態是UnconnectedState,
當程式呼叫了connectToHost之後,QAbstractSocket的狀態會變成HostLookupState,,
如果主機被找到,QAbstaractSocket進入connectingState狀態並且發射HostFound()訊號,當連線被建立的時候QAbstractSocket 進入了connectedState狀態 並且發射connected()訊號,如果再這些階段出現了錯誤,QAbstractSocket將會發射error()訊號,無論在什麼時候,如果狀態改變了,都會發射stateChanged(),如果套接字準備好了讀寫資料,isValid()將會返回true。

如上所述呼叫state()判斷QAbstractSocket是否是connectedState,即已經連線的狀態。如果尚未連線繼續執行,呼叫隨後呼叫QTcpSocket的connectToHost(從控制元件中獲取的IP和埠號)連線伺服器,呼叫waitForConnected判斷是否連線成功,但如果是已經連線成功彈出提示框。
QMessageBox彈出提示語對話方塊,常用於幫助你判斷socket是否連線成功。
在伺服器中通過write()將資料寫入socket緩衝區,在已經連線成功的情況下客戶端當有資料要讀的時候,會觸發readyRead()訊號,隨後執行showMessage()槽函式,showMessage()函式是將傳過來的資料型別轉化後輸出。宣告資料流in接收資料,設定QT版本。
注意在.h中定義:

private:
 quint16 blockSize;

quint16 型別的blockSize用於從資料流中接收之前儲存為quint16 的字串的大小,用來進行資料是否傳輸完整的比對,並在.cpp中賦值為blockSize = 0。

tcpSocket->bytesAvailable()返回已經接收到的可以被讀出的位元組數,(int)sizeof(quint16)返回一個quint16型別變數的大小,只有當前者大於後者說明資料的長度已經傳輸完整,則儲存到blockSize變數中,否則直接返回,繼續接收資料。
隨後用blockSize值進行比對,只有當接收到的位元組數大於blockSize的值才說明資料全部傳輸成功,否則返回繼續接收資料。

傳輸資料的大小和比對,這兩步是基本步驟,必不可少
定義一個暫時儲存資料的char型陣列的指標,new一段大小為512的記憶體,將接下來的char型的字串輸入其中。一定記得在最後刪除指標。
關於刪除指標著重說一點——

關於C++中的解構函式,分配到棧(區域性記憶體管理)的不需要釋放,分配到堆(全域性記憶體管理)需要釋放。一般區域性物件在區域性函式結束的時候會自動釋放。
New一個記憶體空間或者malloc()動態分配記憶體空間一般都需要自己在解構函式中釋放。比如在此程式中,函式執行結束之後指向記憶體空間的指標buf最後會被自動刪除,而這一塊儲存空間並不會被釋放,而且也將再無法對其進行操作,長此以往記憶體會愈來愈小。因此有兩條路可選,一是對該指標進行儲存以便以後對其指向的記憶體空間進行管理。二是在函式結束之前釋放記憶體空間。

最後獲取當前的時間和日期顯示到textEdit中。結束。

傳輸圖片的server和client實現

關於socket的連線,這裡將不再累述,只對傳送和顯示圖片的過程進行簡單地講解。
同上,先再QTdesigner中繪製客戶端和伺服器的介面,同傳輸文字大致相同,只需將客戶端介面中的textEdit換成QLabel用於顯示圖片。
伺服器中傳送圖片和客戶端中接收圖片的槽函式如下:

void pictureSever::on_sendPictureButton_clicked()
{
    if(tcpSocket==NULL)
        return;
    QByteArray block;
    QDataStream out(&block,QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_5_8);
    out<<(quint32)buffer.data().size();
    block.append(buffer.data());
    tcpSocket->write(block);
}

在.h中宣告

private:
QBuffer buffer;

在.cpp的建構函式中,將圖片儲存在buffer中:

QPixmap(":/new/prefix1/sendPicture/007.bmp").save(&buffer,"BMP");

圖片在專案的資原始檔中。

此處將字串讀入socket中的方法與上例中的方法大致相同,先讀入資料的大小,後讀入資料。但過程不同,兩個過程並無太大區別,可自行選擇。
不過要注意的就是上面已經談過的,這裡使用quint32而不是quint16來儲存圖片的大小。

void pictureClient::showPicture()
{
    while(tcpSocketConnection->bytesAvailable()>0)
    {
        if(blockSize==0)
        {
            QDataStream in(tcpSocketConnection);
            in.setVersion(QDataStream::Qt_5_8);
            if(tcpSocketConnection->bytesAvailable()<sizeof(quint32))
                return;
            in>>blockSize;
        }

        if(tcpSocketConnection->bytesAvailable()<blockSize)
            return;

        QByteArray array = tcpSocketConnection->read(blockSize);//blockSize作read()的引數。
        QBuffer buffer(&array);
        buffer.open(QIODevice::ReadOnly);

        QImageReader reader(&buffer,"BMP");
        QImage image = reader.read();
        blockSize=0;//①

        if(!image.isNull())
       {
            image=image.scaled(ui->showPicturelabel->size());
            ui->showPicturelabel->setPixmap(QPixmap::fromImage(image));
            blockSize=0;//②
       }
    }
}

同樣的方式,先確定用quint32變數儲存的圖片大小的的值已經傳入,然後比對確定圖片完全傳入。
注意這裡要使用QImageReader將圖片的資料流轉化回BMP格式,必須使用QBuffer,將資料裝入QBuffer型別的變數中進行轉化。
QImageReader的幫助文件
QBuffer的幫助文件
有概念性的疑問一定去找Qt幫助文件,這才是權威。
最後說一點:blockSize的問題,在傳輸字串時,由於我們要傳輸的資料比較短,我們就當作記錄字串長度的變數一次性傳入成功,其實這是存在問題的。資料較多時,並不一定能一次成功的,應該使用傳輸圖片時使用的while迴圈比對判斷。因為要使用blockSize==0作為判斷條件,在迴圈體的結尾處要將blockSize再置為0,①處是傳輸異常情況下走不到下面的if結構中時的情況,②處是傳輸正常情況下的置0。
最後將圖片讀入到image,自適應Label的大小並顯示。

有點囉嗦,如有錯誤還望指正,謝謝。

相關推薦

Qt實現伺服器客戶傳輸文字圖片Qt

初學者記錄學習內容,如有錯誤請各位前輩指點。 此次工程完成過程借鑑了下面得兩個帖子,附上鍊接,並致以感謝: qt 寫的tcp客戶端程式實現簡單的連線接受和傳送訊息 qt寫的一個簡單的tcp伺服器程式,可以接受訊息傳送資料 好了閒話少說進入正題。 瞭解C

二、Netty實現伺服器客戶完整互動連線實戰

        本節內容是程式碼實現伺服器與客戶端完整連線過程。整體把控netty的工作流程。我們先不要被某個類,某個api的封裝深入挖掘,這樣你會踩很多坑,陷入進去而拔不出來,後面我會一一講解,原始碼剖析工作原理。這就是我個人學習技術的一種方法,深入淺出

【隨堂筆記】unity開發中Socket的用法一,實現伺服器客戶簡單的連結

實現了簡單的連結,也增加了客戶端沒有連結到伺服器的自動重連 伺服器程式碼 using System; using System.Net; using System.Net.Sockets; namespace SeverSocket { class Program

Vue+Java servlet 通過websocket實現伺服器客戶雙向通訊

1. vue程式碼 methods: { //在方法裡呼叫 this.websocketsend()傳送資料給伺服器 onConfirm () { //需要傳輸的資料 let data = { cod

JAVA整合WebSocket,實現伺服器客戶握手

                                      WebSocket實現伺服器與客戶端握手 自學的WebSocket途中遇到很多坑,希望需要使用的朋友可以少走彎路, 使用的環境:tomcat7.0,mysql,springMvc,spring,M

socket程式設計實現伺服器客戶簡單通訊

本節主講客戶端向服務傳送資訊,伺服器轉發給客戶端,當然也可以稍微改一下程式碼可以實現互傳訊息,不再贅述。 難點在於伺服器端的程式碼思路: (1)主程式Socket socket=server.acc

挖掘機DMS伺服器客戶讀寫檔案,收發資料

Client package com.company; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IO

一個基於TCP/IP的伺服器客戶通訊的小專案超詳細版

1.目的:實現客戶端向伺服器傳送資料   原理: 2.建立兩個控制檯應用,一個為伺服器,用於接收資料。一個為客戶端,用於傳送資料。   關鍵類與對應方法:   1)類IPEndPoint:   1.是抽象類EndPoint的實現類   2.So

SqlServer資料庫連線數客戶連線池關係測試

  連線池連線數             DB連線數      峰值錯誤資訊     連線峰值   40000 0(32767)    ? ? 3000 0(32767) ? ? 40000 3000 ? ?

CSS3動畫實現高亮光弧效果,文字圖片一閃而過

前言   好久沒有寫部落格啦,高亮文字和圖片一閃而過的特效,用CSS3來寫  先看文字吧, 就上程式碼了 .shadow { /* 背景顏色線性漸變 */ /* 老式寫法 */ /* linear為線性漸變,也可以用下面的那種寫法。left top,right t

C#.網路程式設計 Socket基礎 基於WinForm系統Socket TCP協議 實現伺服器客戶.txt.word.png等不同型別檔案傳輸

一、簡介: 前面的兩篇介紹了字串傳輸、圖片傳輸: 其實,本文針對Socket基礎(二)進一步完成,以便可以進行多種檔案傳輸。 二、基於不同的流(檔案流、記憶體流、網路等)讀寫。 1、圖片傳輸 方法一:(在客戶端用檔案流傳送(即將圖片寫到檔案流去,以便傳送),

搭建FTP伺服器客戶(1) - Python實現

FTP背景介紹:FTP(File Transfer Protocol)協議,顧名思義為檔案傳輸協議。由已故的Jon Postel與Joyce Reynolds開發,並於1985年10月釋出。其底層基於TCP/IP協議。FTP目前主要用於匿名下載公共檔案,也可以在兩臺跨系統的計算機之間傳輸檔案。為了實現F

【開發筆記】Unity聯網鬥地主的實現一,伺服器客戶的資料傳遞流程

話不多說,先上我李老師的思維導圖 大致構思了一個框架 1.首先要定義一下伺服器與客戶端的傳輸協議,必須保持一致 2.定義服務於客戶端傳輸的訊息型別,如(申請加入,同意加入,出牌,之類的) 3.定義一下牌的型別,出的牌的型別,在客戶端判斷是否可以出牌,牌型傳給伺服器,伺服器在完成三個玩家的出

Qt 多執行緒伺服器客戶

文章目錄 思路 伺服器 myserver.h myserver.cpp mythread.h mythread.cpp mysocket.h mysocket.cpp

C#.網路程式設計 Socket基礎Socket TCP協議 實現伺服器客戶簡單字串通訊

簡介:        本章節主要討論了Socket的入門知識,還未針對Socket的難點問題(比如TCP的無訊息邊界問題)展開討論,往後在其他章節中進行研究。 注意點: 伺服器(比如臺式電腦)的IP為1.1.1.2,那麼客戶端(其他裝置,比如手機,Ipad)連線的一定是

C#.網路程式設計 Socket基礎 WPF系統Socket TCP協議 伺服器客戶 不同型別檔案傳輸,同時解決UI執行緒工作執行緒的卡頓問題

一、簡介 雖然,本文的前面幾篇文章在WinForm中實現了Socket TCP協議 伺服器與客戶端 不同型別檔案傳輸,詳情見 但是,卻沒有在WPF中實現 Socket TCP協議 伺服器與客戶端 不同型別檔案傳輸。因此,本文將描述如何在WPF中實現該功能。

JAVA NIO 伺服器客戶實現示例(程式碼1)

公共類: [java] view plain copy print? package com.stevex.app.nio;  import java.nio.ByteBuffer;  import java.nio.CharBuffer;  import j

linux c伺服器客戶之間的檔案傳輸

最近做了一下linux C網路方面的專案,簡單的寫了一下伺服器於客戶端之間上傳,下載檔案,利用併發伺服器,可以實現多個客戶端同時上傳,下載。 寫的不好,還請大神多多指教!多的不說,一切都在程式碼中,部分程式碼如下所示: /*server.c */ 伺服器端 void *re

Java實現簡單的Socket伺服器客戶字串通訊適合初學者閱讀

       近段時間,頻繁看到很多學生做畢業設計用到了Socket通訊技術,問題非常多,特寫一個小例子,希望對馬上畢業的同學有所幫助。如果希望學習的更加深入,需要掌握的知識有:面向物件、多執行緒、Socket通訊、IO流、異常處理 伺服器端程式碼: import java

JAVA NIO 伺服器客戶實現示例

公共類: package com.stevex.app.nio; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingExcepti