學習目標:?TCP網絡通信編程
學習前置環境
運行環境:qt?creator 4.12
QT TCP網絡通信編程-CSDN博客
Qt 線程 QThread類詳解-CSDN博客
學習內容
使用多線程技術實現服務端計數器
?核心代碼
客戶端
客戶端:負責連接服務端,每次連接次數+1。以及連接的報錯信息
#include "dialog.h"
#include "ui_dialog.h"
#include<QDebug>
Dialog::Dialog(QWidget *parent): QDialog(parent), ui(new Ui::Dialog)
{ui->setupUi(this);ui->server_ip->setText("127.0.0.1");ui->server_port->setText("8888");clientSocket =new QTcpSocket;setWindowTitle("連接計數器客戶端");//連接錯誤回調QObject::connect(clientSocket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error), this, [this](QAbstractSocket::SocketError error){switch (error) {case QAbstractSocket::RemoteHostClosedError: // 遠程主機關閉連接// QMessageBox::information(this, "提示", "遠程主機關閉連接", QMessageBox::Yes);break;case QAbstractSocket::HostNotFoundError: // 找不到主機地址QMessageBox::information(this, "提示", "找不到主機地址", QMessageBox::Yes);break;case QAbstractSocket::ConnectionRefusedError: // 連接被對方拒絕(或者超時)QMessageBox::information(this, "提示", "連接被對方拒絕(或者超時)", QMessageBox::Yes);break;default:QMessageBox::information(this, "提示", tr("致命錯誤為:").arg(clientSocket->errorString()), QMessageBox::Yes);}ui->request->setEnabled(true);ui->close->setEnabled(true);});//當 socket 成功連接到服務器時,會發射 connected() 信號。connect(clientSocket,&QTcpSocket::connected,this,[this](){QString str ="已經連接到服務器端\n服務器端ip:"+clientSocket->peerAddress().toString()+"服務器端port:"+QString::number(clientSocket->peerPort());//QMessageBox::information(this, "提示",str , QMessageBox::Yes);ui->request->setEnabled(false);ui->close->setEnabled(true);QString msg=ui->currentv->text()+'\n';clientSocket->write(msg.toUtf8(),msg.length());int count =(ui->currentv->text().toUInt());ui->currentv->setNum(++count);});//當 socket 與服務器斷開連接時,會發射 disconnected() 信號。connect(clientSocket,&QTcpSocket::disconnected,this,[this](){QString str ="已斷開與服務器端的連接\n服務器端ip:"+clientSocket->peerAddress().toString()+"服務器端port:"+QString::number(clientSocket->peerPort());//QMessageBox::information(this, "提示",str , QMessageBox::Yes);clientSocket->close();});ui->request->setEnabled(true);ui->close->setEnabled(true);}Dialog::~Dialog()
{delete ui;
}void Dialog::on_request_clicked()
{clientSocket->connectToHost(ui->server_ip->text(),ui->server_port->text().toInt());}void Dialog::on_close_clicked()
{clientSocket->close(); // 取消已有的連接 后續觸發斷開回調ui->request->setEnabled(true);ui->close->setEnabled(false);
}
服務端
新連接請求類
通過繼承重寫的方式,實現新連接的回調操作。當然你也可以使用信號槽機制。如
connect(tcpServer, &QTcpServer::newConnection,對象,行為)。
實現功能:交給線程池處理,綁定實現連接斷開前,把公用計數器+1操作,釋放并清理資源。可以理解為綁定亡語操作,死后(連接斷開)觸發。
#ifndef TCPNEWCONNET_H
#define TCPNEWCONNET_H#include"writethread.h"
#include"dialog.h"class Dialog;class TcpNewConnet : public QTcpServer //基于重寫虛函數 實現新連接回調函數
{
Q_OBJECT
public:TcpNewConnet()=default;~TcpNewConnet()=default;TcpNewConnet(QObject *parent=0):QTcpServer(parent){dlgs =(Dialog*)parent;}
protected:// 當有新連接的時候會自動調用此函數void TcpNewConnet::incomingConnection(qintptr socketdescriptor){WriteThread *thread=new WriteThread(socketdescriptor,0);// 此處用于處理對話框顯示統計訪問次數信息connect(thread,&QThread::finished,dlgs,&Dialog::slotsdispFunc);connect(thread,&QThread::finished,thread,&QThread::deleteLater);thread->start(); // 通過執行這條語句來調用run()函數
}Dialog *dlgs;
};#endif // TCPNEWCONNET_H
多線程類
依然是通過重寫的方式,注意點是Tcpsocket的生命周期,當過了{}作用域會自動釋放這條連接。實現了客戶端連接成功,再釋放。
功能:創造一個連接,然后等這個連接死亡。觸發亡語操作。
#include "writethread.h"WriteThread::WriteThread(int socketdescriptor,QObject *parent):QThread(parent),socketdescriptor(socketdescriptor)
{}void WriteThread::run(){ //多線程執行的函數//QTcpSocket* tcp =new QTcpSocket; 持久化連接{QTcpSocket tcp2; //離開作用域自動釋放這條新連接QTcpSocket* tcp =& tcp2;if(!tcp->setSocketDescriptor(socketdescriptor)){emit myerror(tcp->error()); //觸發自定義的error信號return;}qDebug()<<"run()";QByteArray data;QDataStream out(&data,QIODevice::WriteOnly);out.setVersion(QDataStream::Qt_5_12);tcp->write(data);}//tcp->disconnectFromHost(); //主動斷開與遠程主機的TCP連接。
}
主邏輯類
主要實現按鈕開啟和關閉服務器
#include "dialog.h"
#include "ui_dialog.h"
#include<QMessageBox>
Dialog::Dialog(QWidget *parent): QDialog(parent), ui(new Ui::Dialog)
{ui->setupUi(this);setWindowTitle("連接計數器服務端");ui->server_ip->setText("127.0.0.1");ui->server_port->setText("8888");icount= 0;tcpserver=new TcpNewConnet(this);}Dialog::~Dialog()
{delete ui;
}void Dialog::slotsdispFunc(){ui->currentv->setText(tr("客戶端請求%1次").arg(++icount));
}void Dialog::on_close_clicked()
{//先關閉所有socketif(!tcpserver){tcpserver->disconnect(); //用于斷開 QTcpSocket 對象的所有信號與槽的連接。tcpserver->close(); //它會向對端發送 FIN 數據包,并等待對端的確認,完成 TCP 連接的正常關閉過程。//fin回調 已調用 tcpSocket->deleteLater(); //它不會立即刪除對象,而是將其標記為待刪除狀態,等到當前事件循環結束后再執行刪除操作。}if(tcpserver->isListening()){tcpserver->close();//不調用 deleteLater 為了下次再次開啟ui->listen->setEnabled(true);ui->close->setEnabled(false);QMessageBox::critical(this,tr("提示"),tr("多線程服務器已關閉"));}}void Dialog::on_listen_clicked()
{QString ip(ui->server_ip->text());uint16_t port =ui->server_port->text().toUInt();if(!tcpserver->listen(QHostAddress(ip),port)){tcpserver->close();QMessageBox::critical(this,tr("提示"),tr("多線程服務器已關閉"));return;}QMessageBox::information(this,tr("提示"),tr("多線程服務器已經啟動"));ui->listen->setEnabled(false);ui->close->setEnabled(true);
}
總結
通過繼承重寫和信號槽的方式,可以實現連接建立,斷開,發送前,發送后等等操作綁定,重寫需要去找指定的重寫函數,而信號去找指定的信號名。信號槽機制當綁定多個的時候,是按照綁定的順序執行,因為底層是信號隊列,保證順序。
如果對信號槽有興趣,可以看我之前發布的qt 多線程和網絡編程文章。
最后附上源代碼鏈接
對您有幫助的話,幫忙點個star
?41-clinet-count · jbjnb/Qt demo - 碼云 - 開源中國 (gitee.com)
41-server-count · jbjnb/Qt demo - 碼云 - 開源中國 (gitee.com)