QT——TCP网络调试助手
一.项目概述
- 学习QTcpServer
- 学习QTcpClicent
- 学习TextEdit特定位置输入的文字颜色
- 学习网络通信相关知识点
二.开发流程
1.首先我们实现当用户选择不同协议类型时不同的UI组件如何切换
绑定信号&QComboBox::currentIndexChanged与QComBobox组件,当QComboBox组件的索引变化时,执行下面槽函数,实现不同的UI组件之间的切换
// 当 QComboBox 组件索引发生改变时的槽函数
void Widget::on_CurrentIndexChanged()
{
// 获取当前选择的索引
currentIndex = ui->comboBox_1->currentIndex();
// 删除垂直布局中的第六个组件(如果存在)
if (ui->verticalLayout->count() >= 6) {
QWidget* widget = ui->verticalLayout->itemAt(5)->widget(); // 获取第六个控件
if (widget) {
ui->verticalLayout->removeWidget(widget); // 从布局中移除
widget->deleteLater(); // 删除组件
}
}
// 判断是客户端还是服务端
if (currentIndex == 1) { // 用户选择了客户端=================================
ui->label_2->setText("(2)本地主机地址");
ui->label_3->setText("(3)远程主机地址");
// 获取本地主机地址
QString localHostName = QHostInfo::localHostName();
QHostInfo info = QHostInfo::fromName(localHostName);
QList<QHostAddress> addresses = info.addresses();
// 清空 comboBox_2 并添加 IPv4 地址
ui->comboBox_2->clear();
for (const auto& address : addresses) {
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
qDebug() << "IPv4 Address:" << address.toString();
ui->comboBox_2->addItem(address.toString());
}
}
// 创建并设置新的 QComboBox
QComboBox* box = new QComboBox(this);
box->addItem("192.168.56.1 :8080");
box->setEditable(true); // 设置为组件中的内容可修改
// 将组件添加到垂直布局中
ui->verticalLayout->addWidget(box);
box->show(); // 显示新创建的 QComboBox
} else if (currentIndex == 2) { // 用户选择了服务端=================================
ui->label_2->setText("(2)本地主机地址");
ui->label_3->setText("(3)本地主机端口");
// 创建并设置新的 QLineEdit
QLineEdit* edt = new QLineEdit(this);
edt->setText("8080");
// 将组件添加到垂直布局中
ui->verticalLayout->addWidget(edt);
edt->show(); // 显示新创建的 QLineEdit
}else{
ui->label_2->setText("(2)本地主机地址");
ui->label_3->setText("(3)本地主机端口");
// 创建并设置新的 QLineEdit
QLineEdit* edt = new QLineEdit(this);
edt->setText("8080");
// 将组件添加到垂直布局中
ui->verticalLayout->addWidget(edt);
edt->show(); // 显示新创建的 QLineEdit
}
}
2.实现打开/关闭按键图片的切换
方式一:通过其父类所提供的void setIcon(const QIcon &icon)函数去实现
#include "widget.h"
#include "ui_widget.h"
const QString ICON_PATH_MM = ":/pictures/mm.png"; // 图标路径常量
const QString ICON_PATH_PP = ":/pictures/pp.png"; // 图标路径常量
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->pushButton->setCheckable(true);
// 设置按钮透明背景
ui->pushButton->setStyleSheet("QPushButton {"
"background-color: transparent;"
"border: none;" // 可选:去掉按钮边框
"}");
// 设置图标和大小
setButtonIconAndSize(ICON_PATH_MM);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked(bool checked)
{
// 根据按钮的 checked 状态切换图标
setButtonIconAndSize(checked ? ICON_PATH_PP : ICON_PATH_MM);
}
// 封装设置图标和大小的逻辑
void Widget::setButtonIconAndSize(const QString &iconPath)
{
QSize buttonSize = ui->pushButton->size(); // 获取按钮的当前大小
ui->pushButton->setIconSize(buttonSize); // 设置图标大小
ui->pushButton->setIcon(QIcon(iconPath)); // 设置图标
}
方式二:重写QPushButton的事件
#include "mybtnopen.h"
#include <QPainter>
#include <QMouseEvent>
myBtnOpen::myBtnOpen(QWidget *parent):QPushButton(parent)
{
// 加载初始图片
pic.load(":/pictures/mm.png");
// 设置按钮的固定大小为图片的大小
//setFixedSize(pic.size());
//setFixedSize(100,100);
// 刷新界面,触发 paintEvent 进行绘制
update();
}
void myBtnOpen::paintEvent(QPaintEvent *e)
{
// 创建一个 QPainter 对象,负责绘制图片
QPainter painter(this);
// 使用 QPainter 在按钮区域内绘制当前加载的图片
painter.drawPixmap(rect(), pic);
}
void myBtnOpen::mousePressEvent(QMouseEvent *e)
{
if(e->button() == Qt::LeftButton){
//打开
if(t == true){
pic.load(":/pictures/mm.png");
update();
t = false;
}else{
pic.load(":/pictures/pp.png");
update();
t = true;
}
}
QPushButton::mousePressEvent(e);
}
3.打开/关闭按键具体实现逻辑
- 首先在构造函数中执行以下代码获取本地主机ip地址并显示到QComboBox组件中
void Widget::getLocalhostIpAddress()
{
QString localHostName = QHostInfo::localHostName(); //获取本地主机名称
QHostInfo info = QHostInfo::fromName(localHostName); //根据本地主机名称获取本地主机信息
QList<QHostAddress> addresses = info.addresses(); //将本地主机的所有ip地址赋值给容器中
// 清空 comboBox_2 并添加 IPv4 地址
ui->comboBox_2->clear();
for (const auto& address : addresses) {
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
qDebug() << "IPv4 Address:" << address.toString();
ui->comboBox_2->addItem(address.toString());
}
}
}
- 接着在构造函数中进行信号与槽的连接。
- 1.服务端有新的连接
- 2.客户端连接成功
- 3.客户端接收到新数据
- 4.客户端与服务端断开连接
server = new QTcpServer(this);
connect(server, &QTcpServer::newConnection, this, &Widget::on_newConnection); // 服务端有新连接时槽函数
connect(socket, &QTcpSocket::connected, this, [=]() { // 客户端连接到服务端槽函数
ui->btn_open->setIcon(QIcon(":/pictures/pp.png"));
ui->comboBox_1->setEnabled(false);
});
connect(socket, &QTcpSocket::readyRead, this, [=]() { // 客户端接收到数据槽函数
int sum = 0;
QByteArray data = socket->readAll(); // 读取接收到的数据
sum = data.size();
revCnt += sum;
qDebug() << "Received from server:" << data;
if (ui->checkBox_2->isChecked()) {
data.append("\r\n");
}
if (ui->checkBox_4->isChecked()) { // Hex显示是否勾选,此处为勾选
QByteArray tmpHexString = data.toHex().toUpper();
ui->textEdit->insertPlainText(tmpHexString);
} else {
ui->textEdit->insertPlainText(data);
}
ui->label_revCnt->setText("接受: " + QString::number(revCnt));
});
connect(socket, &QTcpSocket::disconnected, this, [=]() { // 客户端与服务端断开连接槽函数
qDebug() << "Disconnected from server.";
socket->deleteLater(); // 断开连接时删除 socket
});
打开/关闭按键槽函数分为三大部分(服务端、客户端、UDP)
//打开按键槽函数
void Widget::on_btn_open_clicked(bool checked)
{
if(checked){
//获取本地主机ip地址
QString localIpAddress = ui->comboBox_2->currentText();
QHostAddress ipaddress = (QHostAddress)localIpAddress;
if(ui->comboBox_1->currentIndex() == 2){ //当前所选的是服务端===================================================
QString honts = edt->text();
qDebug() << "localIpAddress: " << localIpAddress;
qDebug() << "honts: " << honts;
quint16 port = honts.toUInt();
server->listen(ipaddress, port); //开始监听
ui->btn_open->setIcon(QIcon(":/pictures/pp.png"));
ui->comboBox_1->setEnabled(false);
t = 2;
}else if(ui->comboBox_1->currentIndex() == 1){ //当前所选的是客户端===================================================
QHostAddress remIpaddress; //远程主机ip地址
quint16 remPort; //远程主机端口号
//获取用户输入的远程主机ip地址与端口号
QString data = box->currentText();
// 使用 ':' 字符分割字符串
QStringList parts = data.split(":");
if (parts.size() == 2) {
QString ipAddress = parts[0];
QString port = parts[1];
remIpaddress = (QHostAddress)ipAddress; //远程主机ip地址
remPort = port.toUInt(); //远程主机端口号
} else {
qDebug() << "Invalid address format!";
}
socket = new QTcpSocket;
socket->connectToHost(remIpaddress, remPort); //尝试连接服务端
t = 1;
}else{ //当前所选的是UDP===================================================
QString honts = edt->text();
qDebug() << "localIpAddress: " << localIpAddress;
qDebug() << "honts: " << honts;
t = 0;
}
}else{
ui->btn_open->setIcon(QIcon(":/pictures/mm.png")); // 设置按钮图标
ui->comboBox_1->setEnabled(true); // 启用下拉框
if (t == 1) { // 断开客户端连接
if (socket) {
socket->disconnectFromHost(); // 断开与服务器的连接
socket->waitForDisconnected(); // 等待断开完成
socket->deleteLater(); // 删除 socket
qDebug() << "Client socket disconnected.";
}
} else if (t == 2) { // 断开服务端连接
if (server->isListening()) {
server->close(); // 关闭服务器
qDebug() << "Server stopped listening.";
}
}
}
}
服务端有新的连接的槽函数如下
void Widget::on_newConnection()
{
// 获取新连接
clientSocket = server->nextPendingConnection();
if (clientSocket) {
qDebug() << "New connection from:" << clientSocket->peerAddress().toString() << ":" << clientSocket->peerPort();
// 连接信号和槽
connect(clientSocket, &QTcpSocket::readyRead, this, [=]() {
int sum = 0;
QByteArray data = clientSocket->readAll();
qDebug() << "Received data:" << data;
sum = data.size();
revCnt += sum;
ui->label_revCnt->setText("接受: "+ QString::number(revCnt));
if(ui->checkBox_4->isChecked()){ //Hex显示是否勾选,此处为勾选
QByteArray tmpHexString = data.toHex().toUpper();
if(ui->checkBox_2->isChecked()) tmpHexString.append("\r\n");
ui->textEdit->append(tmpHexString);
}else{
if(ui->checkBox_2->isChecked()) data.append("\r\n");
ui->textEdit->append(data);
}
});
connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
qDebug() << "Client disconnected:" << clientSocket->peerAddress().toString() << ":" << clientSocket->peerPort();
clientSocket->deleteLater(); // 断开连接时删除 socket
});
} else {
qDebug() << "Failed to accept new connection.";
}
}
4.发送数据具体实现逻辑
4.1首先实现正常发送
//发送按键槽函数
void Widget::on_pushButton_send_clicked()
{
//(1)先获取用户输入的要发送的内容
QString data = ui->textEdit_sendData->toPlainText();
QByteArray arrayData = data.toLocal8Bit();
int cnt = 0;
//(2)判断Hex发送是否勾选,此处为勾选
if (ui->checkBox_9->isChecked()) {
// a.检查字节数是否是偶数
if (0 != arrayData.size() % 2) {
ui->label_state->setText("input error!");
return;
}
// b.检查是否符合Hex表达
for (char c : arrayData) {
if (!isxdigit(c)) {
ui->label_state->setText("input error!");
return;
}
}
// c.转化为16进制
arrayData = QByteArray::fromHex(arrayData);
}
//(3).开始发送
if (t == 1) { // 客户端发送数据
cnt = socket->write(arrayData);
} else if (t == 2) { // 服务端发送数据
cnt = clientSocket->write(arrayData);
}
//(4)判断是否发送成功
if (cnt == -1) {
ui->label_state->setText("send error!");
} else {
if (ui->checkBox_8->isChecked()) {
ui->textEdit_sendData->clear();
}
sendCnt += cnt;
ui->label_state->setText("send OK!");
ui->label_sendCnt->setText("发送: " + QString::number(sendCnt));
}
}
4.2定时发送
通过定时器实现,当用户勾选定时发送组件时,初始化定时器,设置定时器定时时间,启动定时器。在构造函数中绑定定时器的超时信号与槽函数,通过Lambda表达式调用发送函数既可以
//定时发送槽函数
void Widget::on_checkBox_10_clicked(bool checked)
{
if(checked){
//设置定时器定时时间
timer->setInterval(ui->lineEdit_msData->text().toInt());
//启动定时器
timer->start();
}else{
//停止定时器
timer->stop();
}
}
//发送按键槽函数
void Widget::on_pushButton_send_clicked()
{
//(1)先获取用户输入的要发送的内容
QString data = ui->textEdit_sendData->toPlainText();
QByteArray arrayData = data.toLocal8Bit();
int cnt = 0;
//(2)判断Hex发送是否勾选,此处为勾选
if (ui->checkBox_9->isChecked()) {
// a.检查字节数是否是偶数
if (0 != arrayData.size() % 2) {
ui->label_state->setText("input error!");
return;
}
// b.检查是否符合Hex表达
for (char c : arrayData) {
if (!isxdigit(c)) {
ui->label_state->setText("input error!");
return;
}
}
// c.转化为16进制
arrayData = QByteArray::fromHex(arrayData);
}
//(3).开始发送
if (t == 1) { // 客户端发送数据
cnt = socket->write(arrayData);
} else if (t == 2) { // 服务端发送数据
cnt = clientSocket->write(arrayData);
}
//(4)判断是否发送成功
if (cnt == -1) {
ui->label_state->setText("send error!");
} else {
if (ui->checkBox_8->isChecked()) {
ui->textEdit_sendData->clear();
}
sendCnt += cnt;
ui->label_state->setText("send OK!");
ui->label_sendCnt->setText("发送: " + QString::number(sendCnt));
}
}