1. 程式人生 > >Qt學習之路十三—— 再談TCP/IP(多客戶端連線伺服器)

Qt學習之路十三—— 再談TCP/IP(多客戶端連線伺服器)

一、TCP和UDP的區別

這裡我會用一個表格來顯示這兩者的區別

比較項TCPUDP
是否連線面向連線無連線
傳輸是否可靠可靠不可靠
流量控制提供不提供
工作方式全雙工可以是全雙工
應用場合大量資料少量資料
速度
二、incomingConnection函式

這個函式和之前講過的newConnection訊號功能差不多,只要有新的連接出現,就會自動呼叫這個函式。

然後我們只需在這個函式中新建一個QTcpSocket物件,並且將這個套接字指定為這個函式的引數socketDescriptor,然後將這個套接字存放到套接字列表中就可以實現多個客戶端同時登陸了。

這裡我們簡單看一下這個函式裡的內容

void Server::incomingConnection(int socketDescriptor)
{
    TcpClientSocket *tcpclientsocket = new TcpClientSocket(this);//只要有新的連線就生成一個新的通訊套接字
    //將新建立的通訊套接字描述符指定為引數socketdescriptor
    tcpclientsocket->setSocketDescriptor(socketDescriptor);

    //將這個套接字加入客戶端套接字列表中
    tcpclientsocketlist.append(tcpclientsocket);

}

Server這個類是繼承於QTcpServer類的,所以我們需要在Server類中重寫incomingConnection函式。

三、多個客戶端同時登陸的小聊天室示例。

首先說明一下這個小示例的功能,當有一個客戶端進入聊天室的時候,會向伺服器傳送一個資訊,告訴伺服器這個賬號進入了聊天室, 然後伺服器會在介面中顯示哪個賬號進入了聊天室,並且伺服器會向所有的客戶端傳送訊息,告訴所有客戶端有哪個賬號進入了聊天室。某個賬號傳送資訊的時候也是如此,伺服器會將收到的資訊傳送給所有的客戶端。

如果需要實現網路通訊,就必須在pro檔案中加入 QT += network

伺服器端

在伺服器端需要新增三個類,一個類用來建立介面,一個類用來監聽,這個類的基類是QTcpServer,最後一個類用來通訊,通訊的類的基類是QTcpSocket類。

1、用來建立介面的Tcpserver類

#ifndef TCPSERVER_H
#define TCPSERVER_H

#include <QWidget>
#include "server.h"

namespace Ui {
class TcpServer;
}

class TcpServer : public QWidget
{
    Q_OBJECT

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

private:
    Ui::TcpServer *ui;
    int port;
    Server *server;

protected slots:
    void slotupdateserver(QString, int);//接收到server發過來的訊號就更新介面的資訊


private slots:
    void on_pushButtonCreateChattingRoom_clicked();
};

#endif // TCPSERVER_H
#include "tcpserver.h"
#include "ui_tcpserver.h"


TcpServer::TcpServer(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::TcpServer)
{
    ui->setupUi(this);
    port = 8888;

}

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

void TcpServer::on_pushButtonCreateChattingRoom_clicked()
{
    server  = new Server(this, port);
    connect(server, &Server::updateserver, this, &TcpServer::slotupdateserver);
    ui->pushButtonCreateChattingRoom->setEnabled(false);
}

void TcpServer::slotupdateserver(QString msg, int length)
{
    ui->textEdit->append(msg);
}

2、用來監聽的類Server

#ifndef SERVER_H
#define SERVER_H

#include <QTcpServer>
#include <QObject>
#include <QList>
#include "tcpclientsocket.h"

class Server : public QTcpServer
{
    Q_OBJECT //為了實現訊號和槽的通訊
public:
    Server(QObject *parent = 0, int port = 0);
    QList<TcpClientSocket*> tcpclientsocketlist;
protected:
    void incomingConnection(int socketDescriptor);//只要出現一個新的連線,就會自動呼叫這個函式
protected slots:
    void sliotupdateserver(QString, int);//用來處理tcpclient發過來的訊號
    void slotclientdisconnect(int);

signals:
    void updateserver(QString, int);//傳送訊號給介面,讓介面更新資訊

};

#endif // SERVER_H
#include "server.h"
#include <QHostAddress>

Server::Server(QObject *parent, int port):QTcpServer(parent)
{
    listen(QHostAddress::Any, port); //監聽
}

