文章的目的为了记录使用C++ 进行QT Widget 开发学习的经历。临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C++ QT Widget 开发(一)工程文件结构-CSDN博客
开源 C++ QT Widget 开发(二)基本控件应用-CSDN博客
开源 C++ QT Widget 开发(三)图表--波形显示器-CSDN博客
开源 C++ QT Widget 开发(四)文件--二进制文件查看编辑-CSDN博客
开源 C++ QT Widget 开发(五)通讯--串口调试-CSDN博客
开源 C++ QT Widget 开发(六)通讯--TCP调试-CSDN博客
推荐链接:
开源 java android app 开发(一)开发环境的搭建-CSDN博客
开源 java android app 开发(二)工程文件结构-CSDN博客
开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客
开源 java android app 开发(四)GUI界面重要组件-CSDN博客
开源 java android app 开发(五)文件和数据库存储-CSDN博客
开源 java android app 开发(六)多媒体使用-CSDN博客
开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客
开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客
开源 java android app 开发(九)后台之线程和服务-CSDN博客
开源 java android app 开发(十)广播机制-CSDN博客
开源 java android app 开发(十一)调试、发布-CSDN博客
开源 java android app 开发(十二)封库.aar-CSDN博客
推荐链接:
开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客
开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客
开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-CSDN博客
开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-CSDN博客
开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客
本章主要内容:Tcp通讯调试工具,ip,端口等参数可设置。可以设为服务器端和客户端,同时数据可以发送文本或16进制。
一、源码分析
1.1 界面设计 (UI)
主窗口布局:采用垂直布局,包含TCP配置区、接收数据区和发送数据区
控件组织:
TCP配置区:模式选择、地址选择、端口输入、连接按钮、状态显示
接收区:文本显示框、16进制显示选项、清空按钮
发送区:文本输入框、16进制发送选项、清空和发送按钮

1.2 核心功能
网络模式支持:
Server模式:作为TCP服务器监听连接,支持多客户端连接
Client模式:作为TCP客户端连接远程服务器

1.3 数据处理:
16进制与文本模式:支持16进制和文本两种数据格式的发送和显示
实时数据收发:能够实时接收和显示网络数据

