前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Qt | TCP客户端简单实现+TCP助手测试

Qt | TCP客户端简单实现+TCP助手测试

原创
作者头像
Qt历险记
发布2024-12-02 19:49:55
发布2024-12-02 19:49:55
16100
代码可运行
举报
文章被收录于专栏:Qt6 研发工程师Qt6 研发工程师
运行总次数:0
代码可运行

点击上方"蓝字"关注我们

01、QTcpSocket

>>>QTcpSocket是Qt框架中的一个类,用于实现TCP网络通信。它提供了与TCP服务器的连接功能,并允许发送和接收数据。QTcpSocket是继承自QAbstractSocket的,因此它具有许多网络套接字的基本功能。 QTcpSocket的主要功能

  1. 连接到服务器:可以通过指定主机名和端口号来连接到TCP服务器。
  2. 数据传输:支持发送和接收数据,数据以字节流的形式进行处理。
  3. 信号和槽机制:提供信号(signals)和槽(slots)机制,以便于处理异步事件,例如连接成功、接收到数据、连接断开等。
  4. SSL支持:可以通过QSslSocket进行SSL加密的TCP通信。

常用函数 以下是QTcpSocket的一些常用函数:

  • 构造函数与析构函数
    • QTcpSocket(QObject *parent = nullptr): 构造函数。
    • ~QTcpSocket(): 析构函数。
  • 连接与断开
    • void connectToHost(const QString &hostName, quint16 port): 连接到指定的主机和端口。
    • void disconnectFromHost(): 断开与主机的连接。
  • 数据发送
    • qint64 write(const QByteArray &data): 发送数据到远程主机。
  • 接收数据
    • void readAll(): 读取所有可用的数据。
    • QByteArray read(qint64 maxSize = 0): 读取指定大小的数据。
  • 状态查询
    • QAbstractSocket::SocketState state() const: 获取当前套接字的状态。
    • QString errorString() const: 获取错误信息。
  • 信号
    • void connected(): 当成功连接到主机时发出此信号。
    • void disconnected(): 当与主机断开连接时发出此信号。
    • void readyRead(): 当有新数据可读时发出此信号。
    • void errorOccurred(QAbstractSocket::SocketError socketError): 当出现错误时发出此信号。

02、QDataStream

>>>QDataStream是Qt框架中的一个类,主要用于在Qt中以流的方式读写二进制数据。它可以处理多种数据类型,如整型、浮点型、字符串等,并且支持对数据的序列化和反序列化。QDataStream通常与QFileQTcpSocket等类一起使用,以便于文件和网络通信中的数据操作。 QDataStream的主要功能

  1. 数据序列化:将数据类型转换为字节流,以便存储或传输。
  2. 数据反序列化:从字节流中读取数据,恢复为原有数据类型。
  3. 跨平台兼容性:支持不同平台之间的数据交互,自动处理字节序问题。

常用函数 以下是QDataStream的一些常用函数:

  • 构造函数
    • QDataStream(QIODevice *device): 创建一个数据流对象,关联到指定的设备(如文件、套接字等)。
    • QDataStream(const QByteArray &array, QIODevice::OpenMode mode = QIODevice::ReadOnly): 从字节数组创建数据流。
  • 数据写入
    • QDataStream &operator<<(const QString &str): 将字符串写入数据流。
    • QDataStream &operator<<(int i): 将整型数据写入数据流。
    • QDataStream &operator<<(double d): 将浮点型数据写入数据流。
    • QDataStream &operator<<(const QByteArray &ba): 将字节数组写入数据流。
    • QDataStream &operator<<(const QVariant &var): 将变体值写入数据流。
  • 数据读取
    • QDataStream &operator>>(QString &str): 从数据流中读取字符串。
    • QDataStream &operator>>(int &i): 从数据流中读取整型数据。
    • QDataStream &operator>>(double &d): 从数据流中读取浮点型数据。
    • QDataStream &operator>>(QByteArray &ba): 从数据流中读取字节数组。
    • QDataStream &operator>>(QVariant &var): 从数据流中读取变体值。
  • 流状态和版本
    • bool atEnd() const: 判断是否到达流的末尾。
    • void setVersion(QDataStream::Version version): 设置数据流版本。
    • QDataStream::Version version() const: 获取当前数据流版本。
  • 其他功能
    • QDataStream &operator>>(QDataStream &stream): 将数据流对象复制到另一个数据流。

03、实战

>>>

