Qt网络编程-TcpClient入门Demo(1)

11 篇文章 12 订阅

Qt网络编程-TcpClient入门Demo(1)🔺

更多精彩内容
👉个人内容分类汇总 👈
简易版
👉Qt网络编程-简易版TcpClient入门Demo(1)👈
👉Qt网络编程-简易版TcpServer入门Demo(2)👈
👉Qt网络编程-简易版UDP单播通信入门Demo(3)👈
👉Qt网络编程-简易版UDP单播通信入门Demo(4)👈
👉Qt网络编程-简易版UDP组播通信入门Demo(5)👈
👉Qt网络编程-简易版UDP广播通信入门Demo(6)👈
进阶版
👉Qt网络编程-TcpClient入门Demo(1)👈
👉Qt网络编程-TcpServer入门Demo(2)👈

1、TCP Client🔗

使用QTcpSocket实现TCP Client,十分详细的入门Demo
  • 支持打开多个TCP Client窗口;👍
  • 可选择是否以16进制字符串形式显示发送、接收的数据;👍
  • 自动统计发送数据的总字节大小、接收数据的总字节大小;👌
  • 判断TCP Socket状态变化;✌️
  • 判断TCP Socket各类异常状态;✋
  • 详细说明close、disconnectFromHost、abort三种断开连接的方式和优缺点; 👐
  • 代码注释详细,便于学习阅读。 👇

1.1 示例代码结构👀

在这里插入图片描述

1.2 TCPClient流程图🔀

在这里插入图片描述

1.3 实现效果🙉

在这里插入图片描述

1.4 关键信号🎻

信号说明
connectedQTcpSocket连接成功后发出此信号
disconnectedQTcpSocket断开连接后发出此信号
stateChangedQTcpSocket状态发生改变后发出此信号(不必要)
readyRead有可读数据时发出此信号
errorOccurredQTcpSocket发生异常时发出此信号,qt5.15 后error()信号已经弃用

1.5 关键函数 💍

函数名说明
m_tcpClient->connectToHost(IP, 端口)TCP连接Server
m_tcpClient->abort()立即关闭套接字,丢弃写入缓冲区中的任何未决数据
m_tcpClient->close()关闭 QIODevice并调用disconnectFromHost
m_tcpClient->disconnectFromHost()如果有待写入的数据等待写入,等待所有数据写入完毕(不一定立即关闭)[不推荐使用]
m_tcpClient->state()判断QTcpSocket当前状态,UnconnectedState未连接、ConnectedState已连接
m_tcpClient->waitForDisconnected(1000)等待关闭连接,如果使用close或disconnectFromHost关闭连接则可选择使用这个函数
m_tcpClient->write(QByteArray)发送数据
m_tcpClient->readAll()读取所有数据

1.6 关于connectToHost内存泄漏问题🐷

  • 在网络有很多博客都在说connectToHost会有内存泄漏问题,必须控制connectToHost的使用次数,连接接近1000时才会有明显的影响 (这些博客还自己测试过,通过任务管理器看出内存泄漏,不知道他是怎么看出来的)😬;
  • 这么明显的内存泄露我相信Qt开发者不可能不知道,所以对于这个问题最好自己测试一下;👌
  • 一个定时器,间隔100毫秒调用一次下列代码中的on_but_connect_clicked()函数,我一共调用了十万次,通过 Visual Studio 的进程内存窗口看是否内存泄漏 并没有出现内存泄漏,可以放心使用
  • 对于网上说有内存泄漏我有几个猜测:
    1. 在Qt的 远古版本中有这个问题,新版本中被修复了;
    2. 最开始提出connectToHost有内存泄漏的人代码写出来,然后后面一堆人跟着抄作业,抄作业也抄错了。

在这里插入图片描述

  • 对于使用任务管理器看内存泄漏一事也顺手测一下,并不能看出来 👊

在这里插入图片描述
在这里插入图片描述

1.7 主要代码🌈

  • .pro.pri文件中添加QT += network
#include "tcpclient.h"
#include "ui_tcpclient.h"
#include <QHostAddress>
#include <QDebug>
#include <QByteArray>
#include <QHostInfo>

