8.9更新說明
如下圖所示,支持十六進制收發,下載地址已經更新.源碼下載地址:https://download.csdn.net/download/qq_37997682/11504836
在上章 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)); }