cmake_minimum_required(VERSION 3.16)  # 设置 CMake 的最低版本要求为 3.16project(fortuneclient LANGUAGES CXX)  # 定义项目名称为 fortuneclient,并指定使用的语言为 C++# 如果没有定义 INSTALL_EXAMPLESDIR,则设置其默认值为 "examples"if(NOT DEFINED INSTALL_EXAMPLESDIR)    set(INSTALL_EXAMPLESDIR "examples")endif()# 设置安装示例的目录set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/fortuneclient")# 查找 Qt6 必需的模块,包括 Core、Gui、Network 和 Widgetsfind_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)# 配置 Qt 标准项目设置qt_standard_project_setup()# 添加可执行文件 fortuneclient,并指定其源文件qt_add_executable(fortuneclient    client.cpp client.h  # 客户端的源文件    main.cpp             # 主程序的源文件)# 设置可执行文件的属性,使其在 Windows 和 macOS 上为特定类型set_target_properties(fortuneclient PROPERTIES    WIN32_EXECUTABLE TRUE  # 设置为 Windows 下的 GUI 应用程序    MACOSX_BUNDLE TRUE     # 设置为 macOS 下的应用程序包)# 链接需要的 Qt 库target_link_libraries(fortuneclient PRIVATE    Qt6::Core      # 链接 Qt6 Core 模块    Qt6::Gui       # 链接 Qt6 Gui 模块    Qt6::Network    # 链接 Qt6 Network 模块    Qt6::Widgets    # 链接 Qt6 Widgets 模块)# 安装可执行文件及其资源install(TARGETS fortuneclient    RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"  # 可执行文件安装位置    BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"   # 应用程序包安装位置    LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"  # 库文件安装位置)

04、client.h

>>>client.h

代码语言:javascript
代码运行次数:0
复制
#ifndef CLIENT_H  // 如果没有定义 CLIENT_H,则包含以下内容#define CLIENT_H​#include <QDataStream>  // 引入 QDataStream 类,用于数据流的读写#include <QDialog>     // 引入 QDialog 基类,用于创建对话框#include <QTcpSocket>  // 引入 QTcpSocket 类,用于 TCP 网络通信​QT_BEGIN_NAMESPACE  // Qt 命名空间的开始class QComboBox;    // 前向声明 QComboBox 类class QLabel;       // 前向声明 QLabel 类class QLineEdit;    // 前向声明 QLineEdit 类class QPushButton;  // 前向声明 QPushButton 类class QTcpSocket;   // 再次前向声明 QTcpSocket 类QT_END_NAMESPACE    // Qt 命名空间的结束​// 定义 Client 类,继承自 QDialogclass Client : public QDialog{    Q_OBJECT  // 宏,标记该类为 Qt 的元对象系统的一部分​public:    explicit Client(QWidget *parent = nullptr);  // 构造函数,接受父 Widget 指针​private slots:    void requestNewFortune();                     // 请求新的财富(数据)    void readFortune();                          // 读取财富数据    void displayError(QAbstractSocket::SocketError socketError);  // 显示 socket 错误    void enableGetFortuneButton();               // 启用获取财富的按钮​private:    QComboBox *hostCombo = nullptr;              // 主机下拉框指针    QLineEdit *portLineEdit = nullptr;           // 端口输入框指针    QLabel *statusLabel = nullptr;                // 状态标签指针    QPushButton *getFortuneButton = nullptr;     // 获取财富按钮指针​    QTcpSocket *tcpSocket = nullptr;              // TCP 套接字指针    QDataStream in;                               // 输入数据流    QString currentFortune;                       // 当前财富字符串};​#endif  // 结束条件,避免重复包含​

05、client.cpp

>>>