二、所有源码
2.1 mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QComboBox>
#include <QTextEdit>
#include <QCheckBox>
#include <QPushButton>
#include <QLabel>
#include <QGroupBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QScrollBar>
#include <QLineEdit>
#include <QHostInfo>
#include <QNetworkInterface>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onConnectButtonClicked();
void onSendButtonClicked();
void onClearReceiveButtonClicked();
void onClearSendButtonClicked();
void onNewConnection();
void onClientConnected();
void onClientDisconnected();
void onReadyRead();
void onTcpModeChanged(int index);
private:
void createUI();
void initNetwork();
void refreshLocalAddresses();
void connectSignals();
void disconnectSignals();
void updateConnectionStatus();
QString getClientInfo(QTcpSocket *client) const;
// TCP模式选择
QComboBox *tcpModeComboBox;
QComboBox *localAddressComboBox;
QLineEdit *portEdit;
QPushButton *connectButton;
QLabel *statusLabel;
// 客户端连接信息
QLabel *clientsLabel;
// 接收区域控件
QTextEdit *receiveEdit;
QCheckBox *hexDisplayCheckBox;
QPushButton *clearReceiveButton;
// 发送区域控件
QTextEdit *sendEdit;
QCheckBox *hexSendCheckBox;
QPushButton *clearSendButton;
QPushButton *sendButton;
// 网络对象
QTcpServer *tcpServer;
QList<QTcpSocket*> clientSockets;
QTcpSocket *tcpClient;
// 布局和容器
QWidget *centralWidget;
QVBoxLayout *mainLayout;
};
#endif // MAINWINDOW_H
2.2 mainwindow.cpp文件
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, tcpServer(nullptr)
, tcpClient(nullptr)
{
// 设置窗口大小和标题
this->resize(1000, 800);
this->setWindowTitle("QT TCP调试助手");
// 创建界面
createUI();
// 初始化网络
initNetwork();
// 刷新本地地址
refreshLocalAddresses();
// 连接信号槽
connectSignals();
}
MainWindow::~MainWindow()
{
// 关闭服务器
if (tcpServer && tcpServer->isListening()) {
tcpServer->close();
}
// 关闭所有客户端连接
for (QTcpSocket *client : clientSockets) {
if (client && client->state() == QAbstractSocket::ConnectedState) {
client->disconnectFromHost();
if (client->state() != QAbstractSocket::UnconnectedState) {
client->waitForDisconnected(1000);
}
}
delete client;
}
clientSockets.clear();
// 关闭客户端连接
if (tcpClient && tcpClient->state() == QAbstractSocket::ConnectedState) {
tcpClient->disconnectFromHost();
if (tcpClient->state() != QAbstractSocket::UnconnectedState) {
tcpClient->waitForDisconnected(1000);
}
}
delete tcpClient;
delete tcpServer;
}
void MainWindow::createUI()
{
// 创建中央部件
centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
// 主布局
mainLayout = new QVBoxLayout(centralWidget);
mainLayout->setSpacing(10);
mainLayout->setContentsMargins(10, 10, 10, 10);
// 创建TCP配置区域
QGroupBox *configGroup = new QGroupBox("TCP配置", centralWidget);
QHBoxLayout *configLayout = new QHBoxLayout(configGroup);
configLayout->setSpacing(5);
// TCP模式选择
QLabel *modeLabel = new QLabel("TCP模式:", configGroup);
tcpModeComboBox = new QComboBox(configGroup);
tcpModeComboBox->setMinimumWidth(100);
tcpModeComboBox->addItems(QStringList() << "Server" << "Client");
configLayout->addWidget(modeLabel);
configLayout->addWidget(tcpModeComboBox);
// 本地地址选择
QLabel *addressLabel = new QLabel("本地地址:", configGroup);
localAddressComboBox = new QComboBox(configGroup);
localAddressComboBox->setMinimumWidth(150);
configLayout->addWidget(addressLabel);
configLayout->addWidget(localAddressComboBox);
// 端口输入
QLabel *portLabel = new QLabel("端口:", configGroup);
portEdit = new QLineEdit(configGroup);
portEdit->setMinimumWidth(80);
portEdit->setText("8080");
portEdit->setValidator(new QIntValidator(1, 65535, this));
configLayout->addWidget(portLabel);
configLayout->addWidget(portEdit);
// 连接/监听按钮
connectButton = new QPushButton("启动监听", configGroup);
connectButton->setFixedWidth(100);
configLayout->addWidget(connectButton);
// 状态标签
statusLabel = new QLabel("未连接", configGroup);
configLayout->addWidget(statusLabel);
// 客户端连接信息标签
clientsLabel = new QLabel("客户端: 0", configGroup);
configLayout->addWidget(clientsLabel);
configLayout->addStretch();
// 创建接收区域
QGroupBox *receiveGroup = new QGroupBox("接收数据", centralWidget);
QVBoxLayout *receiveLayout = new QVBoxLayout(receiveGroup);
// 接收文本框
receiveEdit = new QTextEdit(receiveGroup);
receiveEdit->setReadOnly(true);
receiveEdit->setMinimumHeight(300);
receiveLayout->addWidget(receiveEdit);
// 接收选项
QHBoxLayout *receiveOptionsLayout = new QHBoxLayout();
hexDisplayCheckBox = new QCheckBox("16进制显示", receiveGroup);
clearReceiveButton = new QPushButton("清空接收", receiveGroup);
clearReceiveButton->setFixedWidth(80);
receiveOptionsLayout->addWidget(hexDisplayCheckBox);
receiveOptionsLayout->addWidget(clearReceiveButton);
receiveOptionsLayout->addStretch();
receiveLayout->addLayout(receiveOptionsLayout);
// 创建发送区域
QGroupBox *sendGroup = new QGroupBox("发送数据", centralWidget);
QVBoxLayout *sendLayout = new QVBoxLayout(sendGroup);
// 发送文本框
sendEdit = new QTextEdit(sendGroup);
sendEdit->setMaximumHeight(150);
sendLayout->addWidget(sendEdit);
// 发送选项
QHBoxLayout *sendOptionsLayout = new QHBoxLayout();
hexSendCheckBox = new QCheckBox("16进制发送", sendGroup);
clearSendButton = new QPushButton("清空发送", sendGroup);
clearSendButton->setFixedWidth(80);
sendButton = new QPushButton("发送", sendGroup);
sendButton->setFixedWidth(80);
sendOptionsLayout->addWidget(hexSendCheckBox);
sendOptionsLayout->addWidget(clearSendButton);
sendOptionsLayout->addWidget(sendButton);
sendOptionsLayout->addStretch();
sendLayout->addLayout(sendOptionsLayout);
// 将各个区域添加到主布局
mainLayout->addWidget(configGroup);
mainLayout->addWidget(receiveGroup);
mainLayout->addWidget(sendGroup);
// 设置布局比例
mainLayout->setStretch(0, 1); // 配置区域
mainLayout->setStretch(1, 4); // 接收区域
mainLayout->setStretch(2, 2); // 发送区域
}
void MainWindow::initNetwork()
{
tcpServer = new QTcpServer(this);
tcpClient = new QTcpSocket(this);
}
void MainWindow::refreshLocalAddresses()
{
localAddressComboBox->clear();
// 添加本地地址选项
localAddressComboBox->addItem("127.0.0.1");
localAddressComboBox->addItem("0.0.0.0");
// 获取本机所有IP地址
QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
for (const QHostAddress &address : addresses) {
if (address.protocol() == QAbstractSocket::IPv4Protocol &&
address != QHostAddress::LocalHost) {
localAddressComboBox->addItem(address.toString());
}
}
localAddressComboBox->setCurrentIndex(0);
}
void MainWindow::connectSignals()
{
connect(connectButton, &QPushButton::clicked, this, &MainWindow::onConnectButtonClicked);
connect(sendButton, &QPushButton::clicked, this, &MainWindow::onSendButtonClicked);
connect(clearReceiveButton, &QPushButton::clicked, this, &MainWindow::onClearReceiveButtonClicked);
connect(clearSendButton, &QPushButton::clicked, this, &MainWindow::onClearSendButtonClicked);
connect(tcpModeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MainWindow::onTcpModeChanged);
connect(tcpServer, &QTcpServer::newConnection, this, &MainWindow::onNewConnection);
connect(tcpClient, &QTcpSocket::connected, this, &MainWindow::onClientConnected);
connect(tcpClient, &QTcpSocket::disconnected, this, &MainWindow::onClientDisconnected);
connect(tcpClient, &QTcpSocket::readyRead, this, &MainWindow::onReadyRead);
}
void MainWindow::disconnectSignals()
{
disconnect(tcpServer, &QTcpServer::newConnection, this, &MainWindow::onNewConnection);
disconnect(tcpClient, &QTcpSocket::connected, this, &MainWindow::onClientConnected);
disconnect(tcpClient, &QTcpSocket::disconnected, this, &MainWindow::onClientDisconnected);
disconnect(tcpClient, &QTcpSocket::readyRead, this, &MainWindow::onReadyRead);
for (QTcpSocket *client : clientSockets) {
disconnect(client, &QTcpSocket::readyRead, this, &MainWindow::onReadyRead);
disconnect(client, &QTcpSocket::disconnected, this, &MainWindow::onClientDisconnected);
}
}
void MainWindow::onTcpModeChanged(int index)
{
if (index == 0) { // Server模式
connectButton->setText("启动监听");
localAddressComboBox->setEnabled(true);
} else { // Client模式
connectButton->setText("连接服务器");
localAddressComboBox->setEnabled(true);
}
// 断开当前连接
if (tcpServer->isListening()) {
tcpServer->close();
}
if (tcpClient->state() == QAbstractSocket::ConnectedState) {
tcpClient->disconnectFromHost();
}
statusLabel->setText("未连接");
clientsLabel->setText("客户端: 0");
}
void MainWindow::onConnectButtonClicked()
{
QString portText = portEdit->text().trimmed();
if (portText.isEmpty()) {
QMessageBox::warning(this, "警告", "请输入端口号");
return;
}
quint16 port = portText.toUShort();
if (port == 0) {
QMessageBox::warning(this, "警告", "端口号无效");
return;
}
if (tcpModeComboBox->currentIndex() == 0) { // Server模式
if (tcpServer->isListening()) {
// 停止监听
tcpServer->close();
for (QTcpSocket *client : clientSockets) {
client->disconnectFromHost();
delete client;
}
clientSockets.clear();
connectButton->setText("启动监听");
statusLabel->setText("已停止监听");
clientsLabel->setText("客户端: 0");
} else {
// 启动监听
QHostAddress address(localAddressComboBox->currentText());
if (tcpServer->listen(address, port)) {
connectButton->setText("停止监听");
statusLabel->setText(QString("监听中: %1:%2").arg(address.toString()).arg(port));
receiveEdit->append(QString("[系统] 开始监听 %1:%2").arg(address.toString()).arg(port));
} else {
QMessageBox::critical(this, "错误", "监听失败: " + tcpServer->errorString());
}
}
} else { // Client模式
if (tcpClient->state() == QAbstractSocket::ConnectedState) {
// 断开连接
tcpClient->disconnectFromHost();
connectButton->setText("连接服务器");
statusLabel->setText("已断开连接");
} else {
// 连接服务器
QHostAddress serverAddress(localAddressComboBox->currentText());
tcpClient->connectToHost(serverAddress, port);
if (tcpClient->waitForConnected(3000)) {
connectButton->setText("断开连接");
statusLabel->setText(QString("已连接到: %1:%2").arg(serverAddress.toString()).arg(port));
receiveEdit->append(QString("[系统] 连接到服务器 %1:%2").arg(serverAddress.toString()).arg(port));
} else {
QMessageBox::critical(this, "错误", "连接失败: " + tcpClient->errorString());
}
}
}
}
void MainWindow::onSendButtonClicked()
{
QString sendText = sendEdit->toPlainText();
if (sendText.isEmpty()) {
QMessageBox::warning(this, "警告", "发送内容不能为空");
return;
}
QByteArray sendData;
if (hexSendCheckBox->isChecked()) {
// 16进制发送
sendText = sendText.trimmed();
sendText.replace(" ", "");
sendText.replace("\n", "");
sendText.replace("\t", "");
sendText.replace("\r", "");
if (sendText.isEmpty()) {
QMessageBox::warning(this, "警告", "16进制数据不能为空");
return;
}
if (sendText.length() % 2 != 0) {
QMessageBox::warning(this, "警告", "16进制数据长度必须为偶数");
return;
}
bool ok;
for (int i = 0; i < sendText.length(); i += 2) {
QString byteStr = sendText.mid(i, 2);
uint8_t byte = byteStr.toUShort(&ok, 16);
if (!ok) {
QMessageBox::warning(this, "警告", "包含非法的16进制字符: " + byteStr);
return;
}
sendData.append(byte);
}
} else {
// 文本发送
sendData = sendText.toUtf8();
}
// 发送数据
if (tcpModeComboBox->currentIndex() == 0) { // Server模式
// 向所有客户端发送数据
for (QTcpSocket *client : clientSockets) {
if (client->state() == QAbstractSocket::ConnectedState) {
client->write(sendData);
}
}
if (clientSockets.isEmpty()) {
QMessageBox::warning(this, "警告", "没有客户端连接");
}
} else { // Client模式
if (tcpClient->state() == QAbstractSocket::ConnectedState) {
tcpClient->write(sendData);
} else {
QMessageBox::warning(this, "警告", "未连接到服务器");
}
}
}
void MainWindow::onNewConnection()
{
while (tcpServer->hasPendingConnections()) {
QTcpSocket *client = tcpServer->nextPendingConnection();
clientSockets.append(client);
connect(client, &QTcpSocket::readyRead, this, &MainWindow::onReadyRead);
connect(client, &QTcpSocket::disconnected, this, [this, client]() {
receiveEdit->append(QString("[系统] 客户端断开: %1").arg(getClientInfo(client)));
clientSockets.removeOne(client);
client->deleteLater();
clientsLabel->setText(QString("客户端: %1").arg(clientSockets.size()));
});
receiveEdit->append(QString("[系统] 新客户端连接: %1").arg(getClientInfo(client)));
clientsLabel->setText(QString("客户端: %1").arg(clientSockets.size()));
}
}
void MainWindow::onClientConnected()
{
receiveEdit->append("[系统] 已连接到服务器");
}
void MainWindow::onClientDisconnected()
{
receiveEdit->append("[系统] 与服务器断开连接");
connectButton->setText("连接服务器");
statusLabel->setText("连接已断开");
}
void MainWindow::onReadyRead()
{
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
QByteArray data = socket->readAll();
if (data.isEmpty()) return;
QString prefix;
if (tcpModeComboBox->currentIndex() == 0) { // Server模式
prefix = QString("[客户端 %1] ").arg(getClientInfo(socket));
} else { // Client模式
prefix = "[服务器] ";
}
if (hexDisplayCheckBox->isChecked()) {
// 16进制显示
QString hexData;
for (int i = 0; i < data.size(); i++) {
hexData += QString("%1 ").arg((uint8_t)data[i], 2, 16, QLatin1Char('0')).toUpper();
}
receiveEdit->append(prefix + hexData.trimmed());
} else {
// 文本显示
QString text = QString::fromUtf8(data);
receiveEdit->append(prefix + text);
}
// 自动滚动到底部
QScrollBar *scrollbar = receiveEdit->verticalScrollBar();
scrollbar->setValue(scrollbar->maximum());
}
void MainWindow::onClearReceiveButtonClicked()
{
receiveEdit->clear();
}
void MainWindow::onClearSendButtonClicked()
{
sendEdit->clear();
}
QString MainWindow::getClientInfo(QTcpSocket *client) const
{
if (!client) return "未知";
return QString("%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
}
void MainWindow::updateConnectionStatus()
{
if (tcpModeComboBox->currentIndex() == 0) { // Server模式
if (tcpServer->isListening()) {
statusLabel->setText("监听中");
} else {
statusLabel->setText("未监听");
}
} else { // Client模式
if (tcpClient->state() == QAbstractSocket::ConnectedState) {
statusLabel->setText("已连接");
} else {
statusLabel->setText("未连接");
}
}
}
2.3 .pro工程文件
QT += core gui network widgets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
三、显示效果