TCPClient::TCPClient(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::TCPClient)
{
    ui->setupUi(this);
    this->setWindowTitle("TCP客户端Demo");

    init();
    connectSlots();
}

TCPClient::~TCPClient()
{
#ifdef QT_DEBUG
    qDebug() <<"~TCPClient()";
#endif
    if(m_tcpClient->state() != QAbstractSocket::UnconnectedState)         // 判断连接状态
    {
        m_tcpClient->abort();
    }
    delete ui;
}

/**
 * @brief  获取本机IPv4地址
 * @return
 */
QString getLocalIP()
{
    QHostInfo info = QHostInfo::fromName(QHostInfo::localHostName());

    for(auto address : info.addresses())
    {
        if(address.protocol() == QAbstractSocket::IPv4Protocol)
        {
            return address.toString();
        }
    }

    return "0.0.0.0";
}

void TCPClient::init()
{
    m_tcpClient = new QTcpSocket(this);
    ui->line_localAddress->setText(getLocalIP());
}

void TCPClient::connectSlots()
{
    connect(m_tcpClient, &QTcpSocket::connected, this, &TCPClient::on_connected);
    connect(m_tcpClient, &QTcpSocket::disconnected, this, &TCPClient::on_disconnected);
    connect(m_tcpClient, &QTcpSocket::stateChanged, this, &TCPClient::on_stateChanged);
    connect(m_tcpClient, &QTcpSocket::readyRead, this, &TCPClient::on_readyRead);

#if (QT_VERSION <= QT_VERSION_CHECK(5,15,0))        // qt5.15 后error已经弃用,这里改用errorOccurred
    connect(m_tcpClient, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error),
            this, &TCPClient::on_errorOccurred);    // 由于QAbstractSocket中有两个error(),所以不能直接使用Qt5的信号槽绑定方式
#else
    connect(m_tcpClient, &QTcpSocket::errorOccurred, this, &TCPClient::on_errorOccurred);
#endif
}

/**
 * @brief 调用 connectToHost() 并成功建立连接。
 */
void TCPClient::on_connected()
{
    ui->but_connect->setText("断开连接");
    ui->spin_localPort->setValue(m_tcpClient->localPort());
    qInfo() << QString("成功连接:%1 %2").arg(m_tcpClient->peerName()).arg(m_tcpClient->peerPort());
}

/**
 * @brief Socket已断开连接
 */
void TCPClient::on_disconnected()
{
    ui->but_connect->setText("连接");
    qInfo() <<QString("断开连接:%1 %2").arg(m_tcpClient->peerName()).arg(m_tcpClient->peerPort());
}

/**
 * @brief       QAbstractSocket 的状态发生变化
 * @param state QAbstractSocket::SocketState 不是注册的元类型,因此对于Qt::QueuedConnection信号槽连接类型,
 *              必须使用 Q_DECLARE_METATYPE() 和 qRegisterMetaType() 注册它。
 *              QAbstractSocket::UnconnectedState	0	套接字未连接。
 *              QAbstractSocket::HostLookupState	1	套接字正在查询主机。
 *              QAbstractSocket::ConnectingState	2	套接字开始建立连接。
 *              QAbstractSocket::ConnectedState	    3	新的连接已建立。
 *              QAbstractSocket::BoundState	        4	套接字已绑定到一个地址和端口。
 *              QAbstractSocket::ClosingState	    6	套接字即将关闭(数据可能仍在等待写入)。
 *              QAbstractSocket::ListeningState	    5	套接字仅限内部使用。
 */
void TCPClient::on_stateChanged(QAbstractSocket::SocketState state)
{
    qInfo() << "状态改变:"<< state;
}

