Qt编程:实用小工具集 -- 网络调试助手

    利用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. 功能说明

这个网络调试助手实现了以下功能:

  1. 多协议支持

    • TCP客户端

    • UDP客户端

    • HTTP客户端(支持GET/POST/PUT/DELETE/HEAD方法)

  2. 数据发送功能

    • 支持文本和十六进制格式发送

    • 支持多种编码格式(UTF-8, GBK等)

    • 定时发送功能

  3. 数据接收功能

    • 实时显示接收数据

    • 支持文本和十六进制格式显示

    • 显示数据来源信息

  4. 实用功能

    • 自动重连

    • 自动清空发送框

    • 会话保存与加载

    • 历史记录查看

  5. 用户界面

    • 状态显示

    • 日志记录

    • 协议切换自动调整UI

7. 使用说明

  1. 选择协议类型(TCP/UDP/HTTP)

  2. 输入服务器地址和端口(TCP/UDP)或URL(HTTP)

  3. 输入要发送的数据

  4. 点击"连接"按钮建立连接(TCP/UDP)或直接发送(HTTP)

  5. 接收的数据将显示在接收框中

8. 扩展建议

  1. 可以添加SSL/TLS支持

  2. 可以实现WebSocket协议支持

  3. 可以添加数据包分析功能

  4. 可以实现更复杂的历史记录管理

  5. 可以添加数据图表显示功能

  6. 添加插件系统

    • 将不同协议实现为插件,运行时加载

    • 方便第三方扩展新协议

  7. 实现数据解析器

    • 支持JSON、XML等格式数据的解析和格式化

    • 添加脚本支持

      • 支持使用脚本自动化测试流程

    • 实现多标签界面

      • 支持同时进行多个网络会话

    • 添加性能统计

      • 统计网络延迟、吞吐量等指标

    这个网络调试助手提供了基本的网络调试功能,后续可以根据实际需求进一步扩展和完善。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值