void Server::incomingConnection(int socketDescriptor)
{
    TcpClientSocket *tcpclientsocket = new TcpClientSocket(this);//只要有新的連線就生成一個新的通訊套接字
    //將新建立的通訊套接字描述符指定為引數socketdescriptor
    tcpclientsocket->setSocketDescriptor(socketDescriptor);

    //將這個套接字加入客戶端套接字列表中
    tcpclientsocketlist.append(tcpclientsocket);


    //接收到tcpclientsocket傳送過來的更新介面的訊號
    connect(tcpclientsocket, &TcpClientSocket::updateserver, this, &Server::sliotupdateserver);
    connect(tcpclientsocket, &TcpClientSocket::clientdisconnected, this, &Server::slotclientdisconnect);

}

void Server::sliotupdateserver(QString msg, int length)
{
    //將這個訊號傳送給介面
    emit updateserver(msg, length);

    //將收到的資訊傳送給每個客戶端,從套接字列表中找到需要接收的套接字
    for(int i = 0; i < tcpclientsocketlist.count(); i++)
    {
        QTcpSocket *item = tcpclientsocketlist.at(i);
//        if(item->write((char*)msg.toUtf8().data(), length) != length)
//        {
//            continue;
//        }
        item->write(msg.toUtf8().data());
    }

}

void Server::slotclientdisconnect(int descriptor)
{
    for(int i = 0; i < tcpclientsocketlist.count(); i++)
    {
        QTcpSocket *item = tcpclientsocketlist.at(i);
        if(item->socketDescriptor() == descriptor)
        {
            tcpclientsocketlist.removeAt(i);//如果有客戶端斷開連線, 就將列表中的套接字刪除
            return;
        }
    }
    return;
}
3、用來通訊的類TcpClientSocket
#ifndef TCPCLIENTSOCKET_H
#define TCPCLIENTSOCKET_H

#include <QTcpSocket>

class TcpClientSocket : public QTcpSocket
{
    Q_OBJECT //新增這個巨集是為了實現訊號和槽的通訊
public:
    TcpClientSocket(QObject *parent = 0);
protected slots:
    void receivedata();//處理readyRead訊號讀取資料
    void slotclientdisconnected();//客戶端斷開連線觸發disconnected訊號,這個槽函式用來處理這個訊號

signals:
    void updateserver(QString, int);//用來告訴tcpserver需要跟新介面的顯示
    void clientdisconnected(int); //告訴server有客戶端斷開連線
};

#endif // TCPCLIENTSOCKET_H
#include "tcpclientsocket.h"

TcpClientSocket::TcpClientSocket(QObject *parent)
{
    //客戶端傳送資料過來就會觸發readyRead訊號
    connect(this, &TcpClientSocket::readyRead, this, &TcpClientSocket::receivedata);
    connect(this, &TcpClientSocket::disconnected, this, &TcpClientSocket::slotclientdisconnected);
}

void TcpClientSocket::receivedata()
{
//    while(bytesAvailable() > 0)
//    {
//        int length = bytesAvailable();
//        char buf[1024]; //用來存放獲取的資料
//        read(buf, length);
//        QString msg = buf;
//        //發訊號給介面,讓介面顯示登入者的資訊
//        emit updateserver(msg, length);
//    }
    int length = 10;
    QByteArray array = readAll();
    QString msg = array;
    emit updateserver(msg, length);
}

void TcpClientSocket::slotclientdisconnected()
{
    emit clientdisconnected(this->socketDescriptor());
}

客戶端

客戶端只需要一個類就行了,這個類我們只需要建立一個通訊套接字來和伺服器進行通訊就可以了。

#ifndef TCPCLIENT_H
#define TCPCLIENT_H

#include <QWidget>
#include <QTcpSocket>

namespace Ui {
class TcpClient;
}

class TcpClient : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButtonEnter_clicked();
    void slotconnectedsuccess();//用來處理連線成功的訊號
    void slotreceive();//接收伺服器傳過來的資訊
    void on_pushButtonSend_clicked();
    void slotdisconnected();//用來處理離開聊天室的訊號


private:
    Ui::TcpClient *ui;
    bool status;//用來判斷是否進入了聊天室
    int port;
    QHostAddress *serverIP;
    QString userName;
    QTcpSocket *tcpsocket;
};

#endif // TCPCLIENT_H
#include "tcpclient.h"
#include "ui_tcpclient.h"
#include <QHostAddress>
#include <QMessageBox>

TcpClient::TcpClient(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::TcpClient)
{
    ui->setupUi(this);
    //將進入聊天室的標誌位置為false
    status = false;
    //埠為8888
    port = 8888;
    ui->lineEditServerPort->setText(QString::number(port));//介面中埠預設顯示8888

    serverIP = new QHostAddress();

    //未進入聊天室內不能傳送資訊
    ui->pushButtonSend->setEnabled(false);
}

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

