1. 程式人生 > >QT5 網絡通訊

QT5 網絡通訊

lose 子類 edi png har 描述符 傳輸 connected list

QT5 TCP網絡通訊

  • 服務器與客戶端建立連接listen() - connectToHost(); 觸發newPendingConnect信號
  • 實時數據通訊write(); read(); 觸發readyRead信號

通訊主要使用的類:

QTcpServer Class

QTcpServer類提供了一個基於TCP的服務器。
這個類可以接受傳入的TCP連接。您可以指定端口或讓QTcpServer自動選擇一個端口。您可以收聽特定地址或所有機器的地址。
調用listen()讓服務器偵聽傳入的連接。每次客戶端連接到服務器時,都會發出newConnection()信號。

QTcpSocket Class

QTcpSocket類提供了一個TCP套接字。
TCP(傳輸控制協議)是一種可靠的,面向流的,面向連接的傳輸協議。 它特別適合連續傳輸數據。
QTcpSocket是QAbstractSocket的一個方便的子類,它允許你建立一個TCP連接並傳輸數據流。

建立連接:

服務器端以監聽的方式監聽客服端是否有連接請求

客戶端以調用connectToHost()函數主動連接服務器端

tcp協議服務器端實現流程

建立服務器對象

listen服務器, 通過建立的服務器 監聽指定地址/端口的客服端;判斷是否有客戶連接有連接就觸發newConnection();

通過connect處理newConnection()信號;

    server = new QTcpServer(this); //建立一個服務器對象
    server->listen(QHostAddress::Any, 8000);//通過建立的服務器監聽指定ip地址及端口號的客服端,如不指定端口號,系統會隨機分配
    connect(server, QTcpServer::newConnection,
    [=]()
    {
        qDebug() << "有連接進來";
    }
    );

tcp協議客戶端實現流程

建立QTcpSocket套節字(ip,端口)

通過套節字connectToHost()

函數主動連接服務器;連接成功則觸發服務器QTcpServer::newConnection信號;並發送套節字到服務器端;

關閉連接;

QTcpSocket Sc(this);
Sc.connectToHost("127.0.0.1", 8888);//實際代碼中參數要進行類型轉化
Sc.close();

實時通訊:

  • 客戶端到服務器端通訊
  1. 當客戶端與服務器端建立連接後;
  2. 客戶端與服務器端通訊在客戶端通過套節字對象調用write()函數發送上傳內容;
  3. 服務器端有客戶端數據寫入時服務器端會自動調用readyread信號
  4. 服務器端在connect中處理readyread信號,並由nextPendingConnection()函數接收客戶端發送的套節字;
  5. 服務器端對接收的套節字進行相應處理,即完成一次客戶端到服務器端的通訊
  • 服務器端到客戶端的通訊
  1. 當客戶端與服務器端建立連接後;
  2. 服務器通過套節字對象調用write()函數發送上傳內容;客戶端會觸發readyread信號
  3. 客戶端在connect中處理readyread信號

客戶端到服務器端實現代碼:

//服務器端   
 connect(server, QTcpServer::newConnection,
            [=]()
    {
       QTcpSocket socket = server->nextPendingConnection();
        connect(socket, &QTcpSocket::readyRead, [=]()
        {
            tp = socket->readAll();
            ui->textrc->append(tp);
        });
    }
    );
//客戶端
void socket::on_buttonsend_clicked()
{
    QString temp = ui->textrc->toPlainText();
    if(!temp.isEmpty())sock->write(temp.toUtf8());

}

服務器端客戶端實現代碼:

//服務器端
void Widget::on_buttonsend_clicked()
{
    socket->write(ui->textEdit->toPlainText().toUtf8());
}
//客戶端
    connect(sock, &QTcpSocket::readyRead,
            [=]()
    {
       ui->textdis->append(sock->readAll());
    });

完整代碼:

服務器ui設計:

技術分享圖片

服務器端頭文件widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_buttonsend_clicked();

private:
    Ui::Widget *ui;

    QTcpServer *server; //建立服務器對象
    QTcpSocket *socket; //套節字對象
    QByteArray tp;   //
};

#endif // WIDGET_H

服務器端cpp文件 widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("服務器");
    tp = nullptr;

    server = new QTcpServer(this);
    server->listen(QHostAddress::Any, 8000);
    connect(server, QTcpServer::newConnection,
            [=]()
    {
        socket = server->nextPendingConnection();
        connect(socket, &QTcpSocket::readyRead, [=]()
        {
            tp = socket->readAll();
            ui->testdis->append(tp);
        });
    }
    );

}

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

