golang udp 服务的坑
golang udp 服务端演示级的写法一般是:
1 | conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: 8866}) |
这段代码作为教学或演示是没有问题的。但应用于生产时,处在在一个频繁收发报文的中心服务器上,这里就有两个问题了:
- 在
for
循环中不断申请变量data
并make
。会产生大量内存消耗。引起频繁GC。 - 收到数据后处理不应该在
for
循环内部。因为如果数据处理时间过长,就会拥塞。拥塞期间若底层缓冲区满了,说不定会丢包。
那么V2版写法来了:
1 | conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: 8866}) |
V2版看上去完美!其实运行结果会完全出人意料!
因为每次for
循环没有将data
置0,当传输的是二进制时,会导致上次Read的结果干扰下次Read的结果。导致对数据 Unmarshal
时随机出错!如果不借助tcpdump
抓包做位级比对!这会变成一个玄学问题!
ok!再来V3!
1 | conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: 8866}) |
V3版成功解决了干扰!
但又来了新问题。就是:在协程中处理数据data[:n]
时总是所有位是0
的n
位长的数组。辣么,数据呢?
当然是协程内引用data
导致的。因为在协程创建完成,开始执行前,for
已经进入下一个循环并在Read
处等待了。此时的data
经过make
已经重置数据了。
这里要介绍一个
golang
的基础功能copy
golang
为了避免每一层的处理数据都要在内存里建立相同数据的副本。采用了引用的方式传递。也就是slice
的存在的意义!它大量节约了内存!天才般的设计!
这里conn.ReadFromUDP(data)
后data
里放的其实是底层缓冲区内的数据引用。操作data
实际上是在操作底层缓冲区。
所以我们在操作data
前一定要先将data
里的数据读入新的变量里,实现私有化
,再交给协程处理。否则在协程未启动完成前,data
里的数据可能因为进入新的循环,而被刷新!
不幸的是,简单的=
是不能将data
私有化
的。只能make
一个空白的slice
,再将data
逐个复制进来。这个操作golang
已为我们封装好了一个函数,它就是:copy
ok!再来V4!
1 | conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: 8866}) |
ok! 运行起来几乎没啥问题。
- 本文标题:golang udp 服务的坑
- 本文作者:jf wang
- 创建时间:2020-06-29 14:11:38
- 本文链接:https://www.wangjunfeng.com.cn/2020/06/29/golang-udp-server/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!