Qt学习日记 Day15:网络编程(二)- UDP基础

今天学了什么

今天学习Qt中的UDP基础。

UDP

定义:User Datagram Protocol,用户数据报协议。

特点:

  • 无连接:不用握手,发包前不用建立连接;
  • 不可靠:不确认、不重传、不保证顺序,包可能丢、乱序;
  • 面向报文:你一次发多少,就是一个数据报,不会像TCP那样流粘在一起;
  • 支持:单播、广播、组播;

对比TCP:

  • TCP打电话(接通才说话、有确认);
  • UDP寄明信片,扔出去就不管收没收到。

QUdpSocket 使用步骤:

UDP不分严格的客户端/服务端,只分:绑定端口(收包) 和 不绑定(只发包);

想收数据,必须 bind 绑定端口,只发数据可以不 bind;

① 服务端(监听,能收、能发):

创建 QUdpSocket 对象;

  1. bind 绑定本地IP和端口,开始监听这个端口;
  2. 绑定 readyRead 信号,有数据来了触发;
  3. 在槽函数里用 readDatagram() 读包,同时拿到发送方IP+端口;
  4. 回复对方:writeDatagram(数据, 对方IP, 对方端口);

客户端(可以只发,也可以绑定端口收回复):

  1. 创建 QUdpSocket;
  2. 不需要 connectToHost(UDP没有连接);(也可以用connectToHost写,但本质不是连接)
  3. writeDatagram(数据, 目标IP, 目标端口) 直接发包;
  4. 如果要接收对方回复,就也要 bind 端口 + 监听readyRead;

* 注:UDP没有连接,每次发包都要指定目标IP和端口。

核心代码:

服务端(接收+回复)

cpp 复制代码
#include <QUdpSocket>
#include <QHostAddress>

QUdpSocket* udpServer = new QUdpSocket(this);
// 绑定所有网卡,端口8888
udpServer->bind(QHostAddress::Any, 8888);

connect(udpServer, &QUdpSocket::readyRead, this, [=](){
    while(udpServer->hasPendingDatagrams())
    {
        QByteArray buf;
        buf.resize(udpServer->pendingDatagramSize());
        QHostAddress senderIp;
        quint16 senderPort;
        // 读取数据报,拿到发送方地址
        udpServer->readDatagram(buf.data(), buf.size(), &senderIp, &senderPort);

        // 回复消息给发送方
        QByteArray resp = "udp receive ok";
        udpServer->writeDatagram(resp, senderIp, senderPort);
    }
});

客户端(发消息,也可以收回复):

cpp 复制代码
QUdpSocket* udpClient = new QUdpSocket(this);
// 客户端如果只发,不bind也行;要收回复就要bind
// udpClient->bind(QHostAddress::Any, 9999);

// 直接发给服务端:127.0.0.1 8888
QByteArray sendData = "hello udp";
udpClient->writeDatagram(sendData, QHostAddress("127.0.0.1"), 8888);

广播(UDP专属):

  • 发给局域网全部主机,地址填广播地址 QHostAddress::Broadcast;
  • udpClient->writeDatagram("broadcast test", QHostAddress::Broadcast, 8888);

注:接收广播的那一方,bind的时候默认就能收到广播包。

组播:

  • 组播地址范围:224.0.0.0 ~ 239.255.255.255;
  • 组播必须先bind,再调用 joinMulticastGroup 加入组;
  • 同一个组播地址、同一个端口,加入组的程序才能收到组播消息;
  • 离开组:leaveMulticastGroup;退出时记得离开组;
  • 组播不能跨路由器(默认),只能局域网测试;

接收端(加入组,收组播消息):

cpp 复制代码
#include <QUdpSocket>
#include <QHostAddress>

QUdpSocket *udpRecv = new QUdpSocket(this);
const quint16 groupPort = 8888;
const QHostAddress groupAddr("224.0.0.100");

// 绑定端口,Any代表监听所有网卡
udpRecv->bind(QHostAddress::Any, groupPort, QUdpSocket::ShareAddress);

// 加入组播组
bool ret = udpRecv->joinMulticastGroup(groupAddr);
if(!ret)
{
    qDebug() << "加入组播失败";
}

connect(udpRecv, &QUdpSocket::readyRead, this, [=](){
    while (udpRecv->hasPendingDatagrams())
    {
        QByteArray buf;
        buf.resize(udpRecv->pendingDatagramSize());
        QHostAddress senderIp;
        quint16 senderPort;

        udpRecv->readDatagram(buf.data(), buf.size(), &senderIp, &senderPort);
        qDebug() << "收到组播数据:" << buf << "来自:" << senderIp;
    }
});

// 窗口关闭的时候离开组播组
// udpRecv->leaveMulticastGroup(groupAddr);

发送端(往组播地址发包):

cpp 复制代码
#include <QUdpSocket>
#include <QHostAddress>

QUdpSocket *udpSend = new QUdpSocket(this);
const quint16 groupPort = 8888;
const QHostAddress groupAddr("224.0.0.100");

// 发送端可以不bind,直接writeDatagram发到组地址
QByteArray data = "this is multicast msg";
udpSend->writeDatagram(data, groupAddr, groupPort);

注:

  • QUdpSocket::ShareAddress 这个参数,多个程序同时绑定同一个端口收组播必须加;
  • 防火墙容易拦截组播,测试收不到优先关防火墙;
  • 组播和广播区别:广播发给局域网全部设备;组播只发给加入这个组的程序;
  • 224.0.0.x 是本地链路组播,不会被路由器转发,适合局域网调试;
  • 接收端和发送端写在同一个项目里,开启组播环回,方便练习: udpsend->setSocketOption(QAbstractSocket::MulticastLoopbackOption,1);

问题补充

1.UDP不会粘包!一个readDatagram一次读到一个完整报文;

2.报文有长度上限,不能无限大包:

UDP 的理论最大包长受 IP 协议限制是 65535 字节(约 64KB),但在实际网络中,受 MTU 限制,通常建议单包不要超过 1472 字节(以太网 MTU 1500 - 20 IP头 - 8 UDP头),否则会在 IP 层被分片,增加丢包概率。 如果传大文件,还是得用 TCP。

3.丢包是正常现象,重要消息要自己加序号、ACK重试;

4.跨外网受NAT限制,内网广播只能在同一个路由器里面;

5.NAT是什么?TCP能不能客户端直接连客户端(P2P)?

NAT是网络地址转换,家用路由器把内网私网IP转换成公网IP。默认情况下,两个处在不同内网里的TCP客户端,不能直接互相连接;因为内网机器没有独立公网IP,路由器默认拒绝外部主动进来的连接。

TCP:做P2P很麻烦,TCP是面向连接,需要三次握手,打洞难度高,很少直接TCP打洞。

6.网段由IP和子网掩码决定,看IP前面几段;端口是本机区分程序的,和网段无关;

7.224.0.0.0~239.255.255.255 本身就是IPv4预留的组播地址段;IPv6组播以ff开头,例如ff02::100;

8.UDP怎么保证不丢包?

UDP底层无确认、无重传,原生不保证可靠。需要应用层自己做:包序号、ACK应答、超时重传、去重;也可以用KCP等现成可靠UDP库;高可靠优先TCP。

9.TCP和UDP区别

  1. TCP:面向连接,三次握手;可靠,有序、重传、流量/拥塞控制;开销大。

  2. UDP:无连接;不可靠,不保证到达、不排序;开销小,速度快。

10.TCP和UDP的适用场景:

TCP 适合不能丢数据、不能乱序:

  • 文件传输、HTTP/HTTPS、数据库访问、登录聊天、远程控制、支付。

UDP(快、无连接,允许少量丢包:适合追求实时性,丢一点没关系):

  • 视频通话、直播、游戏、局域网设备发现(广播/组播)、DNS查询。
相关推荐
云川之下1 小时前
[ensp] 交换机入门配置例子(带vlan)
运维·服务器·网络
别讲概念1 小时前
JavaEE(6)TCP_IP
网络·tcp/ip·java-ee
FserSuN1 小时前
Hive 分区表存储与查询优化学习总结
hive·hadoop·学习
2603_969694851 小时前
【MySQL学习笔记】DQL(基础查询/条件/聚合/分组/排序/分页)与 DCL(用户权限)全解析
数据库·笔记·学习·mysql·学习方法
承渊政道1 小时前
Linux网络学习【数据链路层、ARP工作过程与ARP欺骗详解】
linux·网络·学习·数据链路层·arp
读书札记20222 小时前
Qt UTF-8和UTF-8(BOM)
开发语言·qt
ShineWinsu2 小时前
对于Qt:QProgressBar常见属性的解析
开发语言·c++·qt·进度条·gui·定时器·qprogressbar
ShineWinsu2 小时前
对于Qt:QLCDNumber常见属性以及QTimer的解析
服务器·数据库·c++·qt·gui·qtimer·qlcdnumber
Novproxy_Z11 小时前
商用代理避坑:如何识别商家拿机房 IP 冒充住宅 IP
java·网络·tcp/ip