void Widget::on_buttonsend_clicked()
{
    socket->write(ui->textEdit->toPlainText().toUtf8());
}

客戶端ui:

技術分享圖片

客戶端頭文件socket.h:

#ifndef SOCKET_H
#define SOCKET_H

#include <QWidget>
#include <QTcpSocket>
#include <QHostAddress>

namespace Ui {
class socket;
}

class socket : public QWidget
{
    Q_OBJECT

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

private slots:

    void on_buttonLink_clicked();

    void on_buttonsend_clicked();

    void on_serverclose_clicked();

private:
    Ui::socket *ui;

    QTcpSocket *sock;
    QHostAddress adrs;
    quint16 port;
};

#endif // SOCKET_H

客戶端cpp文件socket.cpp

#include "socket.h"
#include "ui_socket.h"

socket::socket(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::socket)
{
    ui->setupUi(this);
    sock = new QTcpSocket(this);
    setWindowTitle("張三");
    connect(sock, &QTcpSocket::readyRead,
            [=]()
    {
       ui->textdis->append(sock->readAll());
    });
}

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

void socket::on_buttonLink_clicked()
{
    QString ip = ui->serverIP->text();
    QString p = ui->serverPort->text();
    sock->connectToHost(ip, p.toUShort());
}

void socket::on_buttonsend_clicked()
{
    QString temp = ui->textEdit->toPlainText();
    if(!temp.isEmpty())sock->write(temp.toUtf8());
}

void socket::on_serverclose_clicked()
{
    sock->close();
}

main.cpp文件

#include "widget.h"
#include <QApplication>
#include "socket.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    socket w1;
    w1.show();

    return a.exec();
}

最終運行效果:

技術分享圖片

當然在具體的實現過程中還有很多很多的細節需要優化;

QT5對tcp協議基本的通訊總結:

  • QTcpServer *p = new QTcpServer(this);//建立服務器對象  QTcpSocket *q = new QTcpSocket(this); //客戶機建立套節字對象
  • p.listen(監聽的客戶ip , 監聽端口port);//監聽客戶機         q.conncetToHost(要連接的服務器ip, 要連接的服務器端口);
  • connect(p, &QTcpServer::newConnection, )連接成功觸發信號   q.write();//發送數劇 到服務器
  • QTcpSocket skt = p.nextPendingConnection();//獲取客戶機套節字   connect(q, &QTcpSocket::readyRead, )//服務端發送數據客戶端觸發信號
  • connect(skt, &QTcpSocket::readyRead, )//客戶發送數據觸發信號    q.readall();//讀取客戶端發送的數據;  
  • skt.readall();//讀取客戶端發送的數據; 客戶端處理數據
  • 服務器端處理數據 

QT5 UDP網絡通訊

UDP沒有服務器與客戶端之分;單純通過writeDatagram發( 參數1, 參數2,參數3)送指定的內容(參數1)到指定的ip(參數2),端口(參數3)上;

當收取到網絡中的數據發送,就會觸發自己的readyRead信號;readDatagram(參數1, 參數2,參數3),保存讀取的內容(參數1);保存對方ip(參數2);保存對方端口(參數3)

具體實現過程:

  • 建立QUdpSocket套節字 QUdpSocket* p = new QUdpSocket(this);
  • 綁定本程序端口號 bind() p.bind(8000);
  • 通過writeDatagram()發送數據到指定目標 p.writeDatagram();
  • 當有readyRead信號發生通過readDatagram()函數讀取保存數據       p.readDatagram();

實現代碼:

    QUdpSocket *udp = new QUdpSocket(this);
    udp->bind(8000);
    connect(udp, &QUdpSocket::readyRead, [=]()
    {

       char temp[1024] = {0};
       QHostAddress q;
       quint16 p;
       udp->readDatagram(temp, sizeof(temp), &q, &p);
       ui->textdis->append(temp);
    });

/......./

//按鍵確定發送
void Widget::on_buttonlink_clicked()
{
    udp->writeDatagram(ui->textsend->toPlainText().toUtf8(), QHostAddress(ui->ip->text()), ui->port->text().toUShort());
}

整體代碼:

ui設計:

技術分享圖片

widget.h頭文件

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QUdpSocket>
namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_buttonlink_clicked();

private:
    Ui::Widget *ui;
    QUdpSocket *udp;
};