//進入聊天室
void TcpClient::on_pushButtonEnter_clicked()
{
    //首先判斷這個使用者是不是在聊天室中
    if(status == false)
    {
        //不在聊天室中就和伺服器進行連線
        QString ip = ui->lineEditServerIp->text();//從介面獲取ip地址
        if(!serverIP->setAddress(ip))//用這個函式判斷IP地址是否可以被正確解析
        {
            //不能被正確解析就彈出一個警告視窗
            QMessageBox::warning(this, "錯誤", "IP地址不正確");
            return;
        }
        if(ui->lineEditUserName->text() == "")
        {
            //使用者名稱不能為空
            QMessageBox::warning(this, "錯誤", "使用者名稱不能為空");
            return;
        }

        //從介面獲取使用者名稱
        userName = ui->lineEditUserName->text();
        //建立一個通訊套接字,用來和伺服器進行通訊
        tcpsocket = new QTcpSocket(this);

        //和伺服器進行連線
        tcpsocket->connectToHost(*serverIP, port);

        //和伺服器連線成功能會觸發connected訊號
        connect(tcpsocket, &QTcpSocket::connected, this, &TcpClient::slotconnectedsuccess);
        //接收到伺服器的資訊就會觸發readyRead訊號
        connect(tcpsocket, &QTcpSocket::readyRead, this, &TcpClient::slotreceive);



        //將進入聊天室的標誌位置為true;
        status = true;
    }
    else//已經進入了聊天室
    {
        int length = 0;
        QString msg = userName + ":Leave Chat Room";
//        if((length = tcpsocket->write((char*)msg.toUtf8().data(), msg.length())) != msg.length())
//        {
//            return;
//        }
        tcpsocket->write(msg.toUtf8().data());
        tcpsocket->disconnectFromHost();
        status = false;
        //離開聊天室就會觸發disconnected訊號
        connect(tcpsocket, &QTcpSocket::disconnected, this, &TcpClient::slotdisconnected);
    }
}

//用來處理連線成功的訊號
void TcpClient::slotconnectedsuccess()
{
    //進入聊天室可以傳送資訊了
    ui->pushButtonSend->setEnabled(true);
    //將進入聊天的按鈕改為離開聊天室
    ui->pushButtonEnter->setText("離開聊天室");

    int length = 0;
    //將使用者名稱傳送給伺服器
    QString msg= userName + " :Enter Chat Room";

//    if((length = tcpsocket->write((char*)msg.toUtf8().data(), msg.length())) != msg.length())
//    {
//        return;
//    }
    tcpsocket->write(msg.toUtf8().data());
}


void TcpClient::slotreceive()
{
//    while(tcpsocket->bytesAvailable() > 0 )
//    {
//        QByteArray datagram;
//        datagram.resize(tcpsocket->bytesAvailable());
//        tcpsocket->read(datagram.data(), datagram.size());
//        QString msg = datagram.data();
//        ui->textEdit->append(msg.left(datagram.size()));
//    }
    QByteArray array = tcpsocket->readAll();
    ui->textEdit->append(array);
}

void TcpClient::on_pushButtonSend_clicked()
{
    if(ui->lineEditSend->text() == "")
    {
        return;
    }
    QString msg = userName + ":" + ui->lineEditSend->text();
   // tcpsocket->write((char*)msg.toUtf8().data(), msg.length());
    tcpsocket->write(msg.toUtf8().data());
    ui->lineEditSend->clear();
}

void TcpClient::slotdisconnected()
{
    ui->pushButtonSend->setEnabled(false);
    ui->pushButtonEnter->setText("進入聊天室");
}

編譯完之後執行的介面就是這樣的


整個通訊的步驟

【注】這裡伺服器的說明用藍色的字標識出來,客戶端用紫色標識。

