天天看點

Qt實作用戶端與伺服器消息發送

作者:C加加Qt技術開發老傑

這裡用Qt來簡單設計實作一個場景,即:

(1)兩端:伺服器QtServer和用戶端QtClient

(2)功能:服務端連接配接用戶端,兩者能夠互相發送消息,傳送檔案,并且顯示檔案傳送進度。

環境:VS20013 + Qt5.11.2 + Qt設計師

先看效果:

Qt實作用戶端與伺服器消息發送

一、基本概念

用戶端與伺服器的基本概念不說了,關于TCP通信的三次握手等等,在《計算機網絡》裡都有詳細介紹。這裡說下兩者是如何建立起通信連接配接的。

(1)IP位址:首先伺服器和每一個用戶端都有一個位址,即IP位址。(底層的MAC位址,不關心,因為TCP通信以及IP,是七層架構裡面的網絡層、傳輸層了,底層透明)。對于伺服器來說,用戶端的數量及位址是未知的,除非建立了連接配接。但是對于用戶端來說,必須知道伺服器的位址,因為兩者之間的連接配接是由用戶端主動發起的。

(1)端口号:軟體層面的端口号,指的是 “應用層的各種協定程序與運輸實體進行層間互動的一種位址”。簡而言之,每一個TCP連接配接都是一個程序,作業系統需要為每個程序配置設定一個協定端口(即每一個用戶端與服務端的連接配接,不是兩台主機的連接配接,而是兩個端口的連接配接)。但一台主機通常會有很多服務,很多程序,單靠一個IP位址不能辨別某個具體的程序或者連接配接。是以用端口号來辨別通路的目标伺服器以及伺服器的目标服務類型。端口号也有分類,但這不是本文的重點,詳見教材。

(3)TCP連接配接:總的來說,TCP的連接配接管理分為單個階段:建立連接配接 -> 資料傳送 -> 連接配接釋放。在(2)裡說到,每個TCP連接配接的是具體IP位址的主機的兩個端口,即TCP連接配接的兩個端點由IP位址和端口号組成,這即是套接字(socket)的概念:套接字socket = IP + 端口号。

是以,我們要通過通過套接字來建立服務端與用戶端的通信連接配接。

二、Qt相關類

QTcpSocket:提供套接字

QTcpServer:提供基于TCP的服務端,看官方文檔的解釋如下:

This class makes it possible to accept incoming TCP connections. You can specify the port or have QTcpServer pick one automatically. You can listen on a specific address or on all the machine’s addresses.
           

這個解釋裡面提到兩點:

(1)指定端口:即開通哪一個端口用于建立TCP連接配接;

(2)監聽:監聽(1)中指定的端口是否有連接配接的請求。

三、寫伺服器和用戶端的具體流程

(1)伺服器:

  1. 建立并初始化 QTcpServer 對象;
  2. 啟動伺服器監聽,通過調用成員函數 listen(QHostAddress::Any, 端口号);
  3. 連接配接 QTcpServer 對象的 newConnection 信号槽,當有用戶端連結時,用戶端會發送 newConnection 信号給伺服器,觸發槽函數接受連結(得到一個與用戶端通信的套接字 QTcpSocket);
  4. QTcpsocket 對象調用成員函數 write,發送資料給用戶端;
  5. 當用戶端有資料發送來,QTcpSocket 對象就會發送 readyRead 信号,關聯槽函數讀取資料;
  6. 連接配接 QTcpsocket 對象的 disconnected 信号槽,當用戶端對象調用成員函數 close,會觸發 QTcpsocket 對象的 disconnected 信号,進而觸發槽函數進行相應處理。

Qt學習資料→「連結」

