QT网络编程之如何使用QT6框架QTcpServer和QTcpSocket网络编程实现变长数据包收发:以用户注册和登录为实例讲解

QT网络编程之如何使用QT6框架QTcpServer和QTcpSocket网络编程实现变长数据包收发:以用户注册和登录为实例讲解

简介

本文将介绍如何使用QT6框架QTcpServer和QTcpSocket进行网络编程实现变长数据包收发,本文以用户注册和用户登录这两个基础功能作为实例来介绍QT6网络编程。

【QT免费公开课】您可以到这里观看大量的QT视频课程

【QT付费视频课程】QT网络编程概念与实践

【QT付费视频课程】如何使用Qt WebEngine开发一款Web浏览器软件?

【QT付费视频课程】QT核心模块原理与源码分析

【QT付费视频课程】QT Widgets应用程序优化

【QT付费视频课程】QT QML应用程序优化

目录

QT网络服务器QTcpServer

QT网络客户端QTcpSocket

消息数据包类型定义

服务器实现

客户端实现

套接字基础类ButianyunTcpSocket

测试运行

正文

QT网络服务器QTcpServer

QTcpServer用于实现TCP服务器。通常可以重写incommingConnection这个虚函数,目的是为了创建一个自定义类型的网络套接字类型。

[virtual protected] void QTcpServer::incomingConnection(qintptr socketDescriptor)

创建这个类型的对象实例之后调用listen()函数开始接收网络客户端的连接。

QT网络客户端QTcpSocket

无论是TCP服务器还是TCP客户端,都会使用这个类型或者派生类型作为socket套接字包装类型。
这个类型提供了setSocketDescriptor()函数将一个套接字描述符和一个包装类对象实例关联上。

[virtual] bool QAbstractSocket::setSocketDescriptor(qintptr socketDescriptor, QAbstractSocket::SocketState socketState = ConnectedState, QIODeviceBase::OpenMode openMode = ReadWrite)

同时这个类型的基类QAbstractSocket类型提供了一系列的函数来获取本地IP地址和端口以及远程IP地址和端口。

QAbstractSocket类型提供的获取本地和远程IP地址和端口的函数
QAbstactSocket类型还提供了一系列的信号,可以获知什么时候连接建立完成,以及什么时候连接断开了。

QAbstractSocket类型的基类QIODevice类型提供了一系列的信号,从而可以获知什么时候有新的数据可读,以及已经写了多少数据。

[signal] void QIODevice::readyRead()

[signal] void QIODevice::bytesWritten(qint64 bytes)

QIODevice类型还提供了一些IO读写函数。

消息数据包类型定义

现在的需求是实现用户注册和用户登录两个功能。这里的C++代码基本上都是自注释的,还是很好理解。

作为一个示例程序,这里只考虑相对安全的局域网的比较正常的情况,没有考虑公网情况下的一些复杂情况和异常情况,比如数据包重放攻击,异常数据包等。

#ifndef BUTIANYUNMESSAGE_H
#define BUTIANYUNMESSAGE_H

#if defined(__cplusplus)
extern "C" {
#endif


enum ButianyunMessageType
{
    BMT_InvalidMessage,
    BMT_RegisterRequestMessage,
    BMT_RegisterResponseMessage,
    BMT_LoginRequestMessage,
    BMT_LoginResponseMessage
};

struct ButianyunMessageHeader
{
    unsigned short type;
    unsigned short result;
    unsigned int   size;
};

struct  ButianyunRegisterRequestMessage
{
    ButianyunMessageHeader header;
    char userid[32];
    char password[32];
};

struct  ButianyunRegisterResponseMessage
{
    ButianyunMessageHeader header;
};

struct  ButianyunLoginRequestMessage
{
    ButianyunMessageHeader header;
    char userid[32];
    char password[32];
};

struct  ButianyunLoginResponseMessage
{
    ButianyunMessageHeader header;
};


#if defined(__cplusplus)
}
#endif

#define  BUTIANYUN_MESSAGE_HEADER_SIZE    sizeof(ButianyunMessageHeader)