#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QHostAddress>
#include <QDialog>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("8000");
    udp = new QUdpSocket(this);
    udp->bind(8000);
    udp->joinMulticastGroup(QHostAddress(""));
    connect(udp, &QUdpSocket::readyRead, [=]()
    {

       char temp[1024] = {0};
       QHostAddress q;
       quint16 p;
       udp->readDatagram(temp, sizeof(temp), &q, &p);
       ui->textdis->append(temp);
    });
}

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

void Widget::on_buttonlink_clicked()
{
    udp->writeDatagram(ui->textsend->toPlainText().toUtf8(), QHostAddress(ui->ip->text()), ui->port->text().toUShort());
}

main.cpp文件

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

以便測試:我們先編譯生成一份客戶端;再在源文件中改變bind端口號為8888再生成一份客戶端;最終就會有兩份客戶端以便相互通信測試

運行測試結果:

技術分享圖片

由於UDP不需要服務器,所以,UDP發送的數據,只要能接收到你的ip及端口的客戶端就殾能收到信息;所以在局域網內,ip地址欄可輸入

255.255.255.255 即整個局域網內的客戶端都能收到信息;

UDP通訊組包

為了滿足,發送的信息指定ip段內的客戶收到信息可以用函數JoinMulticastGroup(IPAddress)加入到組;根據msdn記載,沒錯是同樣的功能;

技術分享圖片

技術分享圖片

目前個人理解也不深;詳細數據可查msdn;可用leaveMulticastGroup()函數離開組翻;

Tcp 與 Udp的比較:

Udp不需要服務器,只管發送數據,不對數據進行檢查,也不對接收者檢測;速度快,易丟包,做即時數據傳送比較好;

Tcp方式總結:

服務器端:QTcpServer

1】基本用法: 創建一個QTcpServer,然後調用listen函數監聽相應的地址和端口。當有客戶端鏈接到服務器時,會有信號newConnection()產生。 調用nextPendingConnection()接受一個掛起的TcpSocket連接,該函數返回一個指向QTcpSocket的指針,同時進入到QAbstractSocket::ConnectedState狀態。這樣就可以和客戶端進行通信了。如果錯誤發生,可以用函數serverError()返回錯誤類型,用errorString()返回錯誤提示字符串。 調用close使得QTcpServer停止監聽連接請求。盡管QTcpServer使用了事件循環,但是可以不這麽使用。利用waitForNewConnection(),該函數阻塞直到有連接可用或者時間超時。 2】重要函數: void incomingConnection (int socketDescriptor); 當一個連接可以用時,QTcpServer調用該函數。其基本過程是現創建一個QTcpSocket,設置描述符和保存到列表,最後發送newConnection() 事件消息。 QTcpSocket* QTcpServer::nextPendingConnection(); 返回下一個將要連接的QTcpSocket對象,該返回對象是QTcpServer的子對象,意味著如果刪除了QTcpSServer,則刪除了該對象。也可以在你不需要該對象時,將他刪除掉,以免占用內存。 客戶端:QTcpSocket,QAbstractSocket 1】基本用法: 在客戶端創建一個QTcpSocket,然後用connectToHost函數向對應的主機和端口建立連接。 任何時候,可以用state()查詢狀態,初始為UnconnectedState,然後調用連接函數之後,HostLookupState,如果連接成功進入ConnectedState,並且發送hostFound()信號。 當連接建立,發送connected(),在任何狀態下如果在錯誤發生error()信號發送。狀態改變發送stateChanged()信號。如果QTcpSocket準備好可讀可寫,則isValid() 函數範圍為真。 用read()和write()來讀寫,或者使用readLine()和readAll.當有數據到來的時候,系統會發送readyRead()信號。 bytesAvailable()返回包的字節數,如果你不是一次性讀完數據,新的數據包到來的時候將會附加到內部讀緩存後面。setReadBufferSize()可以設置讀緩存的大小。 用disconnectFromHost()關閉連接,進入ClosingState。當所有數據寫入到socket,QAbstractSocket會關閉該臺socket,同時發送disconnected()消息。 如果想立即終止一個連接,放棄數據發送,調用abort(). 如果遠程主機關閉連接,QAbstractSocket發送QAbstractSocket::RemoteHostClosedError錯誤,但是狀態還停留在ConnectedState,然後發送disconnected()信號。 QAbstractSocket提供幾個函數用來掛起調用線程,知道一定的信號發送,這些函數可以用來阻塞socket: waitForConnected() 阻塞知道一個連接建立。 waitForReadyRead() 阻塞知道有新的數據可以讀取。 waitForBytesWritten() 阻塞直到發送數據寫道socket中。 waitForDisconnected() 阻塞知道鏈接關閉。

QT5 TCP網絡通訊之文件傳送

QT5 網絡通訊