Qt實現網路聊天室(客戶端,服務端)

進擊的汪sir發表於2021-06-23

1. 效果演示

  • 客戶端
    在這裡插入圖片描述
  • 伺服器
    在這裡插入圖片描述

連線成功之後
在這裡插入圖片描述

2. 預備知識

如果不知道網路程式設計的可以去看我的上一篇文章C++網路程式設計

在Qt中,實現網路程式設計的方式比用C++或C實現要方便簡單許多,因為Qt已經替我們封裝好了,我們會使用就可以了,然後大家還需要了解Qt 的訊號槽機制,可以參考我這篇文章,Qt訊號槽


2.1 QTcpServer

QTcpServer 類用於監聽客戶端連線以及和客戶端建立連線,在使用之前先介紹一下這個類提供的一些常用 API 函式:

建構函式

QTcpServer::QTcpServer(QObject *parent = Q_NULLPTR);

給監聽的套接字設定監聽

bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
// 判斷當前物件是否在監聽, 是返回true,沒有監聽返回false
bool QTcpServer::isListening() const;
// 如果當前物件正在監聽返回監聽的伺服器地址資訊, 否則返回 QHostAddress::Null
QHostAddress QTcpServer::serverAddress() const;
// 如果伺服器正在監聽連線,則返回伺服器的埠; 否則返回0
quint16 QTcpServer::serverPort() const

引數:
address:通過類 QHostAddress 可以封裝 IPv4、IPv6 格式的 IP 地址,QHostAddress::Any 表示自動繫結
port:如果指定為 0 表示隨機繫結一個可用埠。
返回值:繫結成功返回 true,失敗返回 false

得到和客戶端建立連線之後用於通訊的 QTcpSocket 套接字物件,它是 QTcpServer 的一個子物件,當 QTcpServer 物件析構的時候會自動析構這個子物件,當然也可自己手動析構,建議用完之後自己手動析構這個通訊的 QTcpSocket 物件。

QTcpSocket *QTcpServer::nextPendingConnection();

阻塞等待客戶端發起的連線請求,不推薦在單執行緒程式中使用,建議使用非阻塞方式處理新連線,即使用訊號 newConnection() 。

bool QTcpServer::waitForNewConnection(int msec = 0, bool *timedOut = Q_NULLPTR);

引數:
msec:指定阻塞的最大時長,單位為毫秒(ms)
timeout:傳出引數,如果操作超時 timeout 為 true,沒有超時 timeout 為 false

2.2 QTcpServer訊號

當接受新連線導致錯誤時,將發射如下訊號。socketError 引數描述了發生的錯誤相關的資訊

[signal] void QTcpServer::acceptError(QAbstractSocket::SocketError socketError);

每次有新連線可用時都會發出 newConnection () 訊號。

[signal] void QTcpServer::newConnection();

2.3 QTcpSocket

QTcpSocket 是一個套接字通訊類,不管是客戶端還是伺服器端都需要使用。在 Qt 中傳送和接收資料也屬於 IO 操作(網路 IO)

建構函式

QTcpSocket::QTcpSocket(QObject *parent = Q_NULLPTR);

連線伺服器,需要指定伺服器端繫結的IP和埠資訊。

[virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);

[virtual] void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite);

在 Qt 中不管呼叫讀操作函式接收資料,還是呼叫寫函式傳送資料,操作的物件都是本地的由 Qt 框架維護的一塊記憶體。因此,呼叫了傳送函式資料不一定會馬上被髮送到網路中,呼叫了接收函式也不是直接從網路中接收資料,關於底層的相關操作是不需要使用者來維護的。

接收資料

// 指定可接收的最大位元組數 maxSize 的資料到指標 data 指向的記憶體中
qint64 QIODevice::read(char *data, qint64 maxSize);
// 指定可接收的最大位元組數 maxSize,返回接收的字串
QByteArray QIODevice::read(qint64 maxSize);
// 將當前可用運算元據全部讀出,通過返回值返回讀出的字串
QByteArray QIODevice::readAll();

2.4 QTcpSocket訊號

在使用 QTcpSocket 進行套接字通訊的過程中,如果該類物件發射出 readyRead() 訊號,說明對端傳送的資料達到了,之後就可以呼叫 read 函式接收資料了。

[signal] void QIODevice::readyRead();

呼叫 connectToHost() 函式併成功建立連線之後發出 connected() 訊號。
在套接字斷開連線時發出 disconnected() 訊號。

呼叫 connectToHost() 函式併成功建立連線之後發出 connected() 訊號。
[signal] void QAbstractSocket::disconnected();

3. 通訊流程

