Qt网络编程-TcpServer入门Demo(2)

11 篇文章 12 订阅

Qt网络编程-TcpServer入门Demo(2)👌

更多精彩内容
👉个人内容分类汇总 👈
简易版
👉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 Server🔗

使用QTcpServer实现TCP Server,十分详细的入门Demo
  • 支持打开多个TCP Server窗口;👍
  • 支持同时连接多个TcpClient,将连接的TcpClient添加进通信列表;
  • 支持自动移除通信列表中断开连接的TcpClient;
  • 支持一对多进行数据通信,或通过在连接TcpClient列表中勾选需要通信的对象进行数据通信;
  • 支持频繁断开连接大量的QTcpSocket并不存在内存泄漏;
  • 可选择是否以16进制字符串形式显示发送、接收的数据;👍
  • 自动统计发送数据的总字节大小、接收数据的总字节大小;👌
  • 判断TCP Socket状态变化;✌️
  • 判断TCP Server各类异常状态;✋
  • 详细说明close、disconnectFromHost、abort三种断开连接的方式和优缺点; 👐
  • 代码注释详细,便于学习阅读。 👇
  • 注意:如果程序需要频繁断开连接,那就需要考虑内存泄漏问题
    • QTcpServer存在一些内存泄漏问题,如果没有通过nextPendingConnection返回所有的的QTcpSocket并释放,将只有在QTcpServer释放时才会统一释放已连接的QTcpSocket;
    • 如果程序需要频繁断开连接,解决这个内存泄漏问题就需要通过hasPendingConnections函数判断是否有未返回的已连接QTcpSocket,如果有就调用nextPendingConnection返回并释放。

1.1 TcpServer流程图🔀

在这里插入图片描述

1.2 实现效果🙉

在这里插入图片描述

1.3 关键信号🎻

信号说明
newConnection监听到新的客户端连接
acceptError当接受新连接导致错误时,会发出此信号

1.4 关键函数 💍

函数名说明
setMaxPendingConnections设置最大允许连接数,不设置的话默认为30
isListening判断是否正在监听连接
listen绑定服务器侦听地址和端口上的传入连接。如果端口为0,则会自动选择一个端口。(相当于bind + listen )
hasPendingConnections判断是否有已连接的QTcpSocket需要返回
nextPendingConnection返回已连接的QTcpSocket,如果有已连接的QTcpSocket没调用这个函数返回,将在QTcpServer释放时会自动统一释放(所以存在一定的内存泄漏问题)
close关闭服务器。 服务器将不再监听传入连接

1.5 主要代码🌈

  • .pro.pri文件中添加QT += network
#if defined(_MSC_VER) && (_MSC_VER >= 1600)
# pragma execution_character_set("utf-8")
#endif
#include "tcpserver.h"
#include "ui_tcpserver.h"
#include <QAbstractSocket>
#include <QDebug>
#include <QListWidgetItem>
#include <qhostinfo.h>

#include "share.h"

using namespace Share;

TCPServer::TCPServer(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::TCPServer)
{
    ui->setupUi(this);
    this->setWindowTitle("TCP服务端Demo");

    init();
    connectSlots();
}

TCPServer::~TCPServer()
{
#ifdef QT_DEBUG
    qDebug() <<"~TCPServer()";
#endif
    delete ui;
}


void TCPServer::init()
{
    m_tcpServer = new QTcpServer(this);
    m_tcpServer->setMaxPendingConnections(30);     // 设置最大允许连接数,不设置的话默认为30(如果设置过大就需要考虑内存泄漏问题)
    ui->line_localAddress->setText(getLocalIP());
}

void TCPServer::connectSlots()
{
    connect(m_tcpServer, &QTcpServer::newConnection, this, &TCPServer::on_newConnection);    // 监听新的客户端连接
    connect(m_tcpServer, &QTcpServer::acceptError, this, &TCPServer::on_acceptError);        // 当接受新连接导致错误时,会发出此信号
}


void TCPServer::on_newConnection()
{
    while (m_tcpServer->hasPendingConnections())
    {
        qDebug() <<m_tcpServer->hasPendingConnections();
        QTcpSocket* tcpSocket = m_tcpServer->nextPendingConnection();      // 存在内存泄漏,最好使用时通过hasPendingConnections判断是否有未返回的连接

        qDebug() <<m_tcpServer->hasPendingConnections();
        if(tcpSocket)
        {
            m_tcpClients.append(tcpSocket);
        }
        connect(tcpSocket, &QTcpSocket::disconnected, this, &TCPServer::on_disconnected);        // 断开连接
        connect(tcpSocket, &QTcpSocket::readyRead, this, &TCPServer::on_readyRead);

        QString strPeer = QString("%1 %2").arg(tcpSocket->peerAddress().toString()).arg(tcpSocket->peerPort());
        strPeer.remove("::ffff:");
        addPeer(strPeer);
    }
}

/**
 * @brief               处理连接中的异常详细
 * @param socketError
 */
