Qt——TCP UDP网络编程

news2024/11/18 3:00:27

目录

  • 前言
  • 正文
    • 一、TCP
    • 二、UDP
      • 1、基本流程
      • 2、必备知识
    • 三、代码层级
      • 1、UDP服务端
    • END、总结的知识与问题
      • 1、如何获取QByteArray中某一字节的数据,并将其转为十进制?
      • 2、如何以本年本月本日为基础,获取时间戳,而不以1970为基础?
      • 3、如何将一个四个字节组成的数拆分成1个字节一个字节的?
      • 4、如何对前面的所有字节进行异或校验?
      • 5、如何将QByteArray中的某个字节转为十六进制?
  • 参考

前言

恰好,有个项目需要用到UDP,之前使用比较多的是TCP,UDP的还是第一次搞,但是感觉流程也是差不多,甚至比TCP要更简单就行,这里就稍微做一下总结,写一下自己需要注意的点,以及从网上查找到的,比较有用的一些资料。本篇文章,主要偏向的还是UDP,TCP的话,大家可以稍微看下,当做过下眼。

正文

一、TCP

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的传输协议,它是OSI(Open System Interconnection,开放式系统互联)模型中的第四层协议,通常使用于网络中的应用层和传输层之间。

​ TCP建立连接主要就是三次握手、断开连接主要就是四次挥手。

三次握手的基本流程如下:
在这里插入图片描述
四次挥手的基本流程如下:

在这里插入图片描述

关于TCP Socket 的实现逻辑如下:

在这里插入图片描述
本篇文章关于TCP就点到为止了。下面就讲下UDP了。

二、UDP

1、基本流程

UDP(User Data Protocol),用户数据报协议,是一种简单轻量级、不可靠、面向数据报、无连接的传输层协议,可以应用在可靠性不是十分重要的场合,如短消息、广播信息等。

适用于以下几种情况:

A、网络数据大多为短消息。

B、拥有大量客户端

C、对数据安全性无特殊要求

D、网络负担非常重,但对响应速度要求高。

这部分内容,应该有一部分难点在于UDP的拆包上面,但由于目前解除的项目不需要拆包,就暂不考虑这个问题了。

UDP编程的流程:

在这里插入图片描述
在这里插入图片描述

2、必备知识

1、数据报的长度一般不少于512字节,每个数据报包含发送者和接收者的IP地址和端口等信息。
2、单播、广播、组播的知识

单播(unicast)模式:一个UDP客户端发出的数据报只发送到另一个指定地址和端口的UDP客户端,是一对一的数据传输。
广播(broadcast)模式:一个UDP客户端发出的数据报,在同一网络范围内其他所有的UDP客户端都可以收到。QUdpSocket支持IPv4广播。广播经常用于实现网络发现的协议。要获取广播数据只需在数据报中指定接收端地址为QHostAddress::Broadcast,一般的广播地址为255.255.255.255。
组播(multicast)模式:也称为多播。UDP客户端加入到另一组播IP地址指定的多播组,成员向多组播地址发送的数据报组内成员都可以接收到,类似于QQ群的功能。

  1. 单播(Unicast):单播是一种点对点的通信方式,其中一个发送方(源)向一个接收方(目标)发送数据。在单播通信中,数据从发送方经过网络传输到指定的接收方,其他设备不会接收到该数据。单播适用于需要将数据传输到特定设备或主机的场景,例如客户端-服务器通信。

  2. 广播(Broadcast):广播是一种一对多的通信方式,其中一个发送方向局域网中的所有设备发送数据。在广播通信中,数据从发送方通过网络传输到同一局域网中的所有设备。所有接收方都会接收到广播数据。广播适用于需要将数据传输到局域网中的所有设备的场景,例如局域网上的服务发现、网络广告等。

  3. 广播UDP与单播UDP的区别就是IP地址不同,广播使用广播地址255.255.255.255,将消息发送到在同一广播网络上的每个主机。值得强调的是:本地广播信息是不会被路由器转发。当然这是十分容易理解的,因为如果路由器转发了广播信息,那么势必会引起网络瘫痪。

    其实广播顾名思义,就是想局域网内所有的人说话,但是广播还是要指明接收者的端口号的,因为不可能接受者的所有端口都来收听广播。
    3、报文大小的限制与各系统的协议实现有关,但不得超过其下层 IP 协议规定的64KB
    4、所谓客户端进行广播,是指客户端向一个广播地址255.255.255.255 + 一个指定的服务端监听的端口号 进行发送数据。只要端口号是对的,那么服务端就会接收到数据。
    5、如何监听某个端口:netstat -ano | findstr 1024
    6、在这里插入图片描述