(2)用戶端:

  1. 建立并初始化 QTcpSocket 對象;
  2. QTcpSocket 調用 connectToHost(QHostAddress("IP"), 端口号),連接配接伺服器IP和端口号;
  3. QTcpsocket 對象調用成員函數 write,發送資料給伺服器;
  4. 連接配接QTcpsocket 對象的 connected() 信号槽,當客服端成功連接配接到伺服器後觸發 connected() 信号;
  5. 連接配接QTcpsocket 對象的 readyread() 信号槽,當用戶端接收到服務端發來資料時觸發 readyread() 信号;
  6. 連接配接 QTcpsocket 對象的 disconnected 信号槽,當用戶端對象調用成員函數 close,會觸發 QTcpsocket 對象的 disconnected 信号,進而觸發槽函數進行相應處理。

四、UI設計

用戶端:

Qt實作用戶端與伺服器消息發送

服務端:

Qt實作用戶端與伺服器消息發送

五、服務端實作

我們先要在工程檔案中加入network

QT       += core gui network
           

下面我們來看看伺服器程式步驟:

1、初始化 QTcpServer 對象

TCP_server = new QTcpServer();
           

2、啟動伺服器監聽

TCP_server->listen(QHostAddress::Any,9988);//9988為端口号
           

3、連接配接 QTcpServer 對象的 newConnection 信号槽,當有用戶端連結時,用戶端會發送 newConnection 信号給伺服器,觸發槽函數接受連結(得到一個與用戶端通信的套接字 QTcpSocket)

connect(TCP_server, SIGNAL(newConnection()), this, SLOT(slot_newconnect()));

// 在與 newConnection 信号連接配接的槽函數中,擷取與相應用戶端通信的套接字
TCP_connectSocket = mServer->nextPendingConnection();
           

4、QTcpsocket 對象調用成員函數 write,發送資料給用戶端

TCP_connectSocket->write(msg.toUtf8());
           

5、當用戶端有資料發送來,QTcpSocket 對象就會發送 readyRead 信号,關聯槽函數讀取資料

connect(mSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage()));
           

6、連接配接 QTcpsocket 對象的 disconnected 信号槽,當用戶端對象調用成員函數 close,會觸發 QTcpsocket 對象的 disconnected 信号,進而觸發槽函數進行相應處理

connect(mSocket,SIGNAL(disconnected()),this,SLOT(slot_disconnect()));
           

TCP_Server.h

#ifndef TCP_SERVER_H
#define TCP_SERVER_H

#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>

namespace Ui {
class TCP_Server;
}

class TCP_Server : public QWidget
{
    Q_OBJECT

public:
    explicit TCP_Server(QWidget *parent = nullptr);
    ~TCP_Server();

private slots:
    void slot_newconnect(); //建立新連接配接的槽
    void slot_sendmessage(); //發送消息的槽
    void slot_recvmessage(); //接收消息的槽
    void slot_disconnect(); //取消連接配接的槽

private:
    Ui::m_tcpServer *ui;

    QTcpServer *TCP_server; //QTcpServer伺服器
    QTcpSocket *TCP_connectSocket; //與用戶端連接配接套接字
};

#endif // TCP_SERVER_H

           

TCP_Server.cpp

#include "tcp_server.h"
#include "ui_tcp_server.h"
#include <QMessageBox>
#include <QDateTime>

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

    //初始化
    TCP_server = new QTcpServer();
    TCP_connectSocket = nullptr;
    connect(ui->pushButton_send,SIGNAL(clicked()),this,SLOT(slot_sendmessage()));

    //調用listen函數監聽同時綁定IP和端口号
    if(TCP_server->listen(QHostAddress::LocalHost,10000)) //判斷listen是否成功,成功則繼續執行,連接配接新接收信号槽
    {
        this->connect(TCP_server,SIGNAL(newConnection()),this,SLOT(slot_newconnect()));  //将伺服器的新連接配接信号連接配接到接收新連接配接的槽
    }
    else
    {
        QMessageBox::critical(this,"錯誤","IP綁定錯誤,請關閉其它服務端或更改綁定端口号");
    }
}

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