代码语言:javascript
代码运行次数:0
复制
/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释 
* #include <QtWidgets>  // 引入 Qt Widgets 模块#include <QtNetwork>  // 引入 Qt Network 模块​#include "client.h"   // 引入 Client 类的头文件​//! [0]Client::Client(QWidget *parent)  // Client 类的构造函数    : QDialog(parent)             // 调用 QDialog 的构造函数,设置父窗口    , hostCombo(new QComboBox)    // 创建一个新的 QComboBox 用于选择主机    , portLineEdit(new QLineEdit)  // 创建一个新的 QLineEdit 用于输入端口    , getFortuneButton(new QPushButton(tr("获取财富"))) // 创建获取财富按钮,按钮文本为“获取财富”    , tcpSocket(new QTcpSocket(this)) // 创建一个新的 TCP 套接字{    //! [0]    hostCombo->setEditable(true);  // 使主机下拉框可编辑    // 找出这台机器的名称    QString name = QHostInfo::localHostName();  // 获取本地主机名    if (!name.isEmpty()) {        hostCombo->addItem(name);  // 将主机名添加到下拉框        QString domain = QHostInfo::localDomainName();  // 获取本地主机的域名        if (!domain.isEmpty())            hostCombo->addItem(name + QChar('.') + domain); // 将域名添加到下拉框    }    if (name != QLatin1String("localhost"))        hostCombo->addItem(QString("localhost"));  // 如果不是 localhost,则添加 localhost​    // 找出这台机器的 IP 地址    const QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses(); // 获取所有地址    // 添加非本地主机地址    for (const QHostAddress &entry : ipAddressesList) { // 遍历所有地址        if (!entry.isLoopback()) // 如果不是回环地址            hostCombo->addItem(entry.toString()); // 将地址添加到下拉框    }    // 添加本地主机地址    for (const QHostAddress &entry : ipAddressesList) {        if (entry.isLoopback()) // 如果是回环地址            hostCombo->addItem(entry.toString()); // 将地址添加到下拉框    }​    portLineEdit->setValidator(new QIntValidator(1, 65535, this)); // 设置端口输入框的有效值为 1 到 65535​    auto hostLabel = new QLabel(tr("&服务器名称:")); // 创建服务器名称标签    hostLabel->setBuddy(hostCombo); // 将标签与下拉框关联    auto portLabel = new QLabel(tr("服务&器端口:")); // 创建服务器端口标签    portLabel->setBuddy(portLineEdit); // 将标签与端口输入框关联​    statusLabel = new QLabel(tr("本示例需要您同时运行 "                                "财富服务器示例。")); // 创建状态标签,提示需要运行 Fortune 服务器​    getFortuneButton->setDefault(true); // 设置获取财富按钮为默认按钮    getFortuneButton->setEnabled(false); // 初始时禁用获取财富按钮​    auto quitButton = new QPushButton(tr("退出"));  // 创建退出按钮​    auto buttonBox = new QDialogButtonBox; // 创建对话框按钮框    buttonBox->addButton(getFortuneButton, QDialogButtonBox::ActionRole); // 添加获取财富按钮    buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole); // 添加退出按钮​    //! [1]    in.setDevice(tcpSocket); // 设置数据流的设备为 TCP 套接字    in.setVersion(QDataStream::Qt_6_5); // 设置数据流版本    //! [1]​    // 连接信号与槽    connect(hostCombo, &QComboBox::editTextChanged,            this, &Client::enableGetFortuneButton); // 当主机名改变时,启用获取财富按钮    connect(portLineEdit, &QLineEdit::textChanged,            this, &Client::enableGetFortuneButton); // 当端口文本改变时,启用获取财富按钮    connect(getFortuneButton, &QAbstractButton::clicked,            this, &Client::requestNewFortune); // 当获取财富按钮被点击时,发送请求    connect(quitButton, &QAbstractButton::clicked, this, &QWidget::close); // 当退出按钮被点击时,关闭窗口    //! [2] //! [3]    connect(tcpSocket, &QIODevice::readyRead, this, &Client::readFortune); // 当 TCP 套接字准备好读取数据时,读取财富    //! [2] //! [4]    connect(tcpSocket, &QAbstractSocket::errorOccurred,            this, &Client::displayError); // 当发生错误时,显示错误信息    //! [4]​    QGridLayout *mainLayout = nullptr; // 主布局指针    // 判断应用程序的显示样式    if (QGuiApplication::styleHints()->showIsFullScreen() || QGuiApplication::styleHints()->showIsMaximized()) {        auto outerVerticalLayout = new QVBoxLayout(this); // 创建垂直布局        outerVerticalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding)); // 添加空隙        auto outerHorizontalLayout = new QHBoxLayout; // 创建水平布局        outerHorizontalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored)); // 添加空隙        auto groupBox = new QGroupBox(QGuiApplication::applicationDisplayName()); // 创建分组框        mainLayout = new QGridLayout(groupBox); // 在分组框中创建网格布局        outerHorizontalLayout->addWidget(groupBox); // 将分组框添加到水平布局        outerHorizontalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored)); // 添加空隙        outerVerticalLayout->addLayout(outerHorizontalLayout); // 将水平布局添加到垂直布局        outerVerticalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding)); // 添加空隙    } else {        mainLayout = new QGridLayout(this); // 默认创建网格布局    }    // 将控件添加到主布局中    mainLayout->addWidget(hostLabel, 0, 0); // 添加服务器名称标签    mainLayout->addWidget(hostCombo, 0, 1); // 添加服务器名称下拉框    mainLayout->addWidget(portLabel, 1, 0); // 添加服务器端口标签    mainLayout->addWidget(portLineEdit, 1, 1); // 添加服务器端口输入框    mainLayout->addWidget(statusLabel, 2, 0, 1, 2); // 添加状态标签    mainLayout->addWidget(buttonBox, 3, 0, 1, 2); // 添加按钮框​    setWindowTitle(QGuiApplication::applicationDisplayName()); // 设置窗口标题    portLineEdit->setFocus(); // 设置端口输入框为焦点    //! [5]}//! [5]​//! [6]void Client::requestNewFortune() // 请求新的财富{    getFortuneButton->setEnabled(false); // 禁用获取财富按钮    tcpSocket->abort(); // 取消当前连接    //! [7]    tcpSocket->connectToHost(hostCombo->currentText(), // 连接到指定主机                             portLineEdit->text().toInt()); // 连接到指定端口    //! [7]}//! [6]​//! [8]void Client::readFortune() // 读取财富数据{    qDebug() << "Incoming data size:" << tcpSocket->bytesAvailable();    QByteArray rawData = tcpSocket->readAll(); // 读取所有可用数据    qDebug() << "Raw data:" << rawData; // 打印原始数据​    in.startTransaction(); // 开始事务​    QString nextFortune; // 存储下一个财富    in >> nextFortune; // 从数据流中读取财富数据​    qDebug() << "nextFortune = " << nextFortune.toUtf8();​    if (!in.commitTransaction()) // 提交事务        return;​    if (nextFortune == currentFortune) { // 如果读取到的财富与当前财富相同        QTimer::singleShot(0, this, &Client::requestNewFortune); // 立即请求新的财富        return;    }​    currentFortune = nextFortune; // 更新当前财富    statusLabel->setText(currentFortune); // 显示当前财富    getFortuneButton->setEnabled(true); // 启用获取财富按钮}//! [8]​//! [13]void Client::displayError(QAbstractSocket::SocketError socketError) // 显示错误信息{    switch (socketError) {    case QAbstractSocket::RemoteHostClosedError: // 远程主机关闭错误        break;    case QAbstractSocket::HostNotFoundError: // 主机未找到错误        QMessageBox::information(this, tr("财富客户端"), // 弹出信息框                                 tr("未找到主机。请检查 "                                    "主机名和端口设置。"));        break;    case QAbstractSocket::ConnectionRefusedError: // 连接被拒绝错误        QMessageBox::information(this, tr("财富客户端"),                                 tr("连接被对方拒绝。"                                    "请确保财富服务器正在运行,"                                    "并检查主机名和端口设置是否正确。"));        break;    default: // 其它错误        QMessageBox::information(this, tr("财富客户端"),                                 tr("发生了以下错误: %1.")                                     .arg(tcpSocket->errorString())); // 显示错误信息    }​    getFortuneButton->setEnabled(true); // 启用获取财富按钮}//! [13]​void Client::enableGetFortuneButton() // 启用获取财富按钮{    getFortuneButton->setEnabled(!hostCombo->currentText().isEmpty() && // 只有当主机名和端口不为空时,才启用按钮                                 !portLineEdit->text().isEmpty());}​
*/

06、main.cpp

>>>

代码语言:javascript
代码运行次数:0
复制
#include <QApplication>#include "client.h"​int main(int argc, char *argv[]){    QApplication app(argc, argv);    QApplication::setApplicationDisplayName(Client::tr("Qt 历险记 qq:906134236"));    Client client;    client.show();    return app.exec();}​

07、源码下载

>>>通过网盘分享的文件:fortuneclient 链接: https://pan.baidu.com/s/1HZlcnzmdRDvUUsDQN-QDDw?pwd=ypkq 提取码: ypkq

08、演示

>>>

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 点击上方"蓝字"关注我们
  • 01、QTcpSocket
  • 02、QDataStream
  • 03、实战
  • 04、client.h
  • 05、client.cpp
  • 06、main.cpp
  • 07、源码下载
  • 08、演示
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档