#endif // BUTIANYUNMESSAGE_H

服务器实现

主程序:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    ButianyunTcpServer server;
    server.listen(QHostAddress("127.0.0.1"), 8888);

    return a.exec();
}

ButianyunTcpServer类型:

这个类型从QTcpServer类型派生出了一个新的类型,使得可以创建自定义的支持特定功能的QTcpSocket的派生类型。

类型定义如下:

#ifndef BUTIANYUNTCPSERVER_H
#define BUTIANYUNTCPSERVER_H



class ButianyunTcpServer : public QTcpServer
{
    Q_OBJECT
public:
    explicit ButianyunTcpServer(QObject *parent = nullptr);

    void incomingConnection(qintptr socket) override;

};

#endif // BUTIANYUNTCPSERVER_H


类型实现如下:

ButianyunTcpServer::ButianyunTcpServer(QObject *parent)
    : QTcpServer{parent}
{

}

void ButianyunTcpServer::incomingConnection(qintptr socket)
{
    ButianyunTcpSocket* connection = new ButianyunServerTcpSocket(this);
    connection->setSocketDescriptor(socket);
    addPendingConnection(connection);
}

ButianyunTcpServerSocet类型

类型定义如下:

#ifndef BUTIANYUNSERVERTCPSOCKET_H
#define BUTIANYUNSERVERTCPSOCKET_H


class ButianyunServerTcpSocket : public ButianyunTcpSocket
{
    Q_OBJECT
public:
    explicit ButianyunServerTcpSocket(QObject *parent = nullptr);

protected:
    void handle_message(QByteArray& data, const ButianyunMessageHeader& header) override;

private:
    void handle_message_register_request(QByteArray& data);
    void handle_message_login_request(QByteArray& data);
};

#endif // BUTIANYUNSERVERTCPSOCKET_H


类型实现如下:

#include "butianyunservertcpsocket.h"
#include "ButianyunMessage.h"

ButianyunServerTcpSocket::ButianyunServerTcpSocket(QObject *parent)
    : ButianyunTcpSocket{parent}
{

}

void ButianyunServerTcpSocket::handle_message(QByteArray& data, const ButianyunMessageHeader& header)
{
    switch (header.type)
    {
    case BMT_RegisterRequestMessage:
        handle_message_register_request(data);
        break;

    case BMT_LoginRequestMessage:
        handle_message_login_request(data);
        break;

    default:
        break;
    }
}

void ButianyunServerTcpSocket::handle_message_register_request(QByteArray& data)
{
    const ButianyunRegisterRequestMessage* message = reinterpret_cast<const ButianyunRegisterRequestMessage*>(data.constData());
    qInfo() << "register:" << message->userid << ", " << message->password;
    ButianyunRegisterResponseMessage response;
    response.header.type = BMT_RegisterResponseMessage;
    response.header.result = 0;
    response.header.size = sizeof(ButianyunRegisterResponseMessage);
    quint64 len = write(reinterpret_cast<const char*>(&response), response.header.size);
    qInfo() << "register response sent.";
}

void ButianyunServerTcpSocket::handle_message_login_request(QByteArray& data)
{
    const ButianyunLoginRequestMessage* message = reinterpret_cast<const ButianyunLoginRequestMessage*>(data.constData());
    qInfo() << "login:" << message->userid << ", " << message->password;
    ButianyunLoginResponseMessage response;
    response.header.type = BMT_LoginResponseMessage;
    response.header.result = 0;
    response.header.size = sizeof(ButianyunLoginResponseMessage);
    quint64 len = write(reinterpret_cast<const char*>(&response), response.header.size);
    qInfo() << "login response sent.";
}

客户端实现

主程序:

这里只测试了几种情况,包括正常注册、正常登录,异常登录,慢速登录等,没有去考虑不完整数据包攻击以及只连接不发送数据包等非正常情况。