首先我們點選伺服器的建立聊天室TcpServer類的中的相應的槽函式會建立一個Server類的物件,然後呼叫建構函式會進行監聽。一旦有客戶端點選進入聊天室的按鈕,客戶端的tcpsocket就會和伺服器進行連線只要伺服器監聽到新的連線,Server類物件就會自動呼叫incomingConnection(int socketDescripter)這個函式。接著我們只需要在這個函式中建立一個通訊套接字也就是TcpClientSocket物件,並且將這個套接字描述符設定為引數socketDescripter,然後將這個套接字加入套接字列表中。這個時候客戶端和伺服器連線成功了,客戶端就會觸發一個connected訊號,需要我們寫一個槽函式來處理這個訊號,這裡的處理就是將這個使用者名稱和“Enter Chat Room”傳送給伺服器。伺服器一旦收到資訊,就會觸發readyRead訊號,這個時候我們需要在寫一個相應的槽函式來讀取套接字中的資訊。我們這裡是用readall來讀取套接字中的所有資訊。當收到資料的時候,我們需要發一個訊號給Server,將讀取的資訊再發送給所有在聊天室裡的使用者,所以在Server類中要新增一個相應的槽函式來將資料傳送給所有的使用者。因為同時需要在伺服器介面中顯示所有使用者發過來的訊息,所以需要在剛剛那個槽函式中將自己定義的一個訊號傳送給TcpServer類,讓其將收到的訊息顯示在介面中。客戶端收到訊息之後也會觸發readyRead訊號,客戶端只需要將從套接字中讀取的訊息顯示在見面中就行了。客戶端傳送訊息的時候需要從QLineEdit物件中讀取內容,然後通過tcpsocket->write()函式將訊息傳送給伺服器就行了。伺服器同樣會將收到的資訊傳送給所有的使用者,同時顯示在自己的介面中。最後客戶端退出聊天室,客戶端點選離開聊天室按鈕,進入相應的槽函式中呼叫disconnectFromHost()函式,就會和伺服器斷開連線,一旦有呼叫了這個函式,就會觸發disconnected訊號,然後我們需要寫一個相應的槽函式來處理這個訊號,我們所做的處理就是將這個使用者名稱和“Leave Chat Roo”傳送給伺服器伺服器那一端檢測到有客戶端斷開連線也會觸發disconnected訊號,這個時候我們需要將套接字列表中將對應的套接字刪除就可以了。

相關推薦

Qt學習十三—— TCP/IP客戶連線伺服器

一、TCP和UDP的區別這裡我會用一個表格來顯示這兩者的區別比較項TCPUDP是否連線面向連線無連線傳輸是否可靠可靠不可靠流量控制提供不提供工作方式全雙工可以是全雙工應用場合大量資料少量資料速度慢快二、incomingConnection函式這個函式和之前講過的newConn

QT學習十三基於Linux qt的聊天室

  前天將Windows下的聊天室改版了,但是我昨天才發現那個版本有一個缺陷,一個我一開始沒有注意到的錯誤,直到昨天除錯的時候才發現,就是我資料傳輸的時候是用std::string型別的,一開始是為了

TCP通訊】客戶連線一個伺服器總結

Windows下TCP通訊,多個客戶端連線伺服器端。 一、伺服器端流程實現如下: 1、通過socket函式,建立基於流式型別的socket,可名為serverSocket; 2、呼叫bind函式,繫結本地的監聽埠號和本地IP地址; 3、呼叫listen函式,開始監聽客戶端請求。其中該函式第二個引數指定了最大

java網路程式設計基於TCP客戶連線伺服器

package com.test.net; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.S

C++基於TCP/IP簡單的客戶伺服器通訊程式例項

本篇文章實現了一個基於TCP 的一個非常簡單的客戶/伺服器通訊程式例項。該程式中通訊協議使用的是面向連線的TCP協議SOCK_STREAM, 伺服器的ip地址為本地地址即: 127.0.0.1,埠號為自定義的5099(大於1024即可),服務端的功能只要有客戶

Linux基於TCP/IP簡單的客戶伺服器通訊程式例項

伺服器端程式碼: #include<stdio.h> #include<string.h> #include<errno.h> #include<sys/socket.h> #include<resolv.h> #

Qt 學習 240:隱式數據共享

深拷貝和淺拷貝 != 這樣的 pointer map painter pos 轉載 多線程 博客轉載自:https://www.devbean.net/2013/01/qt-study-road-2-implicit-sharing/ Qt 中許多 C++ 類使用了隱式數據

ESP8266學習 十三 (SPI讀取max6675)

通過spi模組讀取四路max6675溫度資料   max6675.lua檔案: SpiPinCS1 = 8--GPIO15 SpiPinCS2 = 7--GPIO13 SpiPinCS3 = 0--GPIO16 SpiPinCS4 = 1--GPIO5 --定義四路

Qt 學習 242:QListWidget、QTreeWidget 和 QTableWidget

上一章我們瞭解了 model/view 架構的基本概念。現在我們從最簡單的QListWidget、QTreeWidget和QTableWidget三個類開始瞭解最簡單的 model/view 的使用。這部分內容的確很難組織。首先,從最標準的 model/view 開始,往往會糾結於複雜的程式碼;但是