三、代码层级

1、UDP服务端

该代码是我实现的UDP服务端,只负责接收信息。
UDPServer.h

#ifndef UDPSERVER_H
#define UDPSERVER_H

#include <QObject>
#include <QUdpSocket>

class CUdpServer : public QObject
{
    Q_OBJECT

public:
    explicit CUdpServer(QObject *parent = nullptr);
    ~CUdpServer();
    bool OpenUdpServer(const int &_iPort);
protected:
    bool _WriteDatagram(const QString &_sIp, const int &_iPort, char *_pData, int _iLength);
    bool _ResponseFrame(const QString &_sIp, const int &_iPort, const QString &_sCmd, const QByteArray &_oArray);
    bool _HandleLoginFrame(const QString &_sIp, const int &_iPort, const QByteArray &_oArray);
private:
    void _Init();
    uint8_t _XorCheck(uint8_t *pbuf, uint32_t length);
    qint64 _GetNowDateTimeStampMs();

public slots:
    void on_readyRead();
private:
    QUdpSocket* m_pUdpServer = nullptr;
    bool m_bOpen = false;
    bool m_bDoing = false;
    int m_iElectricQuantity = 0;
};

#endif // UDPSERVER_H

UDPServer.cpp

#include "UdpServer.h"

#include <QNetworkDatagram>
#include <QDateTime>

#include <CommonFunc.h>
#include <KldLog.h>

CUdpServer::CUdpServer(QObject *parent):
    QObject(parent)
{
    _Init();
    connect(m_pUdpServer, &QUdpSocket::readyRead, this, &CUdpServer::on_readyRead);
}

CUdpServer::~CUdpServer()
{

}

bool CUdpServer::OpenUdpServer(const int &_iPort)
{
    bool bRet = false;
    if (false == m_bOpen)
    {
        bRet = m_pUdpServer->bind(QHostAddress::Any, _iPort);

        if (bRet)
        {
            m_bOpen = true;
        }
    }
    return bRet;
}

bool CUdpServer::_WriteDatagram(const QString &_sIp, const int &_iPort, char *_pData, int _iLength)
{
    bool bRet = false;
    QByteArray oOutputArray = QByteArray::fromRawData((char*)_pData, _iLength);
    qint64 iLen = m_pUdpServer->writeDatagram(oOutputArray, QHostAddress(_sIp), _iPort);
    return bRet;
}

bool CUdpServer::_ResponseFrame(const QString &_sIp, const int &_iPort, const QString &_sCmd, const QByteArray &_oArray)
{
    bool bRet = false;
    int iElectricQuantity = (int)_oArray.at(3);
    uchar cArray[16]= {0};
    cArray[0] = 0xAF;//帧头
    cArray[1] = 0x00;//设备类型
    cArray[2] = 0x00;//设备编号
    cArray[3] = 0x64;//电池电量
    qint64 iTimeStamp = _GetNowDateTimeStampMs();

    quint8 byte1 = (iTimeStamp >> 24) & 0xFF;
    quint8 byte2 = (iTimeStamp >> 16) & 0xFF;
    quint8 byte3 = (iTimeStamp >> 8) & 0xFF;
    quint8 byte4 = iTimeStamp & 0xFF;
    LOG_INFO << "--->z CUdpServer::_HandleHeartFrame byte1:"<< iTimeStamp<<"||"<<QString::number(iTimeStamp, 16).toStdString();

    cArray[4] = (int)byte1;//时间戳
    cArray[5] = (int)byte2;//时间戳
    cArray[6] = (int)byte3;//时间戳
    cArray[7] = (int)byte4;//时间戳

    cArray[8] = _oArray.at(8);//唯一ID
    cArray[9] = _oArray.at(9);//唯一ID
    cArray[10] = _oArray.at(10);//唯一ID
    cArray[11] = _oArray.at(11);//唯一ID
    bool bOK = false;
    cArray[12] = _sCmd.toInt(&bOK, 16);//命令类型
    cArray[13] = 0x00;//包序列
    cArray[14] = 0x00;//数据长度
    cArray[15] = _XorCheck(cArray, sizeof(cArray) - 1);//校验字节

    bRet = _WriteDatagram(_sIp, _iPort, (char*)cArray, sizeof(cArray));
    return bRet;
}