void register_client()
{
    ButianyunClientSocket* client = new ButianyunClientSocket(qApp);

    QObject::connect(client, &ButianyunClientSocket::connected, client, [client]{
        qInfo() << "connected to server.";
        qInfo() << "sending register request ...";

        ButianyunRegisterRequestMessage msg;
        memset(&msg, 0, sizeof(ButianyunRegisterRequestMessage));
        msg.header.type = BMT_RegisterRequestMessage;
        msg.header.result = 0;
        msg.header.size = sizeof(ButianyunRegisterRequestMessage);
        strncpy(msg.userid, "butianyun", sizeof("butianyun"));
        strncpy(msg.password, "butianyun", sizeof("butianyun"));
        qint64 len = client->write(reinterpret_cast<const char*>(&msg), sizeof(ButianyunRegisterRequestMessage));
        if (len < sizeof(ButianyunRegisterRequestMessage))
        {
            qInfo() << "Failed to send regiser request:" << len;
            client->close();
            client->deleteLater();
            return;
        }
        qInfo() << "register request ok";
    });

    QObject::connect(client, &ButianyunClientSocket::sig_message_result, client, [client](unsigned short type, unsigned short result) {
        if (BMT_RegisterResponseMessage == type && 0 == result)
        {
            qInfo() << "sending login request ...";

            ButianyunLoginRequestMessage msg;
            memset(&msg, 0, sizeof(ButianyunLoginRequestMessage));
            msg.header.type = BMT_LoginRequestMessage;
            msg.header.result = 0;
            msg.header.size = sizeof(ButianyunLoginRequestMessage);
            strncpy(msg.userid, "butianyun", sizeof("butianyun"));
            strncpy(msg.password, "butianyun", sizeof("butianyun"));
            qint64 len = client->write(reinterpret_cast<const char*>(&msg), sizeof(ButianyunLoginRequestMessage));
            if (len < sizeof(ButianyunLoginRequestMessage))
            {
                qInfo() << "Failed to send login request:" << len;
                client->close();
                client->deleteLater();
                return;
            }
            qInfo() << "login request ok";
        }
    });

    client->connectToHost(QHostAddress("127.0.0.1"), 8888);
}

void login_client()
{
    ButianyunClientSocket* client = new ButianyunClientSocket(qApp);

    QObject::connect(client, &ButianyunClientSocket::connected, client, [client]{
        qInfo() << "connected to server.";
        qInfo() << "sending login request ...";

        ButianyunLoginRequestMessage msg;
        memset(&msg, 0, sizeof(ButianyunLoginRequestMessage));
        msg.header.type = BMT_LoginRequestMessage;
        msg.header.result = 0;
        msg.header.size = sizeof(ButianyunLoginRequestMessage);
        strncpy(msg.userid, "butianyun", sizeof("butianyun"));
        strncpy(msg.password, "butianyun", sizeof("butianyun"));
        qint64 len = client->write(reinterpret_cast<const char*>(&msg), sizeof(ButianyunLoginRequestMessage));
        if (len < sizeof(ButianyunLoginRequestMessage))
        {
            qInfo() << "Failed to send login request:" << len;
            client->close();
            client->deleteLater();
            return;
        }
        qInfo() << "login request ok";
    });

    client->connectToHost(QHostAddress("127.0.0.1"), 8888);
}

void slow_client()
{
    ButianyunClientSocket* client = new ButianyunClientSocket(qApp);

    QObject::connect(client, &ButianyunClientSocket::connected, client, [client]{
        qInfo() << "connected to server.";
        qInfo() << "sending slow login request ...";

        static ButianyunLoginRequestMessage msg;
        memset(&msg, 0, sizeof(ButianyunLoginRequestMessage));
        msg.header.type = BMT_LoginRequestMessage;
        msg.header.result = 0;
        msg.header.size = sizeof(ButianyunLoginRequestMessage);
        strncpy(msg.userid, "butianyun", sizeof("butianyun"));
        strncpy(msg.password, "butianyun", sizeof("butianyun"));

        QTimer* timer = new QTimer(client);
        timer->setTimerType(Qt::CoarseTimer);
        timer->setInterval(1000);
        timer->setSingleShot(false);
        static int i = 0;

        QObject::connect(timer, &QTimer::timeout, client, [client, timer] {
            const char* p = reinterpret_cast<const char*>(&msg);
            qint64 len = client->write(p + i, 1);
            if (len != 1)
            {
                qInfo() << "Failed to send login request:" << len << " : " << i;
                timer->stop();
                timer->deleteLater();
                client->close();
                client->deleteLater();
                return;
            }
            qInfo() << i << ": 1 byte of login message sent.";

            i++;
            if (i == sizeof(ButianyunLoginRequestMessage))
            {
                qInfo() << "all bytes of login message sent.";
                timer->stop();
                timer->deleteLater();
            }
        });

        timer->start();
    });



    client->connectToHost(QHostAddress("127.0.0.1"), 8888);
}


