QT6框架QTcpServer和QTcpSocket网络编程实现变长数据包收发

2023-05-2409:40:19后端程序开发Comments952 views字数 13043阅读模式

本文将介绍如何使用QT6框架QTcpServer和QTcpSocket进行网络编程实现变长数据包收发,本文以用户注册和用户登录这两个基础功能作为实例来介绍QT6网络编程。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

目录文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

QT网络服务器QTcpServer文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

QT网络客户端QTcpSocket文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

消息数据包类型定义文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

服务器实现文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

客户端实现文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

套接字基础类ButianyunTcpSocket文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

测试运行文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

正文文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

QT网络服务器QTcpServer

QTcpServer用于实现TCP服务器。通常可以重写incommingConnection这个虚函数,目的是为了创建一个自定义类型的网络套接字类型。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

[virtual protected] void QTcpServer::incomingConnection(qintptr socketDescriptor)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

创建这个类型的对象实例之后调用listen()函数开始接收网络客户端的连接。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

QT网络客户端QTcpSocket

无论是TCP服务器还是TCP客户端,都会使用这个类型或者派生类型作为socket套接字包装类型。
这个类型提供了setSocketDescriptor()函数将一个套接字描述符和一个包装类对象实例关联上。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

[virtual] bool QAbstractSocket::setSocketDescriptor(qintptr socketDescriptor, QAbstractSocket::SocketState socketState = ConnectedState, QIODeviceBase::OpenMode openMode = ReadWrite)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

同时这个类型的基类QAbstractSocket类型提供了一系列的函数来获取本地IP地址和端口以及远程IP地址和端口。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

QT6框架QTcpServer和QTcpSocket网络编程实现变长数据包收发

QAbstractSocket类型提供的获取本地和远程IP地址和端口的函数文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

QAbstactSocket类型还提供了一系列的信号,可以获知什么时候连接建立完成,以及什么时候连接断开了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

QT6框架QTcpServer和QTcpSocket网络编程实现变长数据包收发

QAbstractSocket类型的基类QIODevice类型提供了一系列的信号,从而可以获知什么时候有新的数据可读,以及已经写了多少数据。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

[signal] void QIODevice::readyRead()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

[signal] void QIODevice::bytesWritten(qint64 bytes)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

QIODevice类型还提供了一些IO读写函数。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

消息数据包类型定义

现在的需求是实现用户注册和用户登录两个功能。这里的C++代码基本上都是自注释的,还是很好理解。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

作为一个示例程序,这里只考虑相对安全的局域网的比较正常的情况,没有考虑公网情况下的一些复杂情况和异常情况,比如数据包重放攻击,异常数据包等。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

#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

服务器实现

主程序:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

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

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

    return a.exec();
}

ButianyunTcpServer类型:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

这个类型从QTcpServer类型派生出了一个新的类型,使得可以创建自定义的支持特定功能的QTcpSocket的派生类型。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

类型定义如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

#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

类型实现如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

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

}

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

ButianyunTcpServerSocet类型文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

类型定义如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

#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

类型实现如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

#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.";
}

客户端实现

主程序:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

这里只测试了几种情况,包括正常注册、正常登录,异常登录,慢速登录等,没有去考虑不完整数据包攻击以及只连接不发送数据包等非正常情况。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html


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类型定义:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

#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

类型实现:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

#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

这个类型实现了对网路编程中常见的半包和粘包的处理,使得可以支持变长数据包的传输,同时也是可以支持文件传输。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

对半包和粘包的介绍请参考:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

QT网络编程之什么是TCP编程中的半包和粘包问题以及具体怎么解决?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

类型定义如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

#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

类型实现:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

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";
}

测试运行

服务器:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

QT6框架QTcpServer和QTcpSocket网络编程实现变长数据包收发

QT6 TCP服务器运行结果文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

客户端:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

QT6框架QTcpServer和QTcpSocket网络编程实现变长数据包收发

QT6 TCP客户端运行结果文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

总结文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

本文介绍了如何使用QT6框架QTcpServer和QTcpSocket进行网络编程实现变长数据包收发,本文还以用户注册和用户登录这两个基础功能作为实例介绍了QT6网络编程。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/41802.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/bc/41802.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定