基于TCP/QT/C++的网络调试助手测试报告
目录
一、项目介绍
1、项目背景
2、项目功能
3、开发流程
二、UI设计
1、服务端
2、客户端
三、代码实现
1、QT中使用TCP的关键流程
2、服务器开发
3、客户端开发
四、结果测试
五、项目总结
一、项目介绍
1、项目背景
在网络应用开发和嵌入式系统调试过程中,开发者经常需要模拟 客户端-服务器 交互,以验证数据传输的正确性和稳定性。然而,传统的网络调试工具通常难以同时支持多个客户端的管理,或者缺乏灵活的调试能力。因此,本项目基于 Qt 框架实现了一款 功能强大、易于使用 的网络调试助手,旨在提高开发和测试效率。
2、项目功能
-
支持TCP连接:使用Socket网络编程,客户端和服务器的连接,是通过网络连接起来的,数据传输方式TCP,比较可靠。
-
监听指定 IP 和端口,支持多个客户端连接
-
管理多个客户端,支持客户端连接、断开及状态监控
-
收发消息,支持单个或所有客户端的消息广播
-
断开连接及停止监听,确保资源释放
本项目为网络开发人员提供了一个高效、直观的调试工具,能够大幅提升 网络通信协议的调试效率,同时也可用于 嵌入式设备调试、物联网设备通信测试 等场景。
3、开发流程
二、UI设计
1、服务端
使用的控件命名如下图所示:
2、客户端
使用的控件命名如下图所示:
在设计布局的时候,可以大体上从上到下分几块,每个小块里面先水平布局,每个小块布局好之后,在所有的小块进行垂直布局,在调整每个小块的占比,可以一点一点调整,上下左右再留一点空白,这样布局会看着比较舒服。
这样设计出来的窗口是固定大小,那么怎样可以在用户使用的时候,鼠标拉动窗口,控件可以跟着改变呢?
其实很简单,只需要一行代码就可以搞定,在Widget的构造方法中添加:
this->setLayout(ui->verticalLayout);
三、代码实现
QTcpServer
是 Qt 框架中用于实现 TCP 服务器功能的类,属于 QtNetwork
模块。它是 Qt 网络编程的重要组成部分,主要用于创建和管理 TCP 服务器端。
主要功能
-
TCP 服务端监听:使用
QTcpServer
可以轻松地创建一个 TCP 服务器,监听特定的 IP 地址和端口。 -
连接管理:当客户端尝试连接到服务器时,
QTcpServer
提供信号(如newConnection()
)通知应用程序有新的连接请求。你可以通过重写虚函数或使用信号与槽来处理这些事件。 -
数据通信:连接建立后,可以通过派生自
QTcpSocket
的套接字类来进行客户端和服务器之间的数据收发。 -
多线程支持:Qt 的网络模块默认是异步的,这意味着服务器可以在不阻塞主线程的情况下处理多个连接。
1、QT中使用TCP的关键流程
首先需要在创建项目后,在.pro文件中加入network网络权限
2、服务器开发
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QNetworkInterface>
#include<QTcpSocket>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
//初始化 UI 界面的代码,它的作用是加载 Qt Designer 设计的 UI 布局,并将界面元素绑定到 ui 指针中
ui->setupUi(this);
//布局自适应窗口
this->setLayout(ui->verticalLayout);
//实例化一个QTcpServer对象
server = new QTcpServer(this);
//绑定信号与槽
connect(ui->comboBoxChildren,&MyComboBox::on_comboBox_clicked,this,&Widget::mComboBox_refresh);
connect(server,SIGNAL(newConnection()),this,SLOT(on_newClient_connect()));
//一些细节的修改,在没有客户端连接的时候,停止监听、断开连接以及发送按钮不可用
ui->btnLineout->setEnabled(false);
ui->btnStopListen->setEnabled(false);
ui->btnSend->setEnabled(false);
//获取本机的所有网络接口地址,并将 IPv4 地址添加到 comboBoxAddr 下拉框中,供用户选择服务器监听的地址。
QList<QHostAddress> addresss = QNetworkInterface::allAddresses();
for(QHostAddress tmp :addresss){
if(tmp.protocol()==QAbstractSocket::IPv4Protocol){
ui->comboBoxAddr->addItem(tmp.toString());
}
}
}
Widget::~Widget()
{
//析构
delete ui;
}
void Widget::on_newClient_connect()
{
qDebug()<<"newClient In!";
//如果有新连接进来
if(server->hasPendingConnections()){
//获得客户端连接信息
QTcpSocket *connection = server->nextPendingConnection();
qDebug()<<"client Addr:"<< connection->peerAddress().toString() <<"port:"<<connection->peerPort();
ui->textEditRev->insertPlainText("客户端地址:"+connection->peerAddress().toString()+
"\n客户端端口号:"+QString::number(connection->peerPort())+"\n");
connect(connection,SIGNAL(readyRead()),this,SLOT(on_readyRead_handler()));
connect(connection,SIGNAL(disconnected()),this,SLOT(mdisconnected()));
connect(connection,SIGNAL(stateChange(QAbstractSocket::SocketState)),
this,SLOT(mststeChanged(QAbstractSocket::SocketState socketState)));
//有新连接进来的时候在comboBoxChildren添加新条目,并显示当前最新进入的连接
ui->comboBoxChildren->addItem(QString::number(connection->peerPort()));
ui->comboBoxChildren->setCurrentText(QString::number(connection->peerPort()));
if(!ui->btnSend->isEnabled()){
ui->btnSend->setEnabled(true);
}
}
}
void Widget::on_readyRead_handler()
{
//获取当前连接对象,sender() 返回当前触发信号的对象,但它是一个 QObject* 类型。转换为 QTcpSocket*。
QTcpSocket *tmpSock = qobject_cast<QTcpSocket*>(sender());
QByteArray revData = tmpSock->readAll();
//光标移到末尾
ui->textEditRev->moveCursor(QTextCursor::End);
ui->textEditRev->ensureCursorVisible();
//将数据显示到textEditRev
ui->textEditRev->insertPlainText("客户端:"+revData);
}
void Widget::mdisconnected()
{
//当客户端断开连接时,删除相应的 QTcpSocket 对象,并在 UI 上显示 客户端断开!
QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());
ui->textEditRev->insertPlainText("客户端断开!");
tmpSock->deleteLater();
}
void Widget::mststeChanged(QAbstractSocket::SocketState socketState)
{
int tmpIndex;
QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());
switch (socketState) {
//如果客户端断开,通过端口号查找comboBoxChildren的索引,并删除条目,最后删除相应的 QTcpSocket 对象
case QAbstractSocket::UnconnectedState:
// case QAbstractSocket::ClosingState:
ui->textEditRev->insertPlainText("客户端断开!");
tmpIndex = ui->comboBoxChildren->findText(QString::number(tmpSock->peerPort()));
ui->comboBoxChildren->removeItem(tmpIndex);
tmpSock->deleteLater();
//如果所有客户端都断开连接后,发送键不可用
if(ui->comboBoxChildren->count()==0)
ui->btnSend->setEnabled(false);
break;
case QAbstractSocket::ConnectedState:
case QAbstractSocket::ConnectingState:
ui->textEditRev->insertPlainText("客户端接入!");
break;
}
}
void Widget::mComboBox_refresh()
{
ui->comboBoxChildren->clear();
//接收所有连接的客户端,将端口号添加下拉框条目中
QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
for(QTcpSocket* tmp:tcpSocketClients){
if(tmp!=nullptr)
if(tmp->peerPort()!=0)
ui->comboBoxChildren->addItem(QString::number(tmp->peerPort()));
}
//all支持向所有客户端通信
ui->comboBoxChildren->addItem("all");
}
void Widget::on_btnListen_clicked()
{
//服务器开始监听指定的 IP 地址和端口,等待客户端连接
// QHostAddress addr("");
int port = ui->lineEditPort->text().toInt();
// if(!server->listen(QHostAddress::Any,port))
if(!server->listen(QHostAddress(ui->comboBoxAddr->currentText()),port)){
qDebug()<< "listenError";
QMessageBox msgBox;
msgBox.setWindowTitle("监听失败");
msgBox.setText("端口号被占用");
msgBox.exec();
return;
}
//监听键不可用,停止监听、断开连接可用
ui->btnListen->setEnabled(false);
ui->btnLineout->setEnabled(true);
ui->btnStopListen->setEnabled(true);
}
void Widget::on_btnSend_clicked()
{
QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
//如果当前无连接,提示发送失败
if(tcpSocketClients.isEmpty()){
QMessageBox msgBox;
msgBox.setWindowTitle("发送错误!");
msgBox.setText("当前无连接!");
msgBox.exec();
ui->btnSend->setEnabled(false);
return;
}
if(ui->comboBoxChildren->currentText() != "all"){
//根据用户选择,找到指定客户端进行通信,通过childIndex来查找,该变量在用户选择条目后触发的on_comboBoxChildren_activated去赋值
QString currentName = ui->comboBoxChildren->currentText();
for(QTcpSocket* tmp:tcpSocketClients){
if(QString::number(tmp->peerPort())==currentName){
tmp->write(ui->textEditSend->toPlainText().toStdString().c_str());
}
}
// tcpSocketClients[childIndex]->write(ui->textEditSend->toPlainText().toStdString().c_str());
}else{
//遍历所有子客户端,并一一调用write函数,向所有客户端发送消息
for(QTcpSocket* tmp:tcpSocketClients){
tmp->write(ui->textEditSend->toPlainText().toStdString().c_str());
}
}
}
void Widget::on_btnStopListen_clicked()
{
//关闭所有的客户端连接
QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket *>();
for(QTcpSocket* tmp:tcpSocketClients){
tmp->close();
}
//关闭服务器,停止监听
server->close();
ui->btnListen->setEnabled(true);
ui->btnLineout->setEnabled(false);
ui->btnStopListen->setEnabled(false);
}
void Widget::on_btnLineout_clicked()
{
//停止监听
on_btnListen_clicked();
//释放服务器对象 server,避免内存泄漏
delete server;
//关闭窗口
this->close();
}
给代码添加了详细的注释,有问题欢迎一起讨论。】
3、客户端开发
#include "widget.h"
#include "ui_widget.h"
#include <QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
//初始化ui界面
ui->setupUi(this);
//布局自适应
this->setLayout(ui->verticalLayout);
//设置按钮状态
ui->btndiscon->setEnabled(false);
ui->btnSend->setEnabled(false);
//实例化一个QTcpSocket对象
client = new QTcpSocket(this);
connect(client,SIGNAL(readyRead()),this,SLOT(mRead_Data_From_Server()));
}
Widget::~Widget()
{
//析构
delete ui;
}
void Widget::on_btnConnect_clicked()
{
//通过ip端口号创建连接
client->connectToHost(ui->lineEditIPAddr->text(),ui->lineEditPort->text().toInt());
//实例化一个计时器,五秒没连接成功给提示
timer = new QTimer(this);
timer->setInterval(5000);
connect(timer,SIGNAL(timeout()),this,SLOT(onTimeOut()));
connect(client,SIGNAL(connected()),this,SLOT(onConnected()));
connect(client,SIGNAL(QAbstractSocket::SocketError),this,SLOT(onError(QAbstractSocket::SocketError)));
//连接按钮按下的时候全窗口不可用,等成功连接时设置可用
this->setEnabled(false);
//启动定时器
timer->start();
}
void Widget::mRead_Data_From_Server()
{
//读取数据
ui->textEditRev->moveCursor(QTextCursor::End);
ui->textEditRev->ensureCursorVisible();
//服务端数据接收之后用黑色字体显示
mInsertTextByColor(Qt::black,client->readAll());
}
void Widget::on_btnSend_clicked()
{
//发送数据
QByteArray sendData = ui->textEditSend->toPlainText().toUtf8();
client->write(sendData);
//客户端发送数据之后用红色字体显示
mInsertTextByColor(Qt::red,sendData);
}
void Widget::on_btndiscon_clicked()
{
//关闭客户端
client->close();
ui->textEditRev->append("终止连接!");
//设置按钮状态
ui->btnConnect->setEnabled(true);
ui->lineEditIPAddr->setEnabled(true);
ui->lineEditPort->setEnabled(true);
ui->btndiscon->setEnabled(false);
ui->btnSend->setEnabled(false);
}
void Widget::onConnected()
{
//连接成功之后关闭计时器,设置按钮状态
timer->stop();
this->setEnabled(true);
ui->textEditRev->append("连接成功!");
ui->btnConnect->setEnabled(false);
ui->lineEditIPAddr->setEnabled(false);
ui->lineEditPort->setEnabled(false);
ui->btndiscon->setEnabled(true);
ui->btnSend->setEnabled(true);
}
void Widget::onError(QAbstractSocket::SocketError error)
{
qDebug()<<"连接错误!"<<error;
ui->textEditRev->insertPlainText("连接出问题啦:"+client->errorString());
this->setEnabled(true);
on_btndiscon_clicked();
}
void Widget::onTimeOut()
{
ui->textEditRev->insertPlainText("连接超时");
client->abort();
this->setEnabled(true);
}
void Widget::mInsertTextByColor(Qt::GlobalColor color,QString str)
{
QTextCursor cursor = ui->textEditRev->textCursor();
QTextCharFormat format;
format.setForeground(QBrush(QColor(color)));
cursor.setCharFormat(format);
cursor.insertText(str);
}
四、结果测试
连接成功
服务端客户端进行通信
服务器停止监听断开连接
客户端断开连接
五、项目总结
类别 | 功能 | API/方法 |
TcpServer | 监听 | bool listen(const QHostAddress& address, quint16 port) |
TcpServer | 连接 | QTcpSocket*nextPendingConnection() |
TcpServer | 关闭 | void close() |
TcpServer 信号 | 新连接 | newConnection() |
TcpScoket | 连接 | void connectToHost(const QString &host, quint16 port) |
TcpScoket | 发送 | qint64 write(const QByteArray& data) |
TcpScoket | 接收 | QByteArray readAll() |
TcpScoket | 断开 | void disconnectFromHost() |
TcpScoket 信号 | 连接 成功 | connected() |
TcpScoket 信号 | 断开 连接 | disconnected() |
TcpScoket 信号 | 有可读数据 | readyRead() |
TcpScoket 信号 | 成功写入数据 | bytesWritten(qint64 bytes) |
EditText | 读数据 | String getText() |
EditText | 写数据 | void setText(String text) |
EditText | 改变颜色 | void changeTextColor(int start, int end, int color) |