//建立新連接配接的槽
void TCP_Server::slot_newconnect()
{
    if(TCP_server->hasPendingConnections())  //查詢是否有新連接配接
    {
        TCP_connectSocket = TCP_server->nextPendingConnection(); //擷取與真實用戶端相連的用戶端套接字
        ui->textBrowser->append("client login!"); //若有新連接配接,則提示

        this->connect(TCP_connectSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage())); //連接配接用戶端的套接字的有新消息信号到接收消息的槽
        this->connect(TCP_connectSocket,SIGNAL(disconnected()),this,SLOT(slot_disconnect())); //連接配接用戶端的套接字取消連接配接信号到取消連接配接槽
    }
}

//發送消息的槽
void TCP_Server::slot_sendmessage()
{
    QString sendMessage = ui->lineEdit->text(); //擷取單行文本框内要發送的内容
    if(TCP_connectSocket != nullptr && !sendMessage.isEmpty()) //確定有用戶端連接配接,并且發送内容不為空
    {
        TCP_connectSocket->write(sendMessage.toLatin1());   //發送消息到用戶端

        QString localDispalyMessage = "send to client: " + sendMessage \
                        + QDateTime::currentDateTime().toString(" yyyy-M-dd hh:mm:ss") + tr("\n");
        ui->textBrowser->append(localDispalyMessage);   //将要發送的内容顯示在listwidget
    }

    ui->lineEdit->clear();
}

//接收消息的槽
void TCP_Server::slot_recvmessage()
{
    if(TCP_connectSocket != nullptr) //與用戶端連接配接的socket,不是nullptr,則說明有用戶端存在
    {
        QByteArray array = TCP_connectSocket->readAll();    //接收消息
        QHostAddress clientaddr = TCP_connectSocket->peerAddress(); //獲得IP
        int port = TCP_connectSocket->peerPort();   //獲得端口号

        QDateTime datetime = QDateTime::currentDateTime();

        QString sendMessage = tr("recv from :") + clientaddr.toString() + tr(" : ") \
                                + QString::number(port) + tr("   ") + datetime.toString("yyyy-M-dd hh:mm:ss") + tr("\n");
        sendMessage += array;

        ui->textBrowser->append(sendMessage);   //将接收到的内容加入到listwidget
    }
}

//取消連接配接的槽
void TCP_Server::slot_disconnect()
{
    if(TCP_connectSocket != nullptr)
    {
        ui->textBrowser->append("client logout!");
        TCP_connectSocket->close(); //關閉用戶端
        TCP_connectSocket->deleteLater();
    }
}           

六、用戶端實作

下面我們來看看用戶端程式步驟:

1、初始化 QTcpSocket 對象

TCP_server = new QTcpSocket();
           

2、QTcpSocket 調用 connectToHost(QHostAddress("IP"), 端口号),連接配接伺服器IP和端口号

TCP_sendMesSocket->connectToHost("127.0.0.1",10000); 
           

3、QTcpsocket 對象調用成員函數 write,發送資料給伺服器

//取發送資訊編輯框内容
QString msg = ui->sendEdit->toPlainText();
TCP_sendMesSocket->write(msg.toUtf8());//轉編碼
           

4、連接配接QTcpsocket 對象的 connected() 信号槽,當客服端成功連接配接到伺服器後觸發 connected() 信号

connect(TCP_sendMesSocket,SIGNAL(connected()),this,SLOT(slot_connected()));
           

5、連接配接QTcpsocket 對象的 readyread() 信号槽,當用戶端接收到服務端發來資料時觸發 readyread() 信号

connect(TCP_sendMesSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage()));
           

6、連接配接 QTcpsocket 對象的 disconnected 信号槽,當用戶端對象調用成員函數 close,會觸發 QTcpsocket 對象的 disconnected 信号,進而觸發槽函數進行相應處理

connect(TCP_sendMesSocket, SIGNAL(disconnected()), this, SLOT(slot_disconnect()));
           

TCP_Client.h

#ifndef TCP_CLIENT_H
#define TCP_CLIENT_H

#include <QWidget>
#include <QTcpSocket>

namespace Ui {
class TCP_Client;
}