void abnormal_client()
{
    ButianyunClientSocket* client = new ButianyunClientSocket(qApp);

    QObject::connect(client, &ButianyunClientSocket::connected, client, [client]{
        qInfo() << "connected to server.";
        qInfo() << "sending login request ...";

        ButianyunLoginRequestMessage msg;
        memset(&msg, 0, sizeof(ButianyunLoginRequestMessage));
        msg.header.type = BMT_LoginRequestMessage;
        msg.header.result = 0;
        msg.header.size = 5; //invalid packet!
        strncpy(msg.userid, "butianyun", sizeof("butianyun"));
        strncpy(msg.password, "butianyun", sizeof("butianyun"));
        qint64 len = client->write(reinterpret_cast<const char*>(&msg), sizeof(ButianyunLoginRequestMessage));
        if (len < sizeof(ButianyunLoginRequestMessage))
        {
            qInfo() << "Failed to send login request:" << len;
            client->close();
            client->deleteLater();
            return;
        }
        qInfo() << "login request ok";
    });

    client->connectToHost(QHostAddress("127.0.0.1"), 8888);
}


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

   register_client();
//   login_client();
//   slow_client();
//   abnormal_client();

    return a.exec();
}

ButianyunClientSocket类型定义:

#ifndef BUTIANYUNCLIENTSOCKET_H
#define BUTIANYUNCLIENTSOCKET_H

#include "butianyuntcpsocket.h"



class ButianyunClientSocket : public ButianyunTcpSocket
{
    Q_OBJECT
public:
    explicit ButianyunClientSocket(QObject *parent = nullptr);

signals:
    void sig_message_result(unsigned short type, unsigned short result);

protected:
    void handle_message(QByteArray& data, const ButianyunMessageHeader& header) override;

private:
    void handle_message_register_response(QByteArray& data);
    void handle_message_login_response(QByteArray& data);
};

#endif // BUTIANYUNCLIENTSOCKET_H

类型实现:

#include "butianyunclientsocket.h"
#include "ButianyunMessage.h"

ButianyunClientSocket::ButianyunClientSocket(QObject *parent)
    : ButianyunTcpSocket{parent}
{

}


void ButianyunClientSocket::handle_message(QByteArray& data, const ButianyunMessageHeader& header)
{
    switch (header.type)
    {
    case BMT_RegisterResponseMessage:
        handle_message_register_response(data);
        break;

    case BMT_LoginResponseMessage:
        handle_message_login_response(data);
        break;

    default:
        break;
    }
}

void ButianyunClientSocket::handle_message_register_response(QByteArray& data)
{
    const ButianyunRegisterResponseMessage* message = reinterpret_cast<const ButianyunRegisterResponseMessage*>(data.constData());
    qInfo() << "register result:" <<  (0 == message->header.result ? "OK" : "FAILED");
    emit sig_message_result(message->header.type, message->header.result);
}

void ButianyunClientSocket::handle_message_login_response(QByteArray& data)
{
    const ButianyunLoginResponseMessage* message = reinterpret_cast<const ButianyunLoginResponseMessage*>(data.constData());
    qInfo() << "login result:" <<  (0 == message->header.result ? "OK" : "FAILED");
    emit sig_message_result(message->header.type, message->header.result);
}