bool CUdpServer::_HandleLoginFrame(const QString &_sIp, const int &_iPort, const QByteArray &_oArray)
{
    bool bRet = false;

    if (_oArray.length() < 16)
    {
        return bRet;
    }

    if (m_bDoing)
    {
        return false;
    }
    m_bDoing = true;

    bRet = _ResponseFrame(_sIp, _iPort, "01", _oArray);
    m_bDoing = false;
    return bRet;
}

void CUdpServer::_Init()
{
    m_pUdpServer = new QUdpSocket(this);
}

uint8_t CUdpServer::_XorCheck(uint8_t *pbuf, uint32_t length)
{
    uint8_t temp = 0;
    uint32_t i;
    pbuf++;
    pbuf++;
    for (i = 0; i < length - 2; i++)
    {
        temp ^= *pbuf++;
    }
    return temp;
}

qint64 CUdpServer::_GetNowDateTimeStampMs()
{
    // 获取当前日期和时间
    QDateTime currentDateTime = QDateTime::currentDateTime();

    // 获取当前年份
    int iCurrentYear = currentDateTime.date().year();
    int iCurrentMonth = currentDateTime.date().month();
    int iCurrentDay = currentDateTime.date().day();
    // 创建基准日期(以当前年份为基准)
    QDateTime baseDateTime(QDate(iCurrentYear, iCurrentMonth, iCurrentDay), QTime(0, 0, 0));

    // 计算当前时间相对于基准日期的毫秒数
    qint64 timestamp = currentDateTime.toMSecsSinceEpoch() - baseDateTime.toMSecsSinceEpoch();
    return timestamp;
}

void CUdpServer::on_readyRead()
{
    //帧头(af)   设备类型    设备编号    电池电量  时间戳     唯一ID    命令类型  包序列  数据长度   校验字节    数据校验    数据
    // 1           1          1          1      4          4        1       1       1         1         1         N
    while (m_pUdpServer->hasPendingDatagrams())                       // 判断是否有可读数据
    {
        QNetworkDatagram datagram = m_pUdpServer->receiveDatagram(); // 读取数据
        QByteArray replyData = datagram.data();
        if(replyData.count())
        {
            QString sIP = datagram.senderAddress().toString().remove("::ffff:");
            int iPort = datagram.senderPort();
            QString sCmd = QString("%1").arg((char)replyData.at(12), 2, 16, QChar('0'));
            for (int i = 0; i< replyData.size(); i++)
            {
                char byte = replyData.at(i);
                QString sByte = QString::number((int)byte, 16);
                QString hexString = QString("%1").arg((int)byte, 2, 16, QChar('0'));
            }
            if (sCmd == "01")
            {
                _HandleLoginFrame(sIP, iPort, replyData);
            }
        }
    }
}

调用的时候,就直接new一个这个UDPServer的对象就可以了。

如果大家需要直接可以运行起来的程序,大家可以参考这位兄弟的,这位兄弟的就写的比较完整了,客户端,服务端都有。
https://github.com/mahuifa/QMDemo/tree/master/QMNetwork

END、总结的知识与问题

1、如何获取QByteArray中某一字节的数据,并将其转为十进制?

int iElectricQuantity = (int)_oArray.at(3);

2、如何以本年本月本日为基础,获取时间戳,而不以1970为基础?

qint64 CUdpServer::_GetNowDateTimeStampMs()
{
    // 获取当前日期和时间
    QDateTime currentDateTime = QDateTime::currentDateTime();

    // 获取当前年份
    int iCurrentYear = currentDateTime.date().year();
    int iCurrentMonth = currentDateTime.date().month();
    int iCurrentDay = currentDateTime.date().day();
    // 创建基准日期(以当前年份为基准)
    QDateTime baseDateTime(QDate(iCurrentYear, iCurrentMonth, iCurrentDay), QTime(0, 0, 0));

    // 计算当前时间相对于基准日期的毫秒数
    qint64 timestamp = currentDateTime.toMSecsSinceEpoch() - baseDateTime.toMSecsSinceEpoch();
    return timestamp;
}

3、如何将一个四个字节组成的数拆分成1个字节一个字节的?

 	qint64 iTimeStamp = _GetNowDateTimeStampMs();//这个实际获取到的是4个字节,但也没关系,我们根据下面的方式,取到的一定是最前面的四个字节

    quint8 byte1 = (iTimeStamp >> 24) & 0xFF;
    quint8 byte2 = (iTimeStamp >> 16) & 0xFF;
    quint8 byte3 = (iTimeStamp >> 8) & 0xFF;
    quint8 byte4 = iTimeStamp & 0xFF;

    cArray[4] = (int)byte1;//时间戳
    cArray[5] = (int)byte2;//时间戳
    cArray[6] = (int)byte3;//时间戳
    cArray[7] = (int)byte4;//时间戳