/**
 * @brief        发生错误
 * @param error  错误异常信息,QAbstractSocket::SocketError 不是已注册的元类型,
 *               因此对于排队连接,您必须使用 Q_DECLARE_METATYPE() 和 qRegisterMetaType() 注册它。
 *               ConnectionRefusedError:连接被对等方拒绝(或超时)。
 *               RemoteHostClosedError:远程主机关闭了连接。请注意,客户端Socket将在发送远程关闭通知后关闭。
 *               HostNotFoundError:找不到主机地址。
 *               SocketAccessError:Socket操作失败,因为应用程序缺少所需的权限。
 *               SocketResourceError:本地系统资源不足(例如,Socket过多)。
 *               SocketTimeoutError:Socket操作超时。
 *               DatagramTooLargeError:数据报大于操作系统的限制。
 *               NetworkError:网络出现错误(例如,网现被拔出)。
 *               AddressInUseError:指定给 bind() 的地址已在使用中并设置为独占。
 *               SocketAddressNotAvailableError:指定给 bind() 的地址不属于主机。
 *               UnsupportedSocketOperationError:本地操作系统不支持请求的Socket操作(例如,缺乏 IPv6 支持)。
 *               ProxyAuthenticationRequiredError:Socket正在使用代理,并且代理需要身份验证。
 *               SslHandshakeFailedError:SSL/TLS 握手失败,因此连接被关闭(仅在 QSslSocket 中使用)
 *               UnfinishedSocketOperationError:上次尝试的操作尚未完成(仍在后台进行)。
 *               ProxyConnectionRefusedError:无法联系代理服务器,因为与该服务器的连接被拒绝
 *               ProxyConnectionClosedError:在与最终对等方的连接建立之前,与代理服务器的连接意外关闭
 *               ProxyConnectionTimeoutError:与代理服务器的连接超时或代理服务器在身份验证阶段停止响应。
 *               ProxyNotFoundError:未找到使用 setProxy()(或应用程序代理)设置的代理地址。
 *               ProxyProtocolError:与代理服务器的连接协商失败,因为无法理解来自代理服务器的响应。
 *               OperationError:当Socket处于不允许的状态时尝试进行操作。
 *               SslInternalError:正在使用的 SSL 库报告了一个内部错误。这可能是库安装错误或配置错误的结果。
 *               SslInvalidUserDataError:提供了无效数据(证书、密钥、密码等),其使用导致 SSL 库中出现错误。
 *               TemporaryError:发生临时错误(例如,操作会阻塞而Socket是非阻塞的)。
 *               UnknownSocketError:发生不明错误。
 */
void TCPClient::on_errorOccurred(QAbstractSocket::SocketError error)
{
    qWarning() << "出现异常:" <<error;
}

/**
 * @brief 接收数据
 */
void TCPClient::on_readyRead()
{
    QByteArray arr = m_tcpClient->readAll();

    if(arr.count() <= 0)
    {
        return;
    }
    ui->spin_recv->setValue(ui->spin_recv->value() + arr.count());
    if(ui->check_hexRecv->isChecked())
    {
        ui->text_recv->append(arr.toHex(' '));
    }
    else
    {
        ui->text_recv->append(arr);
    }
}

/**
 * @brief TCP连接
 */
void TCPClient::on_but_connect_clicked()
{
    if(!m_tcpClient) return;

    if(m_tcpClient->state() == QAbstractSocket::UnconnectedState)         // 判断连接状态
    {
        m_tcpClient->connectToHost(ui->line_peerAddress->text(), ui->spin_peerPort->value());
    }
    else
    {
#if 1   // 使用abort函数立即关闭套接字,丢弃写入缓冲区中的任何未决数据。
        m_tcpClient->abort();

#else   // 等待数据发送完成再关闭

#if 0
        m_tcpClient->disconnectFromHost();  // 不一定立即关闭,如果有待写入的数据等待写入,将进入 ClosingState状态 并等待所有数据写入完毕
#else
        m_tcpClient->close();               // 关闭 QIODevice并调用disconnectFromHost
#endif
        qDebug() <<"IO状态:" << m_tcpClient->isOpen();                 // 可通过这一行代码测试Close和disconnectFromHost的区别
        if (m_tcpClient->state() == QAbstractSocket::UnconnectedState) // 如果调用close后没有立即关闭则等待 1秒中
        {
            if(m_tcpClient->waitForDisconnected(1000))
            {
                qInfo() <<"关闭成功!";
            }
            else
            {
                qWarning() << "关闭失败:"<<m_tcpClient->error();
            }
        }
#endif
    }
}

/**
 * @brief 发送数据
 */
