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网络编程。
如果您认为这篇文章对您有所帮助,请您一定立即点赞+喜欢+收藏,本文作者将能从您的点赞+喜欢+收藏中获取到创作新的好文章的动力。如果您认为作者写的文章还有一些参考价值,您也可以关注这篇文章的作者。
QT网络编程之如何使用QT6框架QTcpServer和QTcpSocket网络编程实现变长数据包收发:以用户注册和登录为实例讲解的更多相关文章
- Linux内核--网络栈实现分析(二)--数据包的传递过程(上)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7492423 更多请看专栏,地址 ...
- Linux内核--网络栈实现分析(二)--数据包的传递过程--转
转载地址http://blog.csdn.net/yming0221/article/details/7492423 作者:闫明 本文分析基于Linux Kernel 1.2.13 注:标题中的”(上 ...
- C# 实现的多线程异步Socket数据包接收器框架
转载自Csdn : http://blog.csdn.net/jubao_liang/article/details/4005438 几天前在博问中看到一个C# Socket问题,就想到笔者2004年 ...
- Linux内核网络数据包处理流程
Linux内核网络数据包处理流程 from kernel-4.9: 0. Linux内核网络数据包处理流程 - 网络硬件 网卡工作在物理层和数据链路层,主要由PHY/MAC芯片.Tx/Rx FIFO. ...
- 【Qt编程】基于Qt的词典开发系列<一>--词典框架设计及成品展示
去年暑假的时候,作为学习Qt的实战,我写了一个名为<我爱查词典>的词典软件.后来由于导师项目及上课等原因,时间不足,所以该软件的部分功能欠缺,性能有待改善.这学期重新拿出来看时,又有很多东 ...
- Qt网络编程QTcpServer和QTcpSocket的理解
前一段时间通过调试Qt源码,大致了解了Qt的事件机制.信号槽机制.毕竟能力和时间有限.有些地方理解的并不是很清楚. 开发环境:Linux((fedora 17),Qt版本(qt-everywhere- ...
- 已看1.熟练的使用Java语言进行面向对象程序设计,有良好的编程习惯,熟悉常用的Java API,包括集合框架、多线程(并发编程)、I/O(NIO)、Socket、JDBC、XML、反射等。[泛型]\
1.熟练的使用Java语言进行面向对象程序设计,有良好的编程习惯,熟悉常用的Java API,包括集合框架.多线程(并发编程).I/O(NIO).Socket.JDBC.XML.反射等.[泛型]\1* ...
- Qt编写气体安全管理系统2-界面框架
一.前言 整体框架包括两个部分,一部分是UI界面框架,比如一级二级导航菜单按钮整体布局等,一部分是项目框架,上一篇文章说的是项目框架,这一篇文章来说界面框架,Qt做界面非常快速和高效,尤其是提供了可视 ...
- 用C++实现网络编程---抓取网络数据包的实现方法
一般都熟悉sniffer这个工具,它可以捕捉流经本地网卡的所有数据包.抓取网络数据包进行分析有很多用处,如分析网络是否有网络病毒等异常数据,通信协议的分析(数据链路层协议.IP.UDP.TCP.甚至各 ...
- 人工神经网络简介和单层网络实现AND运算--AForge.NET框架的使用(五)
原文:人工神经网络简介和单层网络实现AND运算--AForge.NET框架的使用(五) 前面4篇文章说的是模糊系统,它不同于传统的值逻辑,理论基础是模糊数学,所以有些朋友看着有点迷糊,如果有兴趣建议参 ...
随机推荐
- [oeasy]python017_万行代码之梦_vim环境_复制粘贴
继续运行 回忆上次内容 上次 保存运行一条龙 :w|!python3 % 我想 再多输出 几行 增加一下 代码量 可以吗? 添加图片注释,不超过 140 字(可选) 代码量 在正常模式 ...
- [oeasy]python0094_视频游戏_双人网球_pong_atari_mos_6502_雅达利_米洛华
编码进化 回忆上次内容 上次 我们回顾了 微软之前的 比尔盖茨和保罗艾伦 mits 迎来的 是帮手 还是隐患? intel-8080 遇到了 mos-6502 底层硬件 驱动 游戏行业进化 不光是扑克 ...
- AT_arc154_b 题解
洛谷链接&Atcoder 链接 本篇题解为此题较简单做法及较少码量,并且码风优良,请放心阅读. 题目简述 给定两个长度为 \(n\) 的字符串 \(S,T\),定义一次操作可取出 \(S\) ...
- windows下mysql服务局域网访问配置
在局域网中访问本机(Windows)的MySQL服务器,需要确保MySQL服务器配置为允许远程访问,并且防火墙设置允许外部连接.以下是详细的步骤: 1. 修改MySQL配置文件允许远程访问 找到并编辑 ...
- Visual Studio中如何解决error C4996: 问题
error C4996: 'fopen': This function or variable may be unsafe. Consider using fopen_s instead. To di ...
- c++ 快速复习第一部份
去年有事无事学过一c++ ,,由于工作用不上,学来没有用,所以学得乱七八的,最近需要复习一下,因为最近想学习一下硬 件相关 第一 引用头文件和自定义头 #include <iostream& ...
- 我用Awesome-Graphs看论文:解读Pregel
Pregel论文:<Pregel: A System for Large-Scale Graph Processing> 上次向大家分享了论文图谱项目Awesome-Graphs的介绍文章 ...
- 【Java】线程池配置
先看JUC包自带的一个资源 线程池执行器: 初始化参数如下 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( corePo ...
- 【Spring】05 注解开发
环境搭建 配置ApplicationContext.xml容器文件[半注解实现] <?xml version="1.0" encoding="UTF-8" ...
- 外形最漂亮的人形机器人——通用机器人Apollo,设计为可以在任何任务和环境中与人类进行协作
视频地址: https://www.bilibili.com/video/BV11F4m1M7ph/