4、如何对前面的所有字节进行异或校验?

uint8_t CUdpServer::_XorCheck(uint8_t *pbuf, uint32_t length)
{
    uint8_t temp = 0;
    uint32_t i;
    pbuf++;
    pbuf++;
    for (i = 0; i < length - 2; i++)
    {
        temp ^= *pbuf++;
    }
    return temp;
}

5、如何将QByteArray中的某个字节转为十六进制?

char byte = replyData.at(i);//replayData就是一个QByteArray
QString hexString = QString("%1").arg((int)byte, 2, 16, QChar('0'));

参考

参考:
1、UDP广播与多播
2、Qt 网络编程-UDP
3、Qt中实现UDP的分包和组包——参考“草上爬”的博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1363231.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ps 滤镜:高反差保留

Ps菜单&#xff1a;滤镜/其它/高反差保留 Filter/Others/High Pass 高反差保留 High Pass滤镜常用于锐化、保护纹理、提取线条等图像编辑工作流程中。它的工作原理是&#xff1a;只保留显示图像中的高频信息&#xff08;即图像中的细节和边缘区域&#xff09;&#xff0c;而图像…

二分查找算法(指定数值的左右边界)

之前一直以为二分查找有什么难的&#xff0c;不就是确定左右边界&#xff0c;然后while循环求mid&#xff0c;大于mid的找右半边&#xff0c;小于mid的找左半边。直到最后相同了就是最后查找的结果了. 后来等真正用到二分查找算法的时候&#xff0c;发现问题远没有这么简单&…

【论文阅读笔记】ISINet: An Instance-Based Approach for Surgical Instrument Segmentation

1. 论文介绍 ISINet: An Instance-Based Approach for Surgical Instrument Segmentation ISINet&#xff1a;一种基于实例的手术器械分割方法 2020 MICCAI 【Paper】 【Code】 2.摘要 我们研究了机器人辅助手术场景中手术器械的语义分割任务。我们提出了基于实例的手术器械…

计算机Java项目|基于Springboot实现患者管理系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 文末获取源码 项目编号&#xff1a;KS-032…

Hello 2024

Hello 2024 A. Wallet Exchange 题意&#xff1a;Alice和Bob各有a和b枚硬币&#xff0c;每次他们可以选择交换硬币或者保留&#xff0c;然后扣除当前一枚手中的硬币&#xff0c;当一方没得扣另一方就赢了。 思路&#xff1a;Alice先手&#xff0c;所以当硬币和为奇数时Alice…

Java面试之并发篇(一)

1、前言 本篇主要总结JAVA面试中关于并发相关的高频面试题。本篇的面试题基于网络整理&#xff0c;和自己编辑。在不断的完善补充哦。 2、简述程序、进程、线程、的基本概念&#xff1f; 2.1、程序 程序&#xff0c;是含有指令和数据的文件&#xff0c;被存储在磁盘或其他的…

基于神经网络的手写汉字提取与书写评分系统研究

相关源码和文档获取请私聊QQ:3106089953 论文目录结构 目 录 摘 要 I Abstract II 目 录 IV 第1章 绪论 1 1.1. 研究背景与意义 1 1.2. 国内外研究现状 2 1.2.1. 文本定位技术研究现状 2 1.2.2. 手写汉字识别研究现状 3 1.2.3. 汉字书写质量评价方法研究现状 4 1.3. 本文所做工…

OS_lab——bochs源码的编译与安装

1. 实验环境VMware station 15 Ubuntu 14.04.6 32位。2. 实验步骤2.1 安装虚拟机&#xff0c;并在虚拟机根目录下编译并安装bochs环境。 2.2 使用bochs自带工具bximage创建虚拟软驱。 2.3 编写引导程序boot.asm并用nasm编译得到引导文件boot.bin和boot.com。 2.4 修改bochs…

Hadoop分布式文件系统(二)

目录 一、Hadoop 1、文件系统 1.1、文件系统定义 1.2、传统常见的文件系统 1.3、文件系统中的重要概念 1.4、海量数据存储遇到的问题 1.5、分布式存储系统的核心属性及功能含义 2、HDFS 2.1、HDFS简介 2.2、HDFS设计目标 2.3、HDFS应用场景 2.4、HDFS重要特性 2.4…