void TCPServer::on_acceptError(QAbstractSocket::SocketError socketError)
{
    qWarning() << QString("TcpServer异常:%1").arg(socketError);
}

/**
 * @brief       添加已连接的目标信息
 * @param peer  IP + 端口
 */
void TCPServer::addPeer(const QString &peer)
{
    QListWidgetItem *item = new QListWidgetItem(ui->listWidget);
    QCheckBox* checkbox = new QCheckBox();          // 添加复选框
    checkbox->setText(peer);
    //设置item的高度
    item->setSizeHint(QSize(0,20));
    ui->listWidget->addItem(item);
    ui->listWidget->setItemWidget(item, checkbox);
}

/**
 * @brief 开始监听连接
 */
void TCPServer::on_but_connect_clicked()
{
    if(!m_tcpServer) return;

    if(!m_tcpServer->isListening())          // 判断是否正在监听连接
    {
        // 告诉服务器侦听地址和端口上的传入连接。如果端口为0,则会自动选择一个端口。
        // 如果地址是QHostAddress::Any,服务器将监听所有网络接口。
        bool ret = m_tcpServer->listen(QHostAddress::Any, ui->spin_localPort->value());
        if(ret)
        {
            ui->but_connect->setText("停止");
        }
        else
        {
            qDebug() << QString("tcpServer绑定监听IP、端口失败:%1 %2").arg(m_tcpServer->errorString()).arg(m_tcpServer->serverError());
        }
    }
    else
    {
        m_tcpServer->close();
        ui->but_connect->setText("开始监听");
        for(int i = m_tcpClients.count() - 1; i >= 0 ; i--)          // 关闭所有连接的TCP Client
        {
            m_tcpClients.at(i)->abort();
        }
    }
}

/**
 * @brief Socket  已断开连接
 */
void TCPServer::on_disconnected()
{
    for(int i = 0; i < m_tcpClients.count(); i++)
    {
        if(m_tcpClients.at(i)->state() != QAbstractSocket::ConnectedState)       // 未连接
        {
            disconnect(m_tcpClients.at(i), &QTcpSocket::disconnected, this, &TCPServer::on_disconnected);
            disconnect(m_tcpClients.at(i), &QTcpSocket::readyRead, this, &TCPServer::on_readyRead);
            m_tcpClients.takeAt(i)->deleteLater();       // 移除已经断开连接的Client(注意这里不能使用delete,否则在vs中会报错)
            QListWidgetItem * item = ui->listWidget->item(i);
            ui->listWidget->removeItemWidget(item);
            delete item;                    // 不加这一行 listwidget的count不会变化
            item = nullptr;
        }
    }
}

/**
 * @brief 接收数据
 */
void TCPServer::on_readyRead()
{
    for(int i = 0; i < ui->listWidget->count(); i++)
    {
        if(m_tcpClients.at(i)->bytesAvailable() <= 0) continue;;       // 如果无可读数据则返回

        QByteArray arr = m_tcpClients.at(i)->readAll();                // 读取数据
        QCheckBox* checkBox = (QCheckBox*)ui->listWidget->itemWidget(ui->listWidget->item(i));
        if(checkBox->isChecked())                                      // 勾选的已连接Client则显示接收的信息
        {
            if(arr.count() <= 0)
            {
                continue;
            }
            ui->spin_recv->setValue(ui->spin_recv->value() + arr.count());  // 统计接收的数据总大小
            QString strPeer = checkBox->text();
            if(ui->check_hexRecv->isChecked())
            {
                ui->text_recv->append(QString("[%1] ").arg(strPeer) + arr.toHex(' '));
            }
            else
            {
                ui->text_recv->append(QString("[%1] ").arg(strPeer) + arr);
            }
        }
    }
}

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

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

/**
 * @brief          将接收区内容转换为十六进制显示
 * @param checked
 */
void TCPServer::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);
}

/**
 * @brief           将发送区内容转换为十六进制显示
 * @param checked
 */
void TCPServer::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  发送数据
 */
void TCPServer::on_but_send_clicked()
{
    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 = sendData(arr);
    if(len < 0)
    {
        qWarning() <<"发送失败!";
    }
    ui->spin_send->setValue(ui->spin_send->value() + len);
}

/**
 * @brief       向所有已连接并且勾选的Client发送数据
 * @param data
 * @return      返回发送数据的最大长度
 */
qint64 TCPServer::sendData(const QByteArray &data)
{
    qint64 len = 0;
    for(int i = 0; i < ui->listWidget->count(); i++)
    {
        QCheckBox* checkBox = (QCheckBox*)ui->listWidget->itemWidget(ui->listWidget->item(i));
        if(checkBox->isChecked())                                      // 勾选的已连接Client则显示接收的信息
        {
             qint64 ret = m_tcpClients.at(i)->write(data);
             if(ret > len)
             {
                 len = ret;
             }
        }
    }
    return len;
}

2、源代码✌️

gitee
github

接着奏乐,接着舞🙋 👰 🙎 🙍 🙇 💑 💆 💇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

mahuifa

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

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

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

打赏作者

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

抵扣说明:

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

余额充值