0.0 開啟自己的QT學習

2008年左右接觸過一段時間的QT,感覺到了QT的方便快捷,之前我是做JAVA的,不過C++也還能看得懂,一共用QT開發了3個月左右的時間,一直是公司的同事帶著做。N年過去了,QT忘得差不多了,QT也有了很大的發展,現在重新學習是為了之後的工作。 打算用一個周的時間學完QT,達到能開發和看懂QT

QT學習---訊號與槽問題解析

前兩天用到了QT的訊號與槽這個機制,剛開始發射訊號的時候,我是這麼寫的語句 connect(sender,SINGAL(),receiver,SLOT()) 由於我用的是QT 5.11這個本,從網上查到的例子來說,大部分都是以上那個形式,也沒有問題,而實際上在QT5.11版本上,向下面這樣寫

weifu的qt學習

1、寫入文字檔案 使用文字檔案輸出的步驟:         1)包含標頭檔案fstream         2)建立一個ofstream物件         3)將該ofstream物件同一個檔案關聯起來。 關聯的方法:ofstream物件.open("文字檔名")

Qt 學習之路 219:事件的接受與忽略當重寫事件回撥函式時,時刻注意是否需要通過呼叫父類的同名函式來確保原有實現仍能進行!有好幾個例子。為什麼要這麼做?而不是自己去手動呼叫這兩個函式呢?因為我們無法確認父類中的這個處理函式有沒有額外的操作

版本: 2012-09-29 2013-04-23 更新有關accept()和ignore()函式的相關內容。 2013-12-02 增加有關accept()和ignore()函式的示例。 上一章我們介紹了有關事件的相關內容。我們曾經提到,事件可以依情況接受和忽略。現在,我們就

QT學習資料部落格:《Qt 實戰一二三》和《Qt 學習 2》等

參考原貼 https://blog.csdn.net/dpsying/article/details/80615320 目的:僅供自己學習,並無他用。 參考書目: 1《Qt5開發及例項》(Qt 5.8為平臺)         

Qt學習_12(簡易資料管理系統)

前言   最近從大陸來到臺灣,之間雜事很多,擠不出時間來更新部落格…   這次主要是通過做一個簡易的資料庫管理系統,來學習在Qt中對資料庫,xml,介面的各種操作,進一步熟悉Qt。一般而言資料通常存在檔案,資料庫,xml中,本文主要是介紹了sqlite,xml這2種儲存資料的方法,實現了一個家用電器產

Qt 學習---安裝篇】QT5.7.1+VS2013軟體開發環境配置

參考:https://blog.csdn.net/liushuiwen101423/article/details/70882534 安裝任務:完成Qt5.7.1載入到VS2013環境下,程式設計執行Qt應用程式,有詳細步驟,最後完成一個空白視窗UI執行顯示1.基本配置PC

QT學習(8):事件的傳遞和忽略

事件有兩個函式,accept()和event(),前者代表該元件希望接受這個事件,這個事件將不會傳播.而後者代表該元件希望忽略這個事件那麼事件就會繼續向它的父元件傳播.所有事件都是預設為accept()的,但是在QWidget中的所有事件回撥函式都是呼叫了ign

Qt 學習 226:反走樣

我們在光柵圖形顯示器上繪製非水平、非垂直的直線或多邊形邊界時,或多或少會呈現鋸齒狀外觀。這是因為直線和多邊形的邊界是連續的,而光柵則是由離散的點組成。在光柵顯示裝置上表現直線、多邊形等,必須在離散位置取樣。由於取樣不充分重建後造成的資訊失真,就叫走樣;用於減少或消除這種效

Qt學習自定義按鈕

按鈕有三種狀態:當滑鼠點選的時候,當滑鼠進入按鈕的時候,當滑鼠沒有進入按鈕的時候,這3個不同的狀態。 上程式碼~~~~ pushBtn_widget.h  /* 貼圖按鈕的狀態: 進入, 離開, 按下 */ #ifndef PUSHBTNWIDGET_H #def

Tensorflow深度學習七:mnist手寫數字識別程式

之前學習的第一個深度學習的程式就是mnist手寫字型的識別,那個時候對於很多概念不是很理解,現在回過頭再看當時的程式碼,理解了很多,現將加了註釋的程式碼貼上,與大家分享。(本人還是在學習Tensorflow的初始階段,如果有什麼地方理解有誤,還請大家不吝指出。)