class TCP_Client : public QWidget
{
    Q_OBJECT

public:
    explicit TCP_Client(QWidget *parent = nullptr);
    ~TCP_Client();

//與按鈕互動,故函數都設定為槽函數
private slots:
    void slot_connected(); //處理成功連接配接到伺服器的槽
    void slot_sendmessage(); //發送消息到伺服器的槽
    void slot_recvmessage(); //接收來自伺服器的消息的槽
    void slot_disconnect(); //取消與伺服器連接配接的槽

private:
    Ui::TCP_Client *ui;

    bool isconnetion; //判斷是否連接配接到伺服器的标志位
    QTcpSocket *TCP_sendMesSocket; //發送消息套接字
};

#endif // TCP_CLIENT_H
           

TCP_Client.cpp

#include "tcp_client.h"
#include "ui_tcp_client.h"
#include <QHostAddress>
#include <QDateTime>
#include <QMessageBox>

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

    /***    初始化TCP   ***/
    this->setWindowTitle("TCP用戶端");
    this->isconnetion = false;
    //初始化sendMesSocket
    this->TCP_sendMesSocket = new QTcpSocket();

    //終止之前的連接配接,重置套接字
    TCP_sendMesSocket->abort();
    //給定IP和端口号,連接配接伺服器
    this->TCP_sendMesSocket->connectToHost("127.0.0.1",10000); //QHostAddress::LocalHost等于127.0.0.1,是以兩者都可以互相替換

    //成功連接配接伺服器的connected()信号連接配接到slot_connected() (注意:不是connect()信号)
    connect(TCP_sendMesSocket,SIGNAL(connected()),this,SLOT(slot_connected()));
    //發送按鈕的clicked()信号連接配接到slot_sendmessage()
    connect(ui->pushButton_send,SIGNAL(clicked()),this,SLOT(slot_sendmessage()));
    //有新資料到達時的readyread()信号連接配接到slot_recvmessage()
    connect(TCP_sendMesSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage()));
    //與伺服器斷開連接配接的disconnected()信号連接配接到slot_disconnect()
    connect(TCP_sendMesSocket,SIGNAL(disconnected()),this,SLOT(slot_disconnect()));
}

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

//處理成功連接配接到伺服器的槽
void TCP_Client::slot_connected()
{
    this->isconnetion = true;
    ui->textBrowser->append(tr("與伺服器連接配接成功:") + QDateTime::currentDateTime().toString("yyyy-M-dd hh:mm:ss"));
}

//發送消息到伺服器的槽
void TCP_Client::slot_sendmessage()
{
    if(this->isconnetion)
    {
        QString sendMessage = ui->lineEdit->text(); //從單行文本框獲得要發送消息
        if(!sendMessage.isEmpty())
        {
            //發送消息到伺服器
            this->TCP_sendMesSocket->write(sendMessage.toLatin1());
            //本地顯示發送的消息
            QString localDispalyMessage = tr("send to server: ") + sendMessage \
                                            + QDateTime::currentDateTime().toString(" yyyy-M-dd hh:mm:ss") + tr("\n");
            ui->textBrowser->append(localDispalyMessage);
        }
        else
            QMessageBox::warning(this,"錯誤","消息不能為空!",QMessageBox::Ok);
    }
    else
        QMessageBox::warning(this,"錯誤","未連接配接到伺服器!",QMessageBox::Ok);

    ui->lineEdit->clear();
}

//接收來自伺服器的消息的槽
void TCP_Client::slot_recvmessage()
{
    //接收來自伺服器的消息
    QByteArray byteArray = this->TCP_sendMesSocket->readAll();
    QString recvMessage = tr("recv from server: ") + byteArray + QDateTime::currentDateTime().toString(" yyyy-M-dd hh:mm:ss") + tr("\n");
    ui->textBrowser->append(recvMessage);
}

//取消與伺服器連接配接的槽
void TCP_Client::slot_disconnect()
{
    QMessageBox::warning(this,"警告","與伺服器的連接配接中斷",QMessageBox::Ok);
    //關閉并随後删除socket
    TCP_sendMesSocket->close();
    TCP_sendMesSocket->deleteLater();
}
           

Qt開發必備技術棧學習路線和資料

Qt學習資料→「連結」

繼續閱讀