void TCPClient::on_but_send_clicked()
{
    if(!m_tcpClient) return;
    if(m_tcpClient->state() != QAbstractSocket::ConnectedState) return;  // 判断是否已连接

    QString str = ui->text_send->toPlainText();
#if 0
    QByteArray arr = str.toLocal8Bit();              // 根据编译器编码或者 QTextCodec::setCodecForLocale(codec);指定的编码方式将QString转换为QBytearray,一般为utf-8或者GBK,支持中文
#else
    QByteArray arr = str.toUtf8();                   // 指定以utf-8编码
#endif
    if(ui->check_hexSend->isChecked())               // 如果是16进制显示则转换
    {
        arr = QByteArray::fromHex(arr);
    }

    qint64 len = m_tcpClient->write(arr);
    if(len < 0)
    {
        qWarning() <<"发送失败!";
    }
    ui->spin_send->setValue(ui->spin_send->value() + len);
}

/**
 * @brief 清空接收区
 */
void TCPClient::on_but_clearRecv_clicked()
{
    ui->text_recv->clear();
    ui->spin_recv->setValue(0);
}

/**
 * @brief 清空发送区
 */
void TCPClient::on_but_clearSend_clicked()
{
    ui->text_send->clear();
    ui->spin_send->setValue(0);
}

/**
 * @brief           将发送区内容转换为十六进制显示
 * @param checked
 */
void TCPClient::on_check_hexSend_clicked(bool checked)
{
    QString value;
    if(checked)
    {
        value = ui->text_send->toPlainText().toUtf8().toHex(' ');
    }
    else
    {
        value = QByteArray::fromHex(ui->text_send->toPlainText().toUtf8());
    }
    ui->text_send->setText(value);
}

/**
 * @brief          将接收区内容转换为十六进制显示
 * @param checked
 */
void TCPClient::on_check_hexRecv_clicked(bool checked)
{
    QString value;
    if(checked)
    {
        value = ui->text_recv->toPlainText().toUtf8().toHex(' ');             // 01 02 03 1a 格式显示
    }
    else
    {
        value = QByteArray::fromHex(ui->text_recv->toPlainText().toUtf8());   // 123abc格式显示
    }
    ui->text_recv->setText(value);
}



2、源代码✌️

gitee
github

✋ 👐 ☝️ 👇 👈 👉 🙌 🙏 👆 👏 💪 🤘 🖕骚操作走起

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
您好!对于STM32F407和ETH LWIP,我可以为您提供一些关于TCP client客户端的开发方面的信息。 首先,您需要使用STM32CubeMX软件来配置STM32F407的ETH和LWIP库。在STM32CubeMX中,您可以配置以太网接口和选择使用LWIP协议栈。确保在配置期间启用TCP/IP协议。 一旦配置完成并生成代码,您可以在生成的代码中找到LWIP库的API函数以及以太网驱动程序的相关函数。在这里,您将能够设置和管理TCP连接。 以下是一个简单的TCP客户端示例代码,用于向服务器发送数据: ```c #include "lwip/api.h" #define SERVER_IP "192.168.0.100" #define SERVER_PORT 8080 void tcp_client_task(void *arg) { struct netconn *conn; err_t err; // 创建TCP连接 conn = netconn_new(NETCONN_TCP); if (conn != NULL) { ip_addr_t server_addr; // 设置服务器IP地址和端口 IP4_ADDR(&server_addr, 192, 168, 0, 100); // 连接服务器 err = netconn_connect(conn, &server_addr, SERVER_PORT); if (err == ERR_OK) { const char *data = "Hello, server!"; struct netbuf *send_buf; // 创建发送数据包 send_buf = netbuf_new(); if (send_buf != NULL) { // 将数据添加到发送数据包中 netbuf_ref(send_buf, data, strlen(data)); // 发送数据包 err = netconn_send(conn, send_buf); // 释放发送数据包 netbuf_delete(send_buf); } } // 关闭连接 netconn_close(conn); netconn_delete(conn); } vTaskDelete(NULL); } ``` 请注意,此示例代码仅用于演示目的,您可能需要根据您的具体需求进行修改。另外,还要确保正确初始化LWIP协议栈和以太网接口。 希望这些信息对您有所帮助!如果您有任何进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mahuifa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值