49.Qt-網路程式設計之QTCPSocket和QTCPServer(實現簡易網路除錯助手)
在上章 48.QT-網路通訊講解1,我們學習了網路通訊基礎後,本章便來實戰一篇.原始碼正在整理中,等下貼地址.
PS:支援客戶端和伺服器,提供原始碼,並且伺服器支援多客戶端連入,並且可以指定與個別客戶端傳送資料,也可以給所有連入的客戶端傳送資料.
1.效果圖所下所示:
如下圖所示,當伺服器狀態下,如果有客戶端連入,會提示客戶端資訊:
2.效果操作
客戶端操作:
伺服器操作:
從上面操作可以看出,伺服器支援多客戶端連入,並且可以指定與個別客戶端傳送資料,也可以給所有連入的客戶端傳送資料.
3.首先建立UI
4.注意事項
不管是伺服器還是客戶端,都可以通過peerAddress()和peerPort()來獲取目標地址和目標埠
4.1伺服器監聽時
比如伺服器,則可以通過QTcpSocket的peerAddress()則可以獲取連入的客戶端地址
也可以通過children()來獲取所有連入的客戶端(需要注意的是也會獲取到伺服器本身的tcp地址和埠),示例如下:
QList<QTcpSocket *> m_tcps = m_server.findChildren<QTcpSocket *>(); foreach (QTcpSocket *tcp, m_tcps) { qDebug() << "Address:" << tcp->peerAddress (); qDebug() << "Port:" << tcp->peerPort (); }
如果我們只向連入的客戶端某個埠傳送資料時,就可以通過上面的方式篩選出來.
這樣做的話如果覺得很麻煩,也可以將之前連線上的客戶端存到QList裡再進行篩選.
4.2 QTcpSocket步驟
- 首先通過connectToHost()來連線伺服器.
- 然後呼叫waitForConnected()來判斷是否連線伺服器超時
- 當我們接收到伺服器資料的時候,則會發出readyRead()訊號,然後再進行read ()讀取發來的資料
- 傳送資料時,則呼叫write()函式進行傳送,當bytesWritten()訊號函式觸發時,便可以獲取成功傳送的資料長度.
注意:如果read到的資料長度量不是自己想要的,此時我們便可以通過bytesAvailable()來讀取接收到的資料長度量.當達到多少時,再進行read ()讀取.
4.3 QTcpServer步驟
- 首先通過listen(QHostAddress::AnyIPv4, port)來監聽所有來自IPV4的客戶端
- 當有新的客戶端連線伺服器的時候,會自動觸發newConnection()訊號函式,然後我們可以通過通過QTcpSocket * nextPendingConnection()成員函式來獲取當前連線上的新的客戶端類.然後再對QTcpSocket來進行訊號槽繫結
- 當客戶端發來資料的時候,則可以通過我們定義的onServerDataReady()來讀取資料
- 當我們向某個連線的客戶端傳送資料時,則通過m_server.findChildren()來篩選出來,然後write即可.
5.程式碼介紹
5.1 標頭檔案介紹
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QTcpSocket> #include <QTcpServer> #include <QMessageBox> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT QTcpSocket m_client; QTcpServer m_server; QString targetAddr; int targetPort; public: explicit Widget(QWidget *parent = 0); ~Widget(); private slots: void on_btn_switch_clicked(); void on_tcpMode_currentIndexChanged(int index); void on_btn_send_clicked(); //傳送按鈕 void on_betn_clear_clicked(); //清空按鈕 //客戶端槽函式 void onClientConnected(); void onClientDisconnected(); void onClientDataReady(); void onClientBytesWritten(qint64 bytes); void onClientErr(QAbstractSocket::SocketError socketError); //伺服器槽函式 void onServerNewConnection(); void onServerConnected(); void onServerDisconnected(); void onServerDataReady(); void onServerBytesWritten(qint64 bytes); private: void startConnect(bool ison); void initClientSignals(); //初始化客戶端訊號槽 bool startClient(); //啟動客戶端 void initServerSignals(); //初始化客戶端訊號槽 bool startServer(); //啟動伺服器 Ui::Widget *ui; }; #endif // WIDGET_H
5.2 widget.cpp介紹
該cpp主要是用來處理介面操作的函式
#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); startConnect(false); on_tcpMode_currentIndexChanged(0); initClientSignals(); //初始化客戶端訊號槽 initServerSignals(); //初始化伺服器訊號槽 //限制只能數字輸入 QRegExp regx("[0-9]+$"); QValidator *validator = new QRegExpValidator(regx, this ); ui->ipAddr1->setValidator( validator ); ui->ipAddr2->setValidator( validator ); ui->ipAddr3->setValidator( validator ); ui->ipAddr4->setValidator( validator ); ui->ipPort->setValidator( validator ); } void Widget::on_tcpMode_currentIndexChanged(int index) { if(index==0) //clent { ui->ipPortLabel->setText("伺服器埠號:"); ui->ipAddLabel->show(); ui->ipAdds->show(); ui->targetLabel->hide(); ui->targetObject->hide(); } else { ui->ipAddLabel->hide(); ui->ipAdds->hide(); ui->ipPortLabel->setText("本地埠號:"); } } void Widget::on_btn_switch_clicked() //切換連線開關 { bool ret; if(ui->btn_switch->text()=="開啟連線") { if(ui->tcpMode->currentIndex()==0) //啟動客戶端 ret=startClient() ; else ret=startServer(); if(ret) startConnect(true); } else { if(ui->tcpMode->currentIndex()==0) //啟動客戶端 m_client.close(); else { if( m_server.isListening() ) { QList<QTcpSocket *> m_tcps = m_server.findChildren<QTcpSocket *>(); foreach (QTcpSocket *tcp, m_tcps) { tcp->close(); } m_server.close(); } } startConnect(false); } } void Widget::startConnect(bool ison) { if(!ison) { ui->btn_switch->setStyleSheet("color:blue;border: 1px solid blue"); ui->btn_switch->setText("開啟連線"); //使能 ui->ipAddr1->setEnabled(true); ui->ipAddr2->setEnabled(true); ui->ipAddr3->setEnabled(true); ui->ipAddr4->setEnabled(true); ui->tcpMode->setEnabled(true); ui->ipPort->setEnabled(true); ui->localPort->setText(""); } else { ui->btn_switch->setStyleSheet("color:red;border: 1px solid red"); ui->btn_switch->setText("關閉連線"); //失能 ui->ipAddr1->setEnabled(false); ui->ipAddr2->setEnabled(false); ui->ipAddr3->setEnabled(false); ui->ipAddr4->setEnabled(false); ui->tcpMode->setEnabled(false); ui->ipPort->setEnabled(false); targetAddr=""; targetPort=0; ui->sendLenLabel->setText("0"); } } void Widget::on_betn_clear_clicked() { ui->recvEdit->clear(); targetAddr=""; targetPort=0; } void Widget::on_btn_send_clicked() { if(ui->btn_switch->text()!="開啟連線") { if(ui->tcpMode->currentIndex()==0) //客戶端 { m_client.write(ui->sendEdit->toPlainText().toLocal8Bit(),ui->sendEdit->toPlainText().toLocal8Bit().length()); } else { if(ui->targetObject->currentText()!="所有物件") { QList<QTcpSocket *> m_tcps = m_server.findChildren<QTcpSocket *>(); foreach (QTcpSocket *tcp, m_tcps) { if(ui->targetObject->currentText() == tcp->objectName()) { tcp->write(ui->sendEdit->toPlainText().toLocal8Bit(),ui->sendEdit->toPlainText().toLocal8Bit().length()); break; } } } else //所有連線上的客戶端都發送一遍 { QList<QTcpSocket *> m_tcps = m_server.findChildren<QTcpSocket *>(); foreach (QTcpSocket *tcp, m_tcps) { tcp->write(ui->sendEdit->toPlainText().toLocal8Bit(),ui->sendEdit->toPlainText().toLocal8Bit().length()); } } } } } Widget::~Widget() { QList<QTcpSocket *> m_tcps = m_server.findChildren<QTcpSocket *>(); foreach (QTcpSocket *tcp, m_tcps) { tcp->close(); } if(m_client.isOpen()) { m_client.close(); qDebug()<<"m_client close"; } delete ui; }
5.3 clentHandler.cpp介紹
該cpp主要用來處理客戶端操作相關的檔案.
#include "widget.h" #include "ui_widget.h" void Widget::initClientSignals() //初始化客戶端訊號槽 { connect(&m_client, SIGNAL(connected()), this, SLOT(onClientConnected())); connect(&m_client, SIGNAL(disconnected()), this, SLOT(onClientDisconnected())); connect(&m_client, SIGNAL(readyRead()), this, SLOT(onClientDataReady())); connect(&m_client, SIGNAL(bytesWritten(qint64)), this, SLOT(onClientBytesWritten(qint64))); connect(&m_client, SIGNAL(error(QAbstractSocket::SocketError )), this, SLOT(onClientErr(QAbstractSocket::SocketError))); } bool Widget::startClient() //啟動客戶端 { QString ip = QString("%1.%2.%3.%4").arg(ui->ipAddr1->text()).arg(ui->ipAddr2->text()).arg(ui->ipAddr3->text()).arg(ui->ipAddr4->text()); qDebug()<<ip; m_client.connectToHost(ip, ui->ipPort->text().toInt()); if(m_client.waitForConnected(800)) { return true; } else { QMessageBox::information(this,"提示",QString("連線超時"),QMessageBox::Ok); return false; } } void Widget::onClientConnected() { startConnect(true); QMessageBox::information(this,"提示","連線成功",QMessageBox::Ok); ui->localPort->setText(QString("%1").arg(m_client.localPort())); //顯示本地埠號 } void Widget::onClientDisconnected() { startConnect(false); QMessageBox::information(this,"提示","斷開完成",QMessageBox::Ok); } void Widget::onClientDataReady() { if(m_client.peerAddress().toString()!=targetAddr || m_client.peerPort()!=targetPort ) { targetAddr = m_client.peerAddress().toString(); targetPort = m_client.peerPort(); ui->recvEdit->insertPlainText("[接受來自"+ targetAddr+":"+QString("%1").arg(targetPort)+"]:\r\n"); } ui->recvEdit->moveCursor(QTextCursor::End); ui->recvEdit->insertPlainText(QString::fromLocal8Bit(m_client.readAll())+"\r\n"); } void Widget::onClientBytesWritten(qint64 bytes) { qDebug() << "onBytesWritten:" << bytes; ui->sendLenLabel->setText(QString("%1").arg(ui->sendLenLabel->text().toInt()+bytes)); } void Widget::onClientErr(QAbstractSocket::SocketError socketError) { qDebug()<<"onClientErr:"<<socketError; m_client.close(); startConnect(false); QMessageBox::information(this,"提示",QString("連線失敗:%1").arg((int)socketError),QMessageBox::Ok); }
5.4 serverHandler.cpp介紹
該cpp主要用來處理伺服器操作相關的檔案
#include "widget.h" #include "ui_widget.h" void Widget::initServerSignals() //初始化訊號槽 { connect(&m_server, SIGNAL(newConnection()), this, SLOT(onServerNewConnection())); } bool Widget::startServer() //啟動伺服器 { if(m_server.listen(QHostAddress::AnyIPv4,ui->ipPort->text().toInt())) //只監聽IPV4的所有客戶端 { ui->targetLabel->show(); ui->targetObject->show(); ui->localPort->setText(QString("%1").arg(m_server.serverPort())); return true; } else return false; } void Widget::onServerNewConnection() { qDebug() << "onNewConnection"; QTcpSocket* tcp = m_server.nextPendingConnection(); //獲取新的客戶端資訊 QString info=tcp->peerAddress().toString()+":"+QString("%1").arg(tcp->peerPort()); ui->targetObject->addItem(info); QMessageBox::information(this,"提示",QString("新的客戶端連入:%1").arg(info),QMessageBox::Ok); tcp->setObjectName(info); //設定名稱,方便查詢 connect(tcp, SIGNAL(connected()), this, SLOT(onServerConnected())); connect(tcp, SIGNAL(disconnected()), this, SLOT(onServerDisconnected())); connect(tcp, SIGNAL(readyRead()), this, SLOT(onServerDataReady())); connect(tcp, SIGNAL(bytesWritten(qint64)), this, SLOT(onServerBytesWritten(qint64))); } void Widget::onServerConnected() { } void Widget::onServerDisconnected() { QTcpSocket* tcp = dynamic_cast<QTcpSocket*>(sender()); if( tcp != NULL ) //從連線物件中移除掉 { qDebug() << "onServerDisconnected"; qDebug() << "Local Address:" << tcp->peerAddress(); qDebug() << "Local Port:" << tcp->peerPort(); QString info=tcp->peerAddress().toString()+":"+QString("%1").arg(tcp->peerPort()); QMessageBox::information(this,"提示",QString("客戶端斷開連線:%1").arg(info),QMessageBox::Ok); int index = ui-> targetObject ->findText(info); if(index>=0) ui->targetObject->removeItem(index); } } void Widget::onServerDataReady() { QTcpSocket* tcp = dynamic_cast<QTcpSocket*>(sender()); if(tcp->peerAddress().toString()!=targetAddr || tcp->peerPort()!=targetPort ) { targetAddr = tcp->peerAddress().toString(); targetPort = tcp->peerPort(); ui->recvEdit->insertPlainText("[接受來自"+ targetAddr+":"+QString("%1").arg(targetPort)+"]:\r\n"); } ui->recvEdit->moveCursor(QTextCursor::End); ui->recvEdit->insertPlainText(QString::fromLocal8Bit(tcp->readAll())+"\r\n"); } void Widget::onServerBytesWritten(qint64 bytes) { qDebug() << "onBytesWritten:" << bytes; ui->sendLenLabel->setText(QString("%1").arg(ui->sendLenLabel->text().toInt()+bytes)); }
&n