QT5 網絡通訊
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()
關閉連接;
QTcpSocket Sc(this); Sc.connectToHost("127.0.0.1", 8888);//實際代碼中參數要進行類型轉化 Sc.close();
實時通訊:
- 客戶端到服務器端通訊
- 當客戶端與服務器端建立連接後;
- 客戶端與服務器端通訊在客戶端通過套節字對象調用write()函數發送上傳內容;
- 服務器端有客戶端數據寫入時服務器端會自動調用readyread信號
- 服務器端在connect中處理readyread信號,並由nextPendingConnection()函數接收客戶端發送的套節字;
- 服務器端對接收的套節字進行相應處理,即完成一次客戶端到服務器端的通訊
- 服務器端到客戶端的通訊
- 當客戶端與服務器端建立連接後;
- 服務器通過套節字對象調用write()函數發送上傳內容;客戶端會觸發readyread信號
- 客戶端在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 網絡通訊