3.1 伺服器端

  • 建立套接字伺服器 QTcpServer 物件
  • 通過 QTcpServer 物件設定監聽,即:QTcpServer::listen()
  • 基於 QTcpServer::newConnection() 訊號檢測是否有新的客戶端連線
  • 如果有新的客戶端連線呼叫 QTcpSocket *QTcpServer::nextPendingConnection() 得到通訊的套接字物件
  • 使用通訊的套接字物件 QTcpSocket 和客戶端進行通訊

標頭檔案

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private slots:
    void on_startServer_clicked();

    void on_sendMsg_clicked();

private:
    Ui::MainWindow *ui;
    QTcpServer* m_server;
    QTcpSocket* m_tcp;
};

原始檔

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setWindowTitle("TCP - 伺服器");
    // 建立 QTcpServer 物件
    m_server = new QTcpServer(this);
    // 檢測是否有新的客戶端連線
    connect(m_server, &QTcpServer::newConnection, this, [=]()
    {
        m_tcp = m_server->nextPendingConnection();
        ui->record->append("成功和客戶端建立了新的連線...");
        m_status->setPixmap(QPixmap(":/connect.png").scaled(20, 20));
        // 檢測是否有客戶端資料
        connect(m_tcp, &QTcpSocket::readyRead, this, [=]()
        {
            // 接收資料
            QString recvMsg = m_tcp->readAll();
            ui->record->append("客戶端Say: " + recvMsg);
        });
        // 客戶端斷開了連線
        connect(m_tcp, &QTcpSocket::disconnected, this, [=]()
        {
            ui->record->append("客戶端已經斷開了連線...");
            m_tcp->deleteLater();
            m_status->setPixmap(QPixmap(":/disconnect.png").scaled(20, 20));
        });
    });
}

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

// 啟動伺服器端的服務按鈕
void MainWindow::on_startServer_clicked()
{
    unsigned short port = ui->port->text().toInt();
    // 設定伺服器監聽
    m_server->listen(QHostAddress::Any, port);
    ui->startServer->setEnabled(false);
}

// 點選傳送資料按鈕
void MainWindow::on_sendMsg_clicked()
{
    QString sendMsg = ui->msg->toPlainText();
    m_tcp->write(sendMsg.toUtf8());
    ui->record->append("伺服器Say: " + sendMsg);
    ui->msg->clear();
}

3.2 客戶端

通訊流程

  • 建立通訊的套接字類 QTcpSocket 物件
  • 使用伺服器端繫結的 IP 和埠連線伺服器 QAbstractSocket::connectToHost()
  • 使用 QTcpSocket 物件和伺服器進行通訊

標頭檔案

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private slots:
    void on_connectServer_clicked();

    void on_sendMsg_clicked();

    void on_disconnect_clicked();

private:
    Ui::MainWindow *ui;
    QTcpSocket* m_tcp;
};

原始檔

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setWindowTitle("TCP - 客戶端");

    // 建立通訊的套接字物件
    m_tcp = new QTcpSocket(this);
    // 檢測伺服器是否回覆了資料
    connect(m_tcp, &QTcpSocket::readyRead, [=]()
    {
        // 接收伺服器傳送的資料
        QByteArray recvMsg = m_tcp->readAll();
        ui->record->append("伺服器Say: " + recvMsg);
    });
        
    // 檢測是否和伺服器是否連線成功了
    connect(m_tcp, &QTcpSocket::connected, this, [=]()
    {
        ui->record->append("恭喜, 連線伺服器成功!!!");
        m_status->setPixmap(QPixmap(":/connect.png").scaled(20, 20));
    });
        
    // 檢測伺服器是否和客戶端斷開了連線
    connect(m_tcp, &QTcpSocket::disconnected, this, [=]()
    {
        ui->record->append("伺服器已經斷開了連線, ...");
        ui->connectServer->setEnabled(true);
        ui->disconnect->setEnabled(false);
    });
}

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

// 連線伺服器按鈕按下之後的處理動作
void MainWindow::on_connectServer_clicked()
{
    QString ip = ui->ip->text();
    unsigned short port = ui->port->text().toInt();
    // 連線伺服器
    m_tcp->connectToHost(QHostAddress(ip), port);
    ui->connectServer->setEnabled(false);
    ui->disconnect->setEnabled(true);
}

// 傳送資料按鈕按下之後的處理動作
void MainWindow::on_sendMsg_clicked()
{
    QString sendMsg = ui->msg->toPlainText();
    m_tcp->write(sendMsg.toUtf8());
    ui->record->append("客戶端Say: " + sendMsg);
    ui->msg->clear();
}

// 斷開連線按鈕被按下之後的處理動作
void MainWindow::on_disconnect_clicked()
{
    m_tcp->close();
    ui->connectServer->setEnabled(true);
    ui->disconnect->setEnabled(false);
}

相關文章