利用Qt强大的网络模块(如QTcpSocket, QUdpSocket, QNetworkAccessManager等),可以开发出帮助开发者测试网络服务的工具,支持TCP/UDP/HTTP协议,包含发送自定义请求、接收响应、历史记录等功能。
1. 项目结构
- NetworkDebugger
- core/ # 核心网络模块
- networkmanager.h
- networkmanager.cpp
- tcpclient.h
- tcpclient.cpp
- udpclient.h
- udpclient.cpp
- httpclient.h
- httpclient.cpp
- utils/ # 工具模块
- dataconverter.h
- dataconverter.cpp
- settingsmanager.h
- settingsmanager.cpp
- logger.h
- logger.cpp
- widgets/ # 自定义控件
- hextextedit.h
- hextextedit.cpp
- mainwindow.h
- mainwindow.cpp
- main.cpp
- networkdebugger.pro
2. 核心网络模块
实现为所有网络客户端提供了统一的接口和实用的工具方法,使得不同协议的客户端可以共享相同的基类功能,同时保持各自的特定实现。
2.1 网络管理器接口 (core/networkmanager.h)
#ifndef NETWORKMANAGER_H
#define NETWORKMANAGER_H
#include <QObject>
#include <QByteArray>
#include <QHostAddress>
#include <QAbstractSocket>
#include <QHostInfo>
#include <QDateTime>
class NetworkManager : public QObject
{
Q_OBJECT
public:
enum Protocol {
TCP,
UDP,
HTTP
};
Q_ENUM(Protocol)
explicit NetworkManager(QObject *parent = nullptr);
virtual ~NetworkManager();
virtual void connectToHost(const QString &host, quint16 port) = 0;
virtual void disconnectFromHost() = 0;
virtual void sendData(const QByteArray &data) = 0;
virtual bool isConnected() const = 0;
// 通用工具方法
static QString formatNetworkError(QAbstractSocket::SocketError error);
static QString formatNetworkState(QAbstractSocket::SocketState state);
static QString protocolToString(Protocol protocol);
static Protocol stringToProtocol(const QString &protocolStr);
static bool validateHostAddress(const QString &host);
static bool validatePort(quint16 port);
static QString formatByteSize(qint64 bytes);
static QString formatDataRate(qint64 bytes, qint64 msecs);
static QString currentTimestamp();
signals:
void connected();
void disconnected();
void dataReceived(const QByteArray &data, const QString &from);
void errorOccurred(const QString &errorString);
void bytesWritten(qint64 bytes);
void stateChanged(const QString &state);
};
#endif // NETWORKMANAGER_H
#include "networkmanager.h"
NetworkManager::NetworkManager(QObject *parent)
: QObject(parent)
{
// 基类构造函数,所有派生类共享的初始化可以放在这里
}
NetworkManager::~NetworkManager()
{
// 基类析构函数
}
// 以下是一些可以在派生类中使用的通用工具方法
QString NetworkManager::formatNetworkError(QAbstractSocket::SocketError error)
{
switch(error) {
case QAbstractSocket::ConnectionRefusedError:
return tr("连接被拒绝");
case QAbstractSocket::RemoteHostClosedError:
return tr("远程主机关闭了连接");
case QAbstractSocket::HostNotFoundError:
return tr("找不到主机");
case QAbstractSocket::SocketAccessError:
return tr("套接字访问错误");
case QAbstractSocket::SocketResourceError:
return tr("套接字资源错误");
case QAbstractSocket::SocketTimeoutError:
return tr("套接字操作超时");
case QAbstractSocket::DatagramTooLargeError:
return tr("数据报太大");
case QAbstractSocket::NetworkError:
return tr("网络错误");
case QAbstractSocket::AddressInUseError:
return tr("地址已被使用");
case QAbstractSocket::SocketAddressNotAvailableError:
return tr("套接字地址不可用");
case QAbstractSocket::UnsupportedSocketOperationError:
return tr("不支持的套接字操作");
case QAbstractSocket::UnfinishedSocketOperationError:
return tr("未完成的套接字操作");
case QAbstractSocket::ProxyAuthenticationRequiredError:
return tr("需要代理认证");
case QAbstractSocket::SslHandshakeFailedError:
return tr("SSL握手失败");
case QAbstractSocket::ProxyConnectionRefusedError:
return tr("代理连接被拒绝");
case QAbstractSocket::ProxyConnectionClosedError:
return tr("代理连接关闭");
case QAbstractSocket::ProxyConnectionTimeoutError:
return tr("代理连接超时");
case QAbstractSocket::ProxyNotFoundError:
return tr("找不到代理");
case QAbstractSocket::ProxyProtocolError:
return tr("代理协议错误");
case QAbstractSocket::OperationError:
return tr("操作错误");
case QAbstractSocket::SslInternalError:
return tr("SSL内部错误");
case QAbstractSocket::SslInvalidUserDataError:
return tr("SSL无效用户数据");
case QAbstractSocket::TemporaryError:
return tr("临时错误");
default:
return tr("未知网络错误");
}
}
QString NetworkManager::formatNetworkState(QAbstractSocket::SocketState state)
{
switch(state) {
case QAbstractSocket::UnconnectedState:
return tr("未连接");
case QAbstractSocket::HostLookupState:
return tr("正在查找主机");
case QAbstractSocket::ConnectingState:
return tr("正在连接");
case QAbstractSocket::ConnectedState:
return tr("已连接");
case QAbstractSocket::BoundState:
return tr("已绑定");
case QAbstractSocket::ListeningState:
return tr("正在监听");
case QAbstractSocket::ClosingState:
return tr("正在关闭");
default:
return tr("未知状态");
}
}
QString NetworkManager::protocolToString(NetworkManager::Protocol protocol)
{
switch(protocol) {
case TCP: return "TCP";
case UDP: return "UDP";
case HTTP: return "HTTP";
default: return "Unknown";
}
}
NetworkManager::Protocol NetworkManager::stringToProtocol(const QString &protocolStr)
{
if(protocolStr.compare("TCP", Qt::CaseInsensitive) == 0) {
return TCP;
} else if(protocolStr.compare("UDP", Qt::CaseInsensitive) == 0) {
return UDP;
} else if(protocolStr.compare("HTTP", Qt::CaseInsensitive) == 0) {
return HTTP;
}
return TCP; // 默认返回TCP
}
bool NetworkManager::validateHostAddress(const QString &host)
{
QHostAddress address(host);
return !address.isNull() ||
!QHostInfo::fromName(host).addresses().isEmpty();
}
bool NetworkManager::validatePort(quint16 port)
{
return port > 0 && port <= 65535;
}
QString NetworkManager::formatByteSize(qint64 bytes)
{
if(bytes < 1024) {
return QString("%1 B").arg(bytes);
} else if(bytes < 1024 * 1024) {
return QString("%1 KB").arg(bytes / 1024.0, 0, 'f', 2);
} else if(bytes < 1024 * 1024 * 1024) {
return QString("%1 MB").arg(bytes / (1024.0 * 1024.0), 0, 'f', 2);
} else {
return QString("%1 GB").arg(bytes / (1024.0 * 1024.0 * 1024.0), 0, 'f', 2);
}
}
QString NetworkManager::formatDataRate(qint64 bytes, qint64 msecs)
{
if(msecs <= 0) return "0 B/s";
double rate = bytes * 1000.0 / msecs; // B/s
if(rate < 1024) {
return QString("%1 B/s").arg(rate, 0, 'f', 2);
} else if(rate < 1024 * 1024) {
return QString("%1 KB/s").arg(rate / 1024.0, 0, 'f', 2);
} else if(rate < 1024 * 1024 * 1024) {
return QString("%1 MB/s").arg(rate / (1024.0 * 1024.0), 0, 'f', 2);
} else {
return QString("%1 GB/s").arg(rate / (1024.0 * 1024.0 * 1024.0), 0, 'f', 2);
}
}
QString NetworkManager::currentTimestamp()
{
return QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
}
2.2 TCP客户端实现 (core/tcpclient.h)
#ifndef TCPCLIENT_H
#define TCPCLIENT_H
#include "networkmanager.h"
#include <QTcpSocket>
class TcpClient : public NetworkManager
{
Q_OBJECT
public:
explicit TcpClient(QObject *parent = nullptr);
~TcpClient();
void connectToHost(const QString &host, quint16 port) override;
void disconnectFromHost() override;
void sendData(const QByteArray &data) override;
bool isConnected() const override;
private slots:
void onConnected();
void onDisconnected();
void onReadyRead();
void onErrorOccurred(QAbstractSocket::SocketError error);
private:
QTcpSocket *m_socket;
};
#endif // TCPCLIENT_H
#include "tcpclient.h"
TcpClient::TcpClient(QObject *parent)
: NetworkManager(parent), m_socket(new QTcpSocket(this))
{
connect(m_socket, &QTcpSocket::connected, this, &TcpClient::onConnected);
connect(m_socket, &QTcpSocket::disconnected, this, &TcpClient::onDisconnected);
connect(m_socket, &QTcpSocket::readyRead, this, &TcpClient::onReadyRead);
connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred),
this, &TcpClient::onErrorOccurred);
}
TcpClient::~TcpClient()
{
disconnectFromHost();
}
void TcpClient::connectToHost(const QString &host, quint16 port)
{
m_socket->connectToHost(host, port);
}
void TcpClient::disconnectFromHost()
{
if(m_socket->state() == QAbstractSocket::ConnectedState) {
m_socket->disconnectFromHost();
}
}
void TcpClient::sendData(const QByteArray &data)
{
if(m_socket->state() == QAbstractSocket::ConnectedState) {
m_socket->write(data);
}
}
bool TcpClient::isConnected() const
{
return m_socket->state() == QAbstractSocket::ConnectedState;
}
void TcpClient::onConnected()
{
emit connected();
}
void TcpClient::onDisconnected()
{
emit disconnected();
}
void TcpClient::onReadyRead()
{
QByteArray data = m_socket->readAll();
emit dataReceived(data, m_socket->peerAddress().toString() + ":" + QString::number(m_socket->peerPort()));
}
void TcpClient::onErrorOccurred(QAbstractSocket::SocketError error)
{
Q_UNUSED(error)
emit errorOccurred(m_socket->errorString());
}
2.3 UDP客户端实现 (core/udpclient.h)
#ifndef UDPCLIENT_H
#define UDPCLIENT_H
#include "networkmanager.h"
#include <QUdpSocket>
class UdpClient : public NetworkManager
{
Q_OBJECT
public:
explicit UdpClient(QObject *parent = nullptr);
~UdpClient();
void connectToHost(const QString &host, quint16 port) override;
void disconnectFromHost() override;
void sendData(const QByteArray &data) override;
bool isConnected() const override;
void setLocalPort(quint16 port);
quint16 localPort() const;
private slots:
void onReadyRead();
private:
QUdpSocket *m_socket;
QString m_host;
quint16 m_port;
quint16 m_localPort;
};
#endif // UDPCLIENT_H
#include "udpclient.h"
UdpClient::UdpClient(QObject *parent)
: NetworkManager(parent), m_socket(new QUdpSocket(this)), m_port(0), m_localPort(0)
{
connect(m_socket, &QUdpSocket::readyRead, this, &UdpClient::onReadyRead);
}
UdpClient::~UdpClient()
{
disconnectFromHost();
}
void UdpClient::connectToHost(const QString &host, quint16 port)
{
m_host = host;
m_port = port;
if(!m_socket->isValid()) {
if(!m_socket->bind(QHostAddress::Any, m_localPort)) {
emit errorOccurred(m_socket->errorString());
return;
}
}
emit connected();
}
void UdpClient::disconnectFromHost()
{
m_socket->close();
emit disconnected();
}
void UdpClient::sendData(const QByteArray &data)
{
if(m_host.isEmpty() || m_port == 0) {
emit errorOccurred("目标主机和端口未设置");
return;
}
qint64 sent = m_socket->writeDatagram(data, QHostAddress(m_host), m_port);
if(sent == -1) {
emit errorOccurred(m_socket->errorString());
}
}
bool UdpClient::isConnected() const
{
return m_socket->isValid();
}
void UdpClient::setLocalPort(quint16 port)
{
m_localPort = port;
}
quint16 UdpClient::localPort() const
{
return m_socket->localPort();
}
void UdpClient::onReadyRead()
{
while(m_socket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(m_socket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
m_socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
emit dataReceived(datagram, sender.toString() + ":" + QString::number(senderPort));
}
}
2.4 HTTP客户端实现 (core/httpclient.h)
#ifndef HTTPCLIENT_H
#define HTTPCLIENT_H
#include "networkmanager.h"
#include <QNetworkAccessManager>
#include <QNetworkRequest>
class HttpClient : public NetworkManager
{
Q_OBJECT
public:
enum HttpMethod {
GET,
POST,
PUT,
DELETE,
HEAD
};
Q_ENUM(HttpMethod)
explicit HttpClient(QObject *parent = nullptr);
~HttpClient();
void connectToHost(const QString &host, quint16 port) override;
void disconnectFromHost() override;
void sendData(const QByteArray &data) override;
bool isConnected() const override;
void setUrl(const QString &url);
void setMethod(HttpMethod method);
void setHeaders(const QMap<QByteArray, QByteArray> &headers);
private slots:
void onRequestFinished(QNetworkReply *reply);
private:
QNetworkAccessManager *m_manager;
QString m_url;
HttpMethod m_method;
QMap<QByteArray, QByteArray> m_headers;
};
#endif // HTTPCLIENT_H
#include "httpclient.h"
HttpClient::HttpClient(QObject *parent)
: NetworkManager(parent), m_manager(new QNetworkAccessManager(this)),
m_method(GET)
{
}
HttpClient::~HttpClient()
{
disconnectFromHost();
}
void HttpClient::connectToHost(const QString &host, quint16 port)
{
if(!m_url.isEmpty()) return;
m_url = QString("http://%1:%2").arg(host).arg(port);
emit connected();
}
void HttpClient::disconnectFromHost()
{
m_manager->clearAccessCache();
emit disconnected();
}
void HttpClient::sendData(const QByteArray &data)
{
if(m_url.isEmpty()) {
emit errorOccurred("URL未设置");
return;
}
QNetworkRequest request;
request.setUrl(QUrl(m_url));
for(auto it = m_headers.constBegin(); it != m_headers.constEnd(); ++it) {
request.setRawHeader(it.key(), it.value());
}
QNetworkReply *reply = nullptr;
switch(m_method) {
case GET:
reply = m_manager->get(request);
break;
case POST:
reply = m_manager->post(request, data);
break;
case PUT:
reply = m_manager->put(request, data);
break;
case DELETE:
reply = m_manager->deleteResource(request);
break;
case HEAD:
reply = m_manager->head(request);
break;
}
if(reply) {
connect(reply, &QNetworkReply::finished, this, [this, reply]() { onRequestFinished(reply); });
}
}
bool HttpClient::isConnected() const
{
return !m_url.isEmpty();
}
void HttpClient::setUrl(const QString &url)
{
m_url = url;
}
void HttpClient::setMethod(HttpMethod method)
{
m_method = method;
}
void HttpClient::setHeaders(const QMap<QByteArray, QByteArray> &headers)
{
m_headers = headers;
}
void HttpClient::onRequestFinished(QNetworkReply *reply)
{
if(reply->error() == QNetworkReply::NoError) {
QByteArray data = reply->readAll();
QString from = reply->request().url().toString();
emit dataReceived(data, from);
} else {
emit errorOccurred(reply->errorString());
}
reply->deleteLater();
}
3. 工具模块
3.1 数据转换器 (utils/dataconverter.h)
#ifndef DATACONVERTER_H
#define DATACONVERTER_H
#include <QObject>
#include <QByteArray>
#include <QString>
#include <QTextCodec>
class DataConverter : public QObject
{
Q_OBJECT
public:
explicit DataConverter(QObject *parent = nullptr);
QByteArray textToData(const QString &text, const QString &encoding, bool isHex);
QString dataToText(const QByteArray &data, const QString &encoding, bool isHex);
QStringList supportedEncodings() const;
private:
QList<QTextCodec*> m_codecs;
};
#endif // DATACONVERTER_H
#include "dataconverter.h"
#include <QTextCodec>
DataConverter::DataConverter(QObject *parent)
: QObject(parent)
{
// 初始化支持的编码列表
m_codecs.append(QTextCodec::codecForName("UTF-8"));
m_codecs.append(QTextCodec::codecForName("GBK"));
m_codecs.append(QTextCodec::codecForName("GB2312"));
m_codecs.append(QTextCodec::codecForName("Big5"));
m_codecs.append(QTextCodec::codecForName("ISO-8859-1"));
m_codecs.append(QTextCodec::codecForName("UTF-16"));
m_codecs.append(QTextCodec::codecForName("Windows-1252"));
}
QByteArray DataConverter::textToData(const QString &text, const QString &encoding, bool isHex)
{
if(isHex) {
QString hexStr = text.trimmed();
hexStr.remove(' ');
return QByteArray::fromHex(hexStr.toLatin1());
}
QTextCodec *codec = QTextCodec::codecForName(encoding.toUtf8());
if(codec) {
return codec->fromUnicode(text);
}
return text.toUtf8();
}
QString DataConverter::dataToText(const QByteArray &data, const QString &encoding, bool isHex)
{
if(isHex) {
return data.toHex(' ');
}
QTextCodec *codec = QTextCodec::codecForName(encoding.toUtf8());
if(codec) {
return codec->toUnicode(data);
}
return QString::fromUtf8(data);
}
QStringList DataConverter::supportedEncodings() const
{
QStringList encodings;
for(QTextCodec *codec : m_codecs) {
if(codec) encodings.append(codec->name());
}
return encodings;
}
3.2 设置管理器 (utils/settingsmanager.h)
#ifndef SETTINGSMANAGER_H
#define SETTINGSMANAGER_H
#include <QObject>
#include <QSettings>
class SettingsManager : public QObject
{
Q_OBJECT
public:
explicit SettingsManager(QObject *parent = nullptr);
void saveWindowGeometry(const QByteArray &geometry);
QByteArray loadWindowGeometry() const;
void saveProtocol(int protocol);
int loadProtocol() const;
void saveHost(const QString &host);
QString loadHost() const;
void savePort(quint16 port);
quint16 loadPort() const;
// 其他设置保存和加载方法...
private:
QSettings m_settings;
};
#endif // SETTINGSMANAGER_H
#include "settingsmanager.h"
SettingsManager::SettingsManager(QObject *parent)
: QObject(parent), m_settings("NetworkDebugger", "NetworkDebugger")
{
}
void SettingsManager::saveWindowGeometry(const QByteArray &geometry)
{
m_settings.setValue("window/geometry", geometry);
}
QByteArray SettingsManager::loadWindowGeometry() const
{
return m_settings.value("window/geometry").toByteArray();
}
void SettingsManager::saveProtocol(int protocol)
{
m_settings.setValue("network/protocol", protocol);
}
int SettingsManager::loadProtocol() const
{
return m_settings.value("network/protocol", 0).toInt();
}
void SettingsManager::saveHost(const QString &host)
{
m_settings.setValue("network/host", host);
}
QString SettingsManager::loadHost() const
{
return m_settings.value("network/host", "127.0.0.1").toString();
}
void SettingsManager::savePort(quint16 port)
{
m_settings.setValue("network/port", port);
}
quint16 SettingsManager::loadPort() const
{
return m_settings.value("network/port", 8080).toUInt();
}
3.3 日志记录器 (utils/logger.h)
#ifndef LOGGER_H
#define LOGGER_H
#include <QObject>
#include <QPlainTextEdit>
class Logger : public QObject
{
Q_OBJECT
public:
enum LogLevel {
Info,
Warning,
Error,
Success
};
Q_ENUM(LogLevel)
explicit Logger(QPlainTextEdit *outputWidget, QObject *parent = nullptr);
void log(const QString &message, LogLevel level = Info);
void logData(const QByteArray &data, const QString &prefix = "", bool isHex = false);
void clear();
private:
QPlainTextEdit *m_outputWidget;
QString getColor(LogLevel level) const;
};
#endif // LOGGER_H
#include "logger.h"
Logger::Logger(QPlainTextEdit *outputWidget, QObject *parent)
: QObject(parent), m_outputWidget(outputWidget)
{
Q_ASSERT(m_outputWidget != nullptr);
}
void Logger::log(const QString &message, LogLevel level)
{
QString timestamp = QDateTime::currentDateTime().toString("[hh:mm:ss.zzz] ");
QString color = getColor(level);
m_outputWidget->appendHtml(QString("<font color='%1'>%2%3</font>")
.arg(color).arg(timestamp).arg(message));
}
void Logger::logData(const QByteArray &data, const QString &prefix, bool isHex)
{
QString text;
if(isHex) {
text = data.toHex(' ');
} else {
text = QString::fromUtf8(data);
}
log(prefix + text, Info);
}
void Logger::clear()
{
m_outputWidget->clear();
}
QString Logger::getColor(LogLevel level) const
{
switch(level) {
case Info: return "black";
case Warning: return "orange";
case Error: return "red";
case Success: return "green";
default: return "black";
}
}
4. 自定义控件
4.1 十六进制文本编辑器 (widgets/hextextedit.h)
#ifndef HEXTEXTEDIT_H
#define HEXTEXTEDIT_H
#include <QTextEdit>
class HexTextEdit : public QTextEdit
{
Q_OBJECT
public:
explicit HexTextEdit(QWidget *parent = nullptr);
void setHexMode(bool enabled);
bool isHexMode() const;
QByteArray data() const;
void setData(const QByteArray &data);
signals:
void dataChanged(const QByteArray &data);
private slots:
void onTextChanged();
private:
bool m_hexMode;
QByteArray m_lastValidData;
};
#endif // HEXTEXTEDIT_H
#include "hextextedit.h"
#include <QRegularExpression>
HexTextEdit::HexTextEdit(QWidget *parent)
: QTextEdit(parent), m_hexMode(false)
{
connect(this, &HexTextEdit::textChanged, this, &HexTextEdit::onTextChanged);
}
void HexTextEdit::setHexMode(bool enabled)
{
if(m_hexMode == enabled) return;
m_hexMode = enabled;
if(enabled) {
// 切换到十六进制模式时,将现有文本转换为十六进制
QString text = toPlainText();
if(!text.isEmpty()) {
QByteArray data = text.toUtf8();
setPlainText(data.toHex(' '));
}
} else {
// 切换到文本模式时,尝试将十六进制转换为文本
QString hexStr = toPlainText().remove(' ');
QByteArray data = QByteArray::fromHex(hexStr.toLatin1());
setPlainText(QString::fromUtf8(data));
}
}
bool HexTextEdit::isHexMode() const
{
return m_hexMode;
}
QByteArray HexTextEdit::data() const
{
if(m_hexMode) {
QString hexStr = toPlainText().remove(' ');
return QByteArray::fromHex(hexStr.toLatin1());
}
return toPlainText().toUtf8();
}
void HexTextEdit::setData(const QByteArray &data)
{
if(m_hexMode) {
setPlainText(data.toHex(' '));
} else {
setPlainText(QString::fromUtf8(data));
}
m_lastValidData = data;
}
void HexTextEdit::onTextChanged()
{
if(!m_hexMode) {
m_lastValidData = toPlainText().toUtf8();
emit dataChanged(m_lastValidData);
return;
}
// 在十六进制模式下验证输入
QString text = toPlainText();
QRegularExpression hexRegex("^([0-9a-fA-F]{2} )*[0-9a-fA-F]{0,2}$");
if(hexRegex.match(text).hasMatch()) {
QString hexStr = text.remove(' ');
if(!hexStr.isEmpty()) {
m_lastValidData = QByteArray::fromHex(hexStr.toLatin1());
} else {
m_lastValidData.clear();
}
emit dataChanged(m_lastValidData);
} else {
// 输入无效,恢复上一次有效值
QSignalBlocker blocker(this);
setPlainText(m_lastValidData.toHex(' '));
}
}
5. 主窗口 (mainwindow.h)
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "core/networkmanager.h"
#include "utils/logger.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onProtocolChanged(int index);
void onConnectClicked();
void onSendClicked();
void onClearClicked();
void onSaveClicked();
void onLoadClicked();
void onNetworkConnected();
void onNetworkDisconnected();
void onDataReceived(const QByteArray &data, const QString &from);
void onErrorOccurred(const QString &errorString);
void onHexSendChanged(bool checked);
void onHexReceiveChanged(bool checked);
void onAutoClearChanged(bool checked);
void onAutoReconnectChanged(bool checked);
void onTimerStateChanged(bool checked);
void onTimerIntervalChanged(int interval);
void onTimerTimeout();
private:
Ui::MainWindow *ui;
NetworkManager *m_networkManager;
Logger *m_logger;
QTimer *m_sendTimer;
void setupConnections();
void setupUI();
void createNetworkManager(NetworkManager::Protocol protocol);
void updateConnectionStatus();
void saveSettings();
void loadSettings();
void startTimer();
void stopTimer();
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "core/tcpclient.h"
#include "core/udpclient.h"
#include "core/httpclient.h"
#include "utils/dataconverter.h"
#include "utils/settingsmanager.h"
#include "utils/logger.h"
#include <QMessageBox>
#include <QFileDialog>
#include <QTimer>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_networkManager(nullptr)
, m_logger(new Logger(ui->receiveTextEdit, this))
, m_sendTimer(new QTimer(this))
{
ui->setupUi(this);
setupUI();
setupConnections();
loadSettings();
}
MainWindow::~MainWindow()
{
saveSettings();
delete ui;
}
void MainWindow::setupUI()
{
// 初始化协议选择
ui->protocolComboBox->addItems({"TCP", "UDP", "HTTP"});
// 初始化HTTP方法选择
ui->httpMethodComboBox->addItems({"GET", "POST", "PUT", "DELETE", "HEAD"});
// 初始化编码选择
DataConverter converter;
ui->encodingComboBox->addItems(converter.supportedEncodings());
ui->encodingComboBox->setCurrentText("UTF-8");
// 设置默认值
ui->ipLineEdit->setText("127.0.0.1");
ui->portLineEdit->setText("8080");
ui->sendTextEdit->setPlainText("Hello, Server!");
// 初始状态
onProtocolChanged(0);
}
void MainWindow::setupConnections()
{
// UI信号连接
connect(ui->protocolComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MainWindow::onProtocolChanged);
connect(ui->connectButton, &QPushButton::clicked,
this, &MainWindow::onConnectClicked);
connect(ui->sendButton, &QPushButton::clicked,
this, &MainWindow::onSendClicked);
connect(ui->clearButton, &QPushButton::clicked,
this, &MainWindow::onClearClicked);
connect(ui->saveButton, &QPushButton::clicked,
this, &MainWindow::onSaveClicked);
connect(ui->loadButton, &QPushButton::clicked,
this, &MainWindow::onLoadClicked);
// 复选框信号连接
connect(ui->hexSendCheckBox, &QCheckBox::toggled,
this, &MainWindow::onHexSendChanged);
connect(ui->hexReceiveCheckBox, &QCheckBox::toggled,
this, &MainWindow::onHexReceiveChanged);
connect(ui->autoClearCheckBox, &QCheckBox::toggled,
this, &MainWindow::onAutoClearChanged);
connect(ui->autoReconnectCheckBox, &QCheckBox::toggled,
this, &MainWindow::onAutoReconnectChanged);
connect(ui->timerCheckBox, &QCheckBox::toggled,
this, &MainWindow::onTimerStateChanged);
connect(ui->timerIntervalSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
this, &MainWindow::onTimerIntervalChanged);
// 定时器信号连接
connect(m_sendTimer, &QTimer::timeout,
this, &MainWindow::onTimerTimeout);
}
void MainWindow::createNetworkManager(NetworkManager::Protocol protocol)
{
if(m_networkManager) {
m_networkManager->disconnect();
m_networkManager->deleteLater();
}
switch(protocol) {
case NetworkManager::TCP:
m_networkManager = new TcpClient(this);
break;
case NetworkManager::UDP:
m_networkManager = new UdpClient(this);
break;
case NetworkManager::HTTP:
m_networkManager = new HttpClient(this);
break;
}
// 网络信号连接
connect(m_networkManager, &NetworkManager::connected,
this, &MainWindow::onNetworkConnected);
connect(m_networkManager, &NetworkManager::disconnected,
this, &MainWindow::onNetworkDisconnected);
connect(m_networkManager, &NetworkManager::dataReceived,
this, &MainWindow::onDataReceived);
connect(m_networkManager, &NetworkManager::errorOccurred,
this, &MainWindow::onErrorOccurred);
}
void MainWindow::onProtocolChanged(int index)
{
NetworkManager::Protocol protocol = static_cast<NetworkManager::Protocol>(index);
createNetworkManager(protocol);
// 更新UI可见性
bool isHttp = (protocol == NetworkManager::HTTP);
ui->ipLabel->setVisible(!isHttp);
ui->ipLineEdit->setVisible(!isHttp);
ui->portLabel->setVisible(!isHttp);
ui->portLineEdit->setVisible(!isHttp);
ui->urlLabel->setVisible(isHttp);
ui->urlLineEdit->setVisible(isHttp);
ui->httpMethodLabel->setVisible(isHttp);
ui->httpMethodComboBox->setVisible(isHttp);
ui->httpPathLabel->setVisible(isHttp);
ui->httpPathLineEdit->setVisible(isHttp);
ui->httpHeadersLabel->setVisible(isHttp);
ui->httpHeadersTextEdit->setVisible(isHttp);
// 更新连接按钮文本
if(protocol == NetworkManager::HTTP) {
ui->connectButton->setText("发送");
} else {
ui->connectButton->setText(m_networkManager->isConnected() ? "断开" : "连接");
}
updateConnectionStatus();
}
void MainWindow::onConnectClicked()
{
QString host = ui->ipLineEdit->text().trimmed();
quint16 port = ui->portLineEdit->text().toUShort();
if(m_networkManager->isConnected()) {
m_networkManager->disconnectFromHost();
} else {
if(host.isEmpty()) {
m_logger->log("主机地址不能为空", Logger::Error);
return;
}
if(port == 0) {
m_logger->log("端口号无效", Logger::Error);
return;
}
m_networkManager->connectToHost(host, port);
}
}
void MainWindow::onSendClicked()
{
QString data = ui->sendTextEdit->toPlainText();
if(data.isEmpty()) {
m_logger->log("发送数据为空", Logger::Warning);
return;
}
DataConverter converter;
bool isHex = ui->hexSendCheckBox->isChecked();
QString encoding = ui->encodingComboBox->currentText();
QByteArray sendData = converter.textToData(data, encoding, isHex);
// 记录发送的数据
m_logger->logData(sendData, "[发送] ", true);
// 发送数据
m_networkManager->sendData(sendData);
// 自动清空发送框
if(ui->autoClearCheckBox->isChecked()) {
ui->sendTextEdit->clear();
}
}
void MainWindow::onClearClicked()
{
m_logger->clear();
}
void MainWindow::onSaveClicked()
{
QString fileName = QFileDialog::getSaveFileName(this, "保存会话", "", "文本文件 (*.txt);;所有文件 (*.*)");
if(fileName.isEmpty()) return;
QFile file(fileName);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(this, "错误", "无法打开文件进行写入");
return;
}
QTextStream out(&file);
out << "=== 网络调试助手会话 ===\n";
out << "协议: " << ui->protocolComboBox->currentText() << "\n";
out << "IP: " << ui->ipLineEdit->text() << "\n";
out << "端口: " << ui->portLineEdit->text() << "\n";
out << "URL: " << ui->urlLineEdit->text() << "\n";
out << "HTTP方法: " << ui->httpMethodComboBox->currentText() << "\n";
out << "HTTP路径: " << ui->httpPathLineEdit->text() << "\n";
out << "HTTP头:\n" << ui->httpHeadersTextEdit->toPlainText() << "\n";
out << "发送数据:\n" << ui->sendTextEdit->toPlainText() << "\n";
out << "接收数据:\n" << ui->receiveTextEdit->toPlainText() << "\n";
file.close();
m_logger->log(QString("会话已保存到 %1").arg(fileName), Logger::Success);
}
void MainWindow::onLoadClicked()
{
QString fileName = QFileDialog::getOpenFileName(this, "加载会话", "", "文本文件 (*.txt);;所有文件 (*.*)");
if(fileName.isEmpty()) return;
QFile file(fileName);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::warning(this, "错误", "无法打开文件进行读取");
return;
}
QTextStream in(&file);
QString content = in.readAll();
file.close();
// 简单的解析逻辑
QStringList lines = content.split("\n");
for(int i = 0; i < lines.count(); ++i) {
QString line = lines[i].trimmed();
if(line.startsWith("协议: ")) {
QString protocol = line.mid(3).trimmed();
int index = ui->protocolComboBox->findText(protocol);
if(index >= 0) ui->protocolComboBox->setCurrentIndex(index);
} else if(line.startsWith("IP: ")) {
ui->ipLineEdit->setText(line.mid(3).trimmed());
} else if(line.startsWith("端口: ")) {
ui->portLineEdit->setText(line.mid(4).trimmed());
} else if(line.startsWith("URL: ")) {
ui->urlLineEdit->setText(line.mid(4).trimmed());
} else if(line.startsWith("HTTP方法: ")) {
QString method = line.mid(6).trimmed();
int index = ui->httpMethodComboBox->findText(method);
if(index >= 0) ui->httpMethodComboBox->setCurrentIndex(index);
} else if(line.startsWith("HTTP路径: ")) {
ui->httpPathLineEdit->setText(line.mid(6).trimmed());
} else if(line == "HTTP头:") {
QString headers;
while(++i < lines.count() && !lines[i].startsWith("发送数据:")) {
headers += lines[i] + "\n";
}
ui->httpHeadersTextEdit->setPlainText(headers.trimmed());
--i;
} else if(line == "发送数据:") {
QString sendData;
while(++i < lines.count() && !lines[i].startsWith("接收数据:")) {
sendData += lines[i] + "\n";
}
ui->sendTextEdit->setPlainText(sendData.trimmed());
--i;
} else if(line == "接收数据:") {
QString receiveData;
while(++i < lines.count()) {
receiveData += lines[i] + "\n";
}
ui->receiveTextEdit->setPlainText(receiveData.trimmed());
}
}
m_logger->log(QString("会话已从 %1 加载").arg(fileName), Logger::Success);
}
void MainWindow::onNetworkConnected()
{
ui->connectButton->setText("断开");
m_logger->log("连接已建立", Logger::Success);
updateConnectionStatus();
}
void MainWindow::onNetworkDisconnected()
{
ui->connectButton->setText("连接");
m_logger->log("连接已断开", Logger::Error);
updateConnectionStatus();
// 自动重连逻辑
if(ui->autoReconnectCheckBox->isChecked() &&
ui->protocolComboBox->currentIndex() == NetworkManager::TCP) {
QTimer::singleShot(1000, this, [this]() {
m_logger->log("尝试自动重新连接...", Logger::Warning);
QString host = ui->ipLineEdit->text().trimmed();
quint16 port = ui->portLineEdit->text().toUShort();
m_networkManager->connectToHost(host, port);
});
}
}
void MainWindow::onDataReceived(const QByteArray &data, const QString &from)
{
DataConverter converter;
bool isHex = ui->hexReceiveCheckBox->isChecked();
QString encoding = ui->encodingComboBox->currentText();
QString text = converter.dataToText(data, encoding, isHex);
QString timestamp = QDateTime::currentDateTime().toString("[hh:mm:ss.zzz] ");
QString protocol = ui->protocolComboBox->currentText();
ui->receiveTextEdit->append(timestamp + "[" + protocol + "] " + from + " " + text);
}
void MainWindow::onErrorOccurred(const QString &errorString)
{
m_logger->log(errorString, Logger::Error);
}
void MainWindow::onHexSendChanged(bool checked)
{
// 可以在这里添加发送框格式切换逻辑
Q_UNUSED(checked)
}
void MainWindow::onHexReceiveChanged(bool checked)
{
// 可以在这里添加接收框格式切换逻辑
Q_UNUSED(checked)
}
void MainWindow::onAutoClearChanged(bool checked)
{
Q_UNUSED(checked)
}
void MainWindow::onAutoReconnectChanged(bool checked)
{
Q_UNUSED(checked)
}
void MainWindow::onTimerStateChanged(bool checked)
{
if(checked) {
startTimer();
} else {
stopTimer();
}
}
void MainWindow::onTimerIntervalChanged(int interval)
{
if(m_sendTimer->isActive()) {
m_sendTimer->setInterval(interval);
}
}
void MainWindow::onTimerTimeout()
{
onSendClicked();
}
void MainWindow::updateConnectionStatus()
{
QString status;
QString protocol = ui->protocolComboBox->currentText();
if(protocol == "TCP") {
if(m_networkManager->isConnected()) {
status = QString("TCP 已连接 %1:%2")
.arg(ui->ipLineEdit->text())
.arg(ui->portLineEdit->text());
} else {
status = "TCP 未连接";
}
} else if(protocol == "UDP") {
UdpClient *udpClient = qobject_cast<UdpClient*>(m_networkManager);
if(udpClient && udpClient->isConnected()) {
status = QString("UDP 已绑定本地端口 %1")
.arg(udpClient->localPort());
} else {
status = "UDP 未绑定";
}
} else if(protocol == "HTTP") {
status = "HTTP 准备就绪";
}
statusBar()->showMessage(status);
}
void MainWindow::saveSettings()
{
SettingsManager settings;
settings.saveWindowGeometry(saveGeometry());
settings.saveProtocol(ui->protocolComboBox->currentIndex());
settings.saveHost(ui->ipLineEdit->text());
settings.savePort(ui->portLineEdit->text().toUShort());
// 可以保存更多设置...
}
void MainWindow::loadSettings()
{
SettingsManager settings;
restoreGeometry(settings.loadWindowGeometry());
ui->protocolComboBox->setCurrentIndex(settings.loadProtocol());
ui->ipLineEdit->setText(settings.loadHost());
ui->portLineEdit->setText(QString::number(settings.loadPort()));
// 可以加载更多设置...
}
void MainWindow::startTimer()
{
int interval = ui->timerIntervalSpinBox->value();
m_sendTimer->start(interval);
m_logger->log(QString("定时发送已启动,间隔 %1 毫秒").arg(interval), Logger::Success);
}
void MainWindow::stopTimer()
{
m_sendTimer->stop();
m_logger->log("定时发送已停止", Logger::Error);
}
4. 主程序文件 (main.cpp)
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置应用程序信息
QCoreApplication::setApplicationName("网络调试助手");
QCoreApplication::setApplicationVersion("1.0");
QCoreApplication::setOrganizationName("NetworkDebugger");
// 设置样式
QApplication::setStyle("Fusion");
MainWindow w;
w.show();
return a.exec();
}
5. 项目文件 (networkdebugger.pro)
qmake
QT += core gui network widgets printsupport
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
TARGET = NetworkDebugger
TEMPLATE = app
SOURCES += \
main.cpp \
mainwindow.cpp \
core/networkmanager.cpp \
core/tcpclient.cpp \
core/udpclient.cpp \
core/httpclient.cpp \
utils/dataconverter.cpp \
utils/settingsmanager.cpp \
utils/logger.cpp \
widgets/hextextedit.cpp
HEADERS += \
mainwindow.h \
core/networkmanager.h \
core/tcpclient.h \
core/udpclient.h \
core/httpclient.h \
utils/dataconverter.h \
utils/settingsmanager.h \
utils/logger.h \
widgets/hextextedit.h
FORMS += \
mainwindow.ui
# 中文支持
CODECFORTR = UTF-8
# 添加图标(如果有)
RC_ICONS = app.ico
# 发布版本配置
CONFIG(release, debug|release) {
DEFINES += QT_NO_DEBUG_OUTPUT
DEFINES += QT_NO_WARNING_OUTPUT
}
# 调试版本配置
CONFIG(debug, debug|release) {
DEFINES += QT_DEBUG
}
6. 功能说明
这个网络调试助手实现了以下功能:
-
多协议支持:
-
TCP客户端
-
UDP客户端
-
HTTP客户端(支持GET/POST/PUT/DELETE/HEAD方法)
-
-
数据发送功能:
-
支持文本和十六进制格式发送
-
支持多种编码格式(UTF-8, GBK等)
-
定时发送功能
-
-
数据接收功能:
-
实时显示接收数据
-
支持文本和十六进制格式显示
-
显示数据来源信息
-
-
实用功能:
-
自动重连
-
自动清空发送框
-
会话保存与加载
-
历史记录查看
-
-
用户界面:
-
状态显示
-
日志记录
-
协议切换自动调整UI
-
7. 使用说明
-
选择协议类型(TCP/UDP/HTTP)
-
输入服务器地址和端口(TCP/UDP)或URL(HTTP)
-
输入要发送的数据
-
点击"连接"按钮建立连接(TCP/UDP)或直接发送(HTTP)
-
接收的数据将显示在接收框中
8. 扩展建议
-
可以添加SSL/TLS支持
-
可以实现WebSocket协议支持
-
可以添加数据包分析功能
-
可以实现更复杂的历史记录管理
-
可以添加数据图表显示功能
-
添加插件系统:
-
将不同协议实现为插件,运行时加载
-
方便第三方扩展新协议
-
-
实现数据解析器:
-
支持JSON、XML等格式数据的解析和格式化
-
添加脚本支持:
-
支持使用脚本自动化测试流程
-
-
实现多标签界面:
-
支持同时进行多个网络会话
-
-
添加性能统计:
-
统计网络延迟、吞吐量等指标
-
-
这个网络调试助手提供了基本的网络调试功能,后续可以根据实际需求进一步扩展和完善。