QT网络编程之如何使用QT6框架QTcpServer和QTcpSocket网络编程实现变长数据包收发:以用户注册和登录为实例讲解
QT网络编程之如何使用QT6框架QTcpServer和QTcpSocket网络编程实现变长数据包收发:以用户注册和登录为实例讲解
简介
本文将介绍如何使用QT6框架QTcpServer和QTcpSocket进行网络编程实现变长数据包收发,本文以用户注册和用户登录这两个基础功能作为实例来介绍QT6网络编程。
目录
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网络编程。
如果您认为这篇文章对您有所帮助,请您一定立即点赞+喜欢+收藏,本文作者将能从您的点赞+喜欢+收藏中获取到创作新的好文章的动力。如果您认为作者写的文章还有一些参考价值,您也可以关注这篇文章的作者。