性能分析与调优: Linux 安装基于BPF的bcc-tools系统性能工具库

目录 一、实验 1.环境 2.agent服务器安装使用ELRepo安装依赖包 3.agent服务器安装基于BPF的bcc-tools系统性能工具库 二、问题 1.安装bcc-tools后执行命令报错 一、实验 1.环境 &#xff08;1&#xff09;主机 表1-1 主机 主机架构组件IP备注prometheus 监测 系统 pro…

【C++】- 类和对象(!!C++类基本概念!this指针详解)

类和对象 引入类类的定义类的访问限定操作符类的作用域类的实例化类对象模型this指针 引入类 在 C中&#xff0c;引入了一个新的定义----------类。类是一种用户自定义的数据类型&#xff0c;用于封装数据和行为。类可以看作是一个模板或蓝图&#xff0c;描述了一组相关的数据和…

JVM虚拟机的垃圾回收器(面试题)

1.什么是垃圾回收 垃圾回收主要说的是java会自动把程序在运行过程中产生的一些没有用的对象给回收掉&#xff0c;这样可以避免内存的浪费。 java主要是通过一个叫“根可达”的算法来识别这个对象是否可以被回收的&#xff0c;然后回收的算法也主要有三种&#xff1a;标记清除&a…

QT c++和qml交互实例

文章目录 一、demo效果图二、c和qml交互的基本方式1、qml访问C类对象 三、关键代码1、工程结构图2、c代码MainWindow.cppMainQuickView.cppStudentInfoView.cppStudentInfoModel.cpp 3、qml代码main.qmlMainQuickTopRect.qmlMainQuickMiddleRect.qmlMainQuickMiddleTableRect.q…

服务器cpu占用很高如何排查问题

前段时间&#xff0c;运维监控发现有个项目cpu占用很高&#xff0c;并且还在持续不断增长&#xff0c;服务不能正常响应&#xff0c;如下图&#xff1a; 在服务器上面安装了arthas&#xff0c;下载地址&#xff1a; https://alibaba.github.io/arthas/arthas-boot.jar 我使用了…

Linux stm32串口下载程序

一、工具 使用stm32flash进行串口下载 二、stm32flash安装 sudo apt-get install stm32flash 三、查看串口设备名称 先拔掉串口运行下面指令&#xff0c;获得所有设备名称,插上串口再运行一次&#xff0c;新增的就是串口设备名称&#xff0c;记住串口设备名称&#xff0c;以…

【信息论与编码】习题-判断题-第一部分

目录 判断题1. 对于N个对立并联信道&#xff0c;其信道容量CN2. 汉明码是一种线性分组码。3. 某一信源&#xff0c;不管它是否输出符号&#xff0c;只要这些符号具有某些概率特性&#xff0c;就有信息量。4. 若检错码的最小距离为dmin&#xff0c;则可以检测出任意小于等于dmin…

集团企业OA办公协同平台建设方案

一、企业对协同应用的需求分析 实现OA最核心、最基础的应用 业务流转&#xff1a;收/发文、汇报、合同等各种审批事项的业务协作与办理 信息共享&#xff1a;规章制度、业务资料、共享信息资源集中存储、统一管理 沟通管理&#xff1a;电子邮件、手机短信、通讯录、会议协作等…

期货日数据维护与使用_日数据维护_日数据更新

目录 写在前面&#xff1a; 下载日数据 下载“新增合约”日数据 下载“待更新合约”日数据 日数据文件 “选择日数据所在目录”按钮点击 “执行”按钮点击 sqlite3代码 按钮点击后执行的代码 子线程代码 写在前面&#xff1a; 本文默认已经创建了项目&#xff0c;如…

Archlinux下自启动rclone mount

路径&#xff1a; /etc/systemd/system/rclonemount.service [Unit] Descriptionrclonemount Requiresnetwork-online.target.wants Afteralist.service[Service] Typesimple ExecStartPre/bin/mkdir -p /media ExecStart/usr/bin/rclone mount \aliyun: /media \--config /ro…

Docker学习与应用(五)-DockerFile

1、DockerFile 1&#xff09;DockerFile介绍 dockerfile是用来构建docker镜像的文件&#xff01;命令参数脚本&#xff01; 构建步骤&#xff1a; 1. 编写一个dockerfile文件 2. docker build 构建称为一个镜像 3. docker run运行镜像 4. docker push发布镜像&#xff08;D…