套接字基础类ButianyunTcpSocket

这个类型实现了对网路编程中常见的半包和粘包的处理,使得可以支持变长数据包的传输,同时也是可以支持文件传输。

对半包和粘包的介绍请参考:

QT网络编程之什么是TCP编程中的半包和粘包问题以及具体怎么解决?

类型定义如下:

#ifndef BUTIANYUNTCPSOCKET_H
#define BUTIANYUNTCPSOCKET_H


class ButianyunMessageHeader;


class ButianyunTcpSocket : public QTcpSocket
{
    Q_OBJECT
public:
    explicit ButianyunTcpSocket(QObject *parent = nullptr);

protected:
    virtual void handle_message(QByteArray& data, const ButianyunMessageHeader& header);

private slots:
    void slot_readReady();

private:
    int total_size;
    QByteArray bytes;
};

#endif // BUTIANYUNTCPSOCKET_H

类型实现:

ButianyunTcpSocket::ButianyunTcpSocket(QObject *parent)
    : QTcpSocket{parent}
    , total_size(0)
{
    connect(this, &ButianyunTcpSocket::readyRead, this, &ButianyunTcpSocket::slot_readReady);

    connect(this, &ButianyunTcpSocket::connected, this, [this]{
        qInfo() << "connected: local:" << localAddress().toString() << ":" << localPort()
                << " , remote:" << peerAddress().toString() << ":" << peerPort();
    });

    connect(this, &ButianyunTcpSocket::disconnected, this, [this]{
        qInfo() << "disconnected: local:" << localAddress().toString() << ":" << localPort()
                << " , remote:" << peerAddress().toString() << ":" << peerPort();
        deleteLater();
    });
}

void ButianyunTcpSocket::slot_readReady()
{
    QByteArray tempbytes = readAll();
    bytes.append(tempbytes);

    do
    {
        if (total_size == 0)
        {
            if (bytes.length() >= BUTIANYUN_MESSAGE_HEADER_SIZE)
            {
                const ButianyunMessageHeader* header =  reinterpret_cast<const ButianyunMessageHeader*>(bytes.constData());
                total_size = header->size;
                if (total_size < BUTIANYUN_MESSAGE_HEADER_SIZE)
                {
                    qInfo() << "Invalid packet! total_size:" << total_size;
                    close();
                    deleteLater();
                    return;
                }
            }
        }

        if (total_size >= BUTIANYUN_MESSAGE_HEADER_SIZE && bytes.length() >= total_size)
        {
            const ButianyunMessageHeader* header =  reinterpret_cast<const ButianyunMessageHeader*>(bytes.constData());
            handle_message(bytes, *header);
            bytes.remove(0, header->size);
            total_size = 0;
        }
    } while (0 == total_size && bytes.length() >= BUTIANYUN_MESSAGE_HEADER_SIZE);
}

void ButianyunTcpSocket::handle_message(QByteArray& data, const ButianyunMessageHeader& header)
{
    qInfo() << "Message:" << header.type << " NOT handled";
}

测试运行

服务器:
在这里插入图片描述

QT6 TCP服务器运行结果

客户端:
在这里插入图片描述

QT6 TCP客户端运行结果

总结

本文介绍了如何使用QT6框架QTcpServer和QTcpSocket进行网络编程实现变长数据包收发,本文还以用户注册和用户登录这两个基础功能作为实例介绍了QT6网络编程。

【QT免费公开课】您可以到这里观看大量的QT视频课程

【QT付费视频课程】QT网络编程概念与实践

【QT付费视频课程】如何使用Qt WebEngine开发一款Web浏览器软件?

【QT付费视频课程】QT核心模块原理与源码分析

【QT付费视频课程】QT Widgets应用程序优化

【QT付费视频课程】QT QML应用程序优化

如果您认为这篇文章对您有所帮助,请您一定立即点赞+喜欢+收藏,本文作者将能从您的点赞+喜欢+收藏中获取到创作新的好文章的动力。如果您认为作者写的文章还有一些参考价值,您也可以关注这篇文章的作者。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值