用QT做一个网络调试助手
文章目录
- 前言
- 一、TCP网络调试助手介绍
- 1. 项目概述
- 2. 开发流程
- 3. TCP服务器的关键流程
- 4. TCP客户端的关键流程
- 二、实现UI界面
- 1. 服务器界面
- 2. 客户端界面
- 三、实现代码框架
- 1. 服务器代码
- 1.1 初始化服务器地址
- 1.2 开始监听
- 1.3 与客户端连接
- 1.4 接收客户端信息
- 1.5 判断客户端状态(连接或断开)
- 1.6 停止监听或断开退出
- 1.7 显示连接多个客户端
- 1.8 给多个客户端发消息
- 2. 客户端代码
- 2.1 连接服务器
- 2.2 断开服务器
- 2.3 自定义文本颜色
- 2.4 实现收发数据
前言
完整代码:网络调试助手
一、TCP网络调试助手介绍
1. 项目概述
用 QT 模拟网络调试助手的服务器和客户端,可以进行数据收发。
服务器:
客户端:
2. 开发流程
3. TCP服务器的关键流程
工程建立,需要在.pro加入网络权限:
创建一个基于 QTcpServer 的服务端涉及以下关键步骤:
- 创建并初始化 QTcpServer 实例:
- 实例化 QTcpServer 。
- 调用 listen 方法在特定端口监听传入的连接。
- 处理新连接:
- 为 newConnection 信号连接一个槽函数。
- 在槽函数中,使用 nextPendingConnection 获取 QTcpSocket 以与客户端通信。
- 读取和发送数据:
- 通过连接 QTcpSocket 的 readyRead 信号来读取来自客户端的数据。
- 使用 write 方法发送数据回客户端。
- 关闭连接:
- 在适当的时候关闭 QTcpSocket 。
4. TCP客户端的关键流程
工程建立,需要在.pro加入网络权限:
创建一个基于 QTcpSocket 的Qt客户端涉及以下步骤:
- 创建 QTcpSocket 实例:
- 实例化 QTcpSocket 。
- 连接到服务器:
- 使用 connectToHost 方法连接到服务器的IP地址和端口。
- 发送数据到服务器:
- 使用 write 方法发送数据。
- 接收来自服务器的数据:
- 为 readyRead 信号连接一个槽函数来接收数据。
- 关闭连接:
- 关闭 QTcpSocket 连接。
二、实现UI界面
1. 服务器界面
首先我们先放入两个文本编辑框 Text Edit,一个用来发送数据,一个用来接收数据,并放入一个发送按钮与下面的文本编辑框水平对齐:
接着我们再放入三个按钮分别为监听,停止监听,断开,设置水平布局:
然后再放入标签 Label,组合框 Combo Box,行编辑框 Line Edit,设置水平布局:
最后再放入一个组合框 Combo Box,整体垂直布局,修改间距,设置标题:
2. 客户端界面
首先我们先放入两个文本编辑框 Text Edit,一个用来发送数据,一个用来接收数据,并放入一个发送按钮与下面的文本编辑框水平对齐:
接着我们再放入两个按钮分别为连接和断开,并放入两个标签 Label 跟两个行编辑框 Line Edit设置水平布局:
最后再总体进行垂直布局,调整间距大小,设置标题:
三、实现代码框架
1. 服务器代码
实现服务器框架步骤:
- 初始化服务端地址
- 开始监听
- 与客户端连接
- 接收客户端信息
- 判断客户端状态(连接或断开)
- 停止监听或断开退出
- 新建MyComboBox,使用组合框,显示多个客户端连接(记得提升)
- 实现发送消息给客户端,通过索引发送多个
1.1 初始化服务器地址
查看QT提供的帮助手册:
代码示例:
#include <QTcpServer>
#include <QNetworkInterface>
public:
QTcpServer* server;
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
server = new QTcpServer(this);
// 初始化服务器地址
QList<QHostAddress> address = QNetworkInterface::allAddresses();
for (QHostAddress tmp : address)
{
if (tmp.protocol() == QAbstractSocket::IPv4Protocol)
{
ui->comboBoxIP->addItem(tmp.toString());
}
}
}
程序运行结果:
1.2 开始监听
实现步骤:
- 设置端口号
- 开始监听
代码示例:
#include <QMessageBox>
void Widget::on_btnListen_clicked()
{
// 1. 设置端口号
int port = ui->lineEditPort->text().toInt();
// 2. 开始监听
if (!server->listen(QHostAddress(ui->comboBoxIP->currentText()), port))
{
QMessageBox msgBox;
msgBox.setWindowTitle("监听失败");
msgBox.setText("端口号被占用");
msgBox.exec();
return;
}
ui->btnListen->setEnabled(false);
ui->btnStopListen->setEnabled(true);
ui->btnDisconnect->setEnabled(true);
}
程序运行结果:
1.3 与客户端连接
代码示例:
private slots:
void on_newClient_connect();
#include <QTcpSocket>
void Widget::on_newClient_connect()
{
if (server->hasPendingConnections())
{
// 从TcpSocket中获得客户端地址和端口号
QTcpSocket *connection = server->nextPendingConnection();
ui->textEditRecv->insertPlainText("客户端地址:" + connection->peerAddress().toString()
+ "客户端端口号:" + QString::number(connection->peerPort()) + "\n");
}
}
程序运行结果:
1.4 接收客户端信息
代码示例:
private slots:
void on_readyRead_handler();
void Widget::on_newClient_connect()
{
if (server->hasPendingConnections())
{
// 从TcpSocket中获得客户端地址和端口号
QTcpSocket *connection = server->nextPendingConnection();
ui->textEditRecv->insertPlainText("客户端地址:" + connection->peerAddress().toString()
+ "客户端端口号:" + QString::number(connection->peerPort()) + "\n");
// 接收客户端信息
connect(connection, SIGNAL(readyRead()),this, SLOT(on_readyRead_handler()));
}
}
void Widget::on_readyRead_handler()
{
QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());
QByteArray revData = tmpSock->readAll();
ui->textEditRecv->insertPlainText("客户端: " + revData);
}
程序运行结果:
1.5 判断客户端状态(连接或断开)
代码示例:
private slots:
void mstateChange(QAbstractSocket::SocketState);
void Widget::on_newClient_connect()
{
if (server->hasPendingConnections())
{
// 从TcpSocket中获得客户端地址和端口号
QTcpSocket *connection = server->nextPendingConnection();
ui->textEditRecv->insertPlainText("客户端地址:" + connection->peerAddress().toString()
+ "客户端端口号:" + QString::number(connection->peerPort()) + "\n");
// 接收客户端信息
connect(connection, SIGNAL(readyRead()),this, SLOT(on_readyRead_handler()));
// 判断客户端状态
connect(connection, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(mstateChange(QAbstractSocket::SocketState)));
}
}
void Widget::mstateChange(QAbstractSocket::SocketState socketState)
{
QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());
qDebug() << "client out In state:" << socketState;
switch (socketState) {
case QAbstractSocket::UnconnectedState:
ui->textEditRecv->insertPlainText("客户端断开");
tmpSock->deleteLater();
break;
case QAbstractSocket::ConnectedState:
case QAbstractSocket::ConnectingState:
ui->textEditRecv->insertPlainText("客户端接入");
break;
}
}
程序运行结果:
1.6 停止监听或断开退出
代码示例:
void Widget::on_btnStopListen_clicked()
{
// 停止监听所有客户端
QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
for (QTcpSocket* tmp : tcpSocketClients)
{
tmp->close();
}
server->close();
ui->btnListen->setEnabled(true);
ui->btnStopListen->setEnabled(false);
ui->btnDisconnect->setEnabled(false);
}
void Widget::on_btnDisconnect_clicked()
{
on_btnStopListen_clicked();
delete server;
this->close();
}
程序运行结果:
1.7 显示连接多个客户端
新建 MyComboBox 类:
新建MyComboBox,使用组合框,显示多个客户端连接(记得提升)
代码示例:
myCombobox.h
#ifndef MYCOMBOBOX_H
#define MYCOMBOBOX_H
#include <QComboBox>
#include <QWidget>
class MyComboBox : public QComboBox
{
Q_OBJECT
public:
MyComboBox(QWidget *parent);
protected:
void mousePressEvent(QMouseEvent *e) override;
signals:
void on_ComboBox_clicked();
};
#endif // MYCOMBOBOX_H
myCombobox.cpp
#include "mycombobox.h"
#include <QMouseEvent>
MyComboBox::MyComboBox(QWidget *parent) : QComboBox(parent)
{
}
void MyComboBox::mousePressEvent(QMouseEvent *e)
{
if(e->button() == Qt::LeftButton){
emit on_ComboBox_clicked();
}
QComboBox::mousePressEvent(e);
}
widget.h
private slots:
void mComboBox_refresh();
widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
server = new QTcpServer(this);
// 初始化服务器地址
QList<QHostAddress> address = QNetworkInterface::allAddresses();
for (QHostAddress tmp : address)
{
if (tmp.protocol() == QAbstractSocket::IPv4Protocol)
{
ui->comboBoxIP->addItem(tmp.toString());
}
}
// 与客户端连接
connect(server, SIGNAL(newConnection()), this, SLOT(on_newClient_connect()));
ui->btnStopListen->setEnabled(false);
ui->btnDisconnect->setEnabled(false);
ui->btnSend->setEnabled(false);
// 刷新 combox
connect(ui->comboBoxChildren,&MyComboBox::on_ComboBox_clicked,this,&Widget::mComboBox_refresh);
}
void Widget::mComboBox_refresh()
{
ui->comboBoxChildren->clear();
QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
for(QTcpSocket* tmp:tcpSocketClients){
if(tmp!=nullptr)
ui->comboBoxChildren->addItem(QString::number(tmp->peerPort()));
}
ui->comboBoxChildren->addItem("all");
}
程序运行结果:
1.8 给多个客户端发消息
代码示例:
private:
int childIndex;
void Widget::on_btnSend_clicked()
{
QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
// 当用户不选择向all,所有客户端进行发送的时候
qDebug() << childIndex;
if(ui->comboBoxChildren->currentText() != "all")
{
// 根据用户选择,找到指定客户端进行数据通信,通过childInde来查找,该变量在用户选择条目后触发的on_comboBoxChildren_activated去赋值
tcpSocketClients[childIndex]->write(ui->textEditSend->toPlainText().toStdString().c_str());
}
else
{
// 遍历所有子客户端,并一一调用write函数,向所有客户端发送
for(QTcpSocket* tmp:tcpSocketClients)
{
QByteArray sendData = ui->textEditSend->toPlainText().toLocal8Bit();
tmp->write(sendData);
}
}
}
void Widget::on_comboBoxChildren_activated(int index)
{
childIndex = index;
}
程序运行结果:
2. 客户端代码
实现客户端框架步骤:
- 设置定时器判断连接是否成功(成功,超时,错误)
- 设置断开
- 自定义文本颜色
- 实现发送和接收数据
2.1 连接服务器
- 初始化client
- 设置定时器判断是否与服务器连接
代码示例:
private slots:
void on_btnConnect_clicked();
void onConnected();
void onError(QAbstractSocket::SocketError);
void onTimeOUt();
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->btnDisconnect->setEnabled(false);
ui->btnSend->setEnabled(false);
// 初始化client
client = new QTcpSocket(this);
connect(client,SIGNAL(readyRead()),this,SLOT(mRead_Data_From_Server()));
}
void Widget::on_btnConnect_clicked()
{
client->connectToHost(ui->lineEdit_IP->text(),ui->lineEdit_Port->text().toInt());
// 设置定时器判断是否与服务器连接
timer = new QTimer(this);
timer->setSingleShot(true);
// timer->setInterval(5000);
connect(timer, SIGNAL(timeout()),this,SLOT(onTimeOUt()));
connect(client,SIGNAL(connected()),this,SLOT(onConnected()));
connect(client,SIGNAL(error(QAbstractSocket::SocketError)),
this,SLOT(onError(QAbstractSocket::SocketError)));
this->setEnabled(false);
timer->start(1000);
}
void Widget::onTimeOUt()
{
ui->textEdit_Recv->insertPlainText("连接超时");
client->abort();
this->setEnabled(true);
}
void Widget::onConnected()
{
timer->stop();
this->setEnabled(true);
ui->textEdit_Recv->append("连接成功!");
ui->btnConnect->setEnabled(false);
ui->lineEdit_Port->setEnabled(false);
ui->lineEdit_IP->setEnabled(false);
ui->btnDisconnect->setEnabled(true);
ui->btnSend->setEnabled(true);
}
void Widget::onError(QAbstractSocket::SocketError error)
{
qDebug() << "连接错误:" << error;
ui->textEdit_Recv->insertPlainText("连接出问题啦:"+client->errorString());
this->setEnabled(true);
on_btnConnect_clicked();
}
程序运行结果:
2.2 断开服务器
代码示例:
void Widget::on_btnDisconnect_clicked()
{
client->disconnectFromHost();
client->close();
ui->textEdit_Recv->append("断开连接");
ui->btnConnect->setEnabled(true);
ui->btnDisconnect->setEnabled(false);
ui->lineEdit_Port->setEnabled(true);
ui->lineEdit_IP->setEnabled(true);
ui->btnSend->setEnabled(true);
}
程序运行结果:
2.3 自定义文本颜色
代码示例:
private:
void mInserTextByColor(Qt::GlobalColor color,QString str);
void Widget::mInserTextByColor(Qt::GlobalColor color,QString str)
{
// 获取文本编辑器的光标位置,并将其存储在cursor变量中。
QTextCursor cursor = ui->textEdit_Recv->textCursor();
// 创建一个QTextCharFormat对象,用于设置文本格式。
QTextCharFormat format;
// 使用setForeground方法设置文本的前景色为传入的颜色。
format.setForeground(QBrush(QColor(color)));
// 将格式化后的字符格式应用到光标上。
cursor.setCharFormat(format);
// 使用insertText方法在光标位置插入传入的字符串str。
cursor.insertText(str);
}
2.4 实现收发数据
代码示例:
// 发送为红色字体
void Widget::on_btnSend_clicked()
{
QByteArray sendData = ui->textEdit_Send->toPlainText().toUtf8();
client->write(sendData);
// 客户端发送数据,并以红色字体插入到文本编辑器中
mInserTextByColor(Qt::red, sendData);
}
// 接收为黑色字体
void Widget::mRead_Data_From_Server()
{
// 将文本编辑器的光标移动到文本末尾
ui->textEdit_Recv->moveCursor(QTextCursor::End);
// 确保光标可见
ui->textEdit_Recv->ensureCursorVisible();
// 客户端读取所有数据,并以黑色字体插入到文本编辑器中
mInserTextByColor(Qt::black, client->readAll());
}
程序运行结果: