UDP通讯要知道

UDP是无连接的传输协议,该协议称为用户数据包协议(UDP,User Datagram Protocol)。


1. 丢包

造成丢包的原因

  1. 接收端处理速度太慢造成的数据丢包。
  2. 接收端缓存太小造成的数据丢包。
  3. 发送端数据太大,超MTU size。
  4. 发送频率太快,对应1。
  5. UDP包过多,由于QS限定,会被服务商误认为是攻击,UDP包被丢弃。


2. 乱序

乱序是由于路由的不同和路由的存储转发的顺序不同造成的。


3. 帧

理论上,IP数据报的最大长度是65535字节,这是由IP首部16比特总长度字段所限制。去除20字节的IP首部和8字节的UDP首部,UDP数据报中用户的最长长度为65507.但是,大多数实现所提供的长度比这个最大值小。

3.1 缓存大小

$ cat /proc/sys/net/core/rmem_max
212992
$ cat /proc/sys/net/core/rmem_default
212992

$ sysctl -a | grep mem_
net.core.optmem_max = 20480
net.core.rmem_default = 212992
net.core.rmem_max = 212992
net.core.wmem_default = 212992
net.core.wmem_max = 212992
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096

程序中可以使用setsockopt函数与SO_RCVBUF选项对udp缓冲区的值进行更改,但是要注意不管设置的值有多大,超过rmem_max的部分都会被无视。

3.2 MTU 最大传输单元

1500 -> 576 -> 512

绝大部分的网络路由都能通过1500大小的包,但大部分协议(DNS TFTP BOOTP SNMP)都被限制成512字节或更小。 腾讯游戏也实行500+的大小限制来实现。

路径MTU发现机制

这是确定两个IP主机之间路径最大传输单元的技术,其目的就是为了避免IP分片。首先源地址将数据报的DF位置位,在逐渐增大发送的数据报的大小。 路径上任何需要将分组进行分片的设备都会将这种数据报丢弃并返回“数据报过大“的ICMP响应到源地址。 这样源主机就”学习“到了无需分片就能通过这条路径的最大的最大传输单元。

ping -M do进行MTU发现

$ ping -c 3 -s 1473 -M do 104.128.81.247   # 1473+28=1501
PING 104.128.81.247 (104.128.81.247) 1473(1501) bytes of data.
ping: local error: Message too long, mtu=1500
ping: local error: Message too long, mtu=1500
ping: local error: Message too long, mtu=1500

--- 104.128.81.247 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 54ms

3.3 多进程端口复用

可多进程读写同一端口(reuseport),在BIND socket前,通过调用setsockopt()函数,设置成SO_REUSEADDR,这样端口就可以重用了。

但这种方式也只有在多播情况下有可靠的,单播的包则只会被其中一个进程捕获,另一个进程则收不数据。


4. 阻塞与非阻塞

4.1 sendto在阻塞模式下也不会发生阻塞

因为UDP并没有真正的发送缓冲区,它所做的只是将应用缓冲区拷贝给下层协议栈,在此过程中加上UDP头,IP头,所以实际不存在阻塞。

4.2 recvfrom在阻塞模式下会阻塞

  • flags : 非阻塞 =MSG_DONTWAIT
  • flags : 阻塞 =0


5. 异步



优化方法


1. 多端口

多端口就可以多句柄多线程接收,将多核利用起来。



极限

  • 在Linux内核2.6.25版本中因为引入二级锁backlog机制来保护内存记账逻辑多了两个lock,它是借鉴TCP的backlog实现,而事实上,UDP并不需要这种花哨的backlog逻辑。4.10版本后解决了这个bug。

    sendto/recvfrom 耗时

参考: 如何快速优化 Linux 内核 UDP 收包效率? 1



排错

  1. sendto/recvfrom 报错 error Invalid argument,原因多出现在地址(local,peer)上,比如:
    • 向外网地址发送数据,local也需绑定在外网网卡上,不能绑定127.0.0.1上。
    • sockaddr_in结构内存在被赋值前没有做清0操作。