UDP 的包明明发出去了,对方却没收到——丢在哪一层?接收缓冲区有多大,调大有用吗?这篇让接收方故意不读、读得慢,用 netstat 的计数对账,看发送方对此一无所知,以及缓冲区大小到底能挡住多大的突发。
net.inet.udp.recvspace: 786896 kern.ipc.maxsockbuf: 8388608 新建的 UDP 套接字 SO_RCVBUF 默认 = 786896 字节 设置 4096:读回 4096 设置 65536:读回 65536 设置 262144:读回 262144 设置 1048576:读回 1048576 设置 4194304:读回 4194304 设置 8388608:读回 8388608 设置 16777216:读回 8388608 设置 67108864:读回 8388608
默认 786,896 字节,最大 8 MiB;设置过大的值不会报错,只是被截断。
每个 100 字节:发了 5000 个(sendto 报错 0 次),队里攒下 4099 个(409900 字节载荷);Recv-Q 显示 541052 字节;netstat “dropped due to full socket buffers” 增加了 908;攒下的数量 + 被丢的 = 5007 每个 1000 字节:发了 5000 个(sendto 报错 0 次),队里攒下 762 个(762000 字节载荷);Recv-Q 显示 786368 字节;netstat “dropped due to full socket buffers” 增加了 4312;攒下的数量 + 被丢的 = 5074 每个 8000 字节:发了 5000 个(sendto 报错 0 次),队里攒下 97 个(776000 字节载荷);Recv-Q 显示 779088 字节;netstat “dropped due to full socket buffers” 增加了 4932;攒下的数量 + 被丢的 = 5029 发送方 sendto 一次都没有报错:UDP 发送方完全不知道对方的缓冲区满了。Recv-Q 比载荷字节数大(每个数据报还要记账发送方地址等开销),所以小包能攒的个数远少于 默认缓冲区 ÷ 载荷大小 按这三种大小,默认缓冲区(786896 字节)平均每个数据报占 192 / 1033 / 8112 字节
每个数据报占的缓冲区比载荷大(要记账发送方地址等),100 字节的包占 192 字节;小包能攒的个数远少于缓冲区 ÷ 载荷。
SO_RCVBUF = 65536(读回 65536):攒下 63 个 / 5000,丢 4937 个(98.7%);netstat 满缓冲区丢弃 +4972 SO_RCVBUF = 262144(读回 262144):攒下 254 个 / 5000,丢 4746 个(94.9%);netstat 满缓冲区丢弃 +4756 SO_RCVBUF = 786896(读回 786896):攒下 762 个 / 5000,丢 4238 个(84.8%);netstat 满缓冲区丢弃 +4255 SO_RCVBUF = 1048576(读回 1048576):攒下 1016 个 / 5000,丢 3984 个(79.7%);netstat 满缓冲区丢弃 +4024 SO_RCVBUF = 4194304(读回 4194304):攒下 4064 个 / 5000,丢 936 个(18.7%);netstat 满缓冲区丢弃 +946 SO_RCVBUF = 8388608(读回 8388608):攒下 5000 个 / 5000,丢 0 个(0.0%);netstat 满缓冲区丢弃 +30
缓冲区越大越能吸收突发:突发 5000 个 1000 字节的包,8 MiB 全部攒下,64 KiB 只攒下 63 个。但只是把丢包推迟,不能解决持续的过载。
发送 全速,默认缓冲区:0.15 秒发完 20000 个,接收方(另一个进程)处理了 3868 个,丢 16132 个(80.7%);netstat 满缓冲区丢弃 +16326 发送 全速,8 MiB 缓冲区:0.12 秒发完 20000 个,接收方(另一个进程)处理了 20000 个,丢 0 个(0.0%);netstat 满缓冲区丢弃 +583 发送 每 100 µs 一个,默认缓冲区:2.16 秒发完 20000 个,接收方(另一个进程)处理了 10471 个,丢 9529 个(47.6%);netstat 满缓冲区丢弃 +9794 发送 每 100 µs 一个,8 MiB 缓冲区:2.18 秒发完 20000 个,接收方(另一个进程)处理了 20000 个,丢 0 个(0.0%);netstat 满缓冲区丢弃 +478 发送 每 500 µs 一个,默认缓冲区:10.22 秒发完 20000 个,接收方(另一个进程)处理了 20000 个,丢 0 个(0.0%);netstat 满缓冲区丢弃 +784 发送 每 500 µs 一个,8 MiB 缓冲区:10.22 秒发完 20000 个,接收方(另一个进程)处理了 20000 个,丢 0 个(0.0%);netstat 满缓冲区丢弃 +766 注意 netstat 的计数是整台机器所有进程共用的:什么都不做的 3 秒里,“dropped due to full socket buffers”也增加了 231(别的进程的 UDP 流量),所以上面的 netstat 增量里包含这部分背景噪声,以接收方自己数到的个数为准 接收方每个包要 300 µs,最多每秒处理约 3300 个;发送速度低于这个(每 500 µs 一个 = 2000 个/秒)就不丢,高于它多出来的先排进队列,队满了就丢;缓冲区只能吸收短时间的突发,更大的缓冲区只是把丢包推迟
发送速度超过接收速度时,多出的先排队,满了就丢。发送方的 sendto 全程成功,没有任何通知。
发 100 个到没人监听的端口:“dropped due to no socket” +100,“dropped due to full socket buffers” +51(这两个是不同原因的计数;前者对应 w15 里的 ICMP 端口不可达,而且受 ICMP 限速) netstat -s -p udp 里这类计数开机以来累计:rx 20106390 ; full 13614785 ; nosock 1179540 ; delivered 4887494
netstat -s -p udp 的“dropped due to full socket buffers”是整机共用的累计计数,别的进程的流量也算在里面,要以接收方自己数到的个数为准。