Golang Go语言中UDP协议读取报文问题

发布于 1周前 作者 songsunli 来自 Go语言

Golang Go语言中UDP协议读取报文问题

一次性读满 512 个字节没问题,但是分两次读就读不到了,代码如下

// 读满 512 字节没问题
buf := make([]byte, 512)
_, err =io.ReadFull(r,buf)
// 第一次读没问题
head := make([]byte, 20)
_, err =io.ReadFull(r,head)

// 第二次读一直阻塞
body := make([]byte, 6)
_, err =io.ReadFull(r,body)

更多关于Golang Go语言中UDP协议读取报文问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

55 回复

不会像 tcp 一样是流形式发送.
每次接受都是接受发送的一个包.

更多关于Golang Go语言中UDP协议读取报文问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


那如果不知道包的大小咋办,一般 body 的长度都放在 head 里,所以先读了一次 head

io.ReadFull 设计是你预知长度,要读取确定长度的数据,超出部分就不要了(这句我猜的)。

如果读未知长度的要弄个 Writer 用 io.Copy

不会的,TCP 协议的话读满之后还可以接着读后面的报文

网络基础知识太差又不看文档,net.PacketConn.ReadFrom 返回值的第一个是啥?

大佬,第一个返回值不是读取到的字节数吗,和这个问题有啥关联啊?还请赐教

查下文档 https://golang.org/pkg/io/#ReadFull

ReadFull reads exactly len(buf) bytes from r into buf. It returns the number of bytes copied and an error if fewer bytes were read. The error is EOF only if no bytes were read. If an EOF happens after reading some but not all the bytes, ReadFull returns ErrUnexpectedEOF. On return, n == len(buf) if and only if err == nil. If r returns an error having read at least len(buf) bytes, the error is dropped.


// 第一次读没问题
head := make([]byte, 20)
, err =io.ReadFull(r,head)
// 这时候 ReadFull 就全部 512 字节读取完了,然后只保留了 20 字节给你的 head 变量,后面全部舍弃了

// 第二次读一直阻塞
body := make([]byte, 6)
, err =io.ReadFull(r,body)
// 当然就阻塞了,因为已经全部读完了都 EOF 了

你就用普通的 read 就可以了

https://tour.golang.org/methods/21

假设你的 head 长度固定,body 长度存在 head 的某个字段里,那就先 make 一个和 head 等长的[]byte
然后 read,得到长度,再 make 一个对应长度的 body []byte,就可以了,如果末尾还有定长校验字就再 read 就好了

#6

n, err =io.ReadFull(r,head)

如果你把这个 n 打出来看看,会发现是 512 而不是 20,表示他实际读了 512 字节,而不是你想要的只读 20 字节

谢谢老哥耐心解答,我找到原因了,原来一直有 err 返回,只是 io.ReadFull()在读取到指定字节会把 err 置空,

错误信息:wsarecv: A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself.

看样子 upd 接收的缓冲区一定要大于要接收的这次报文,所以你 8L 提供的那种方法也是行不通的,现在我是直接开辟一个大点的 buf 用 conn.Read()接收,只能这样做了

n 我打印出来过了,都是 20 😂,其实是因为有 err,io.ReadFull()里面把 err 忽略掉了。
<br>func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {<br> if len(buf) &lt; min {<br> return 0, ErrShortBuffer<br> }<br> for n &lt; min &amp;&amp; err == nil {<br> var nn int<br> nn, err = r.Read(buf[n:])<br> // nn=20,err!=nil<br> n += nn<br> }<br> if n &gt;= min {<br> //读到了指定的字节数就置空 err<br> err = nil<br> } else if n &gt; 0 &amp;&amp; err == EOF {<br> err = ErrUnexpectedEOF<br> }<br> return<br>}<br>

你说返回 512 吓的我又去翻了一遍资料…

你可以直接用 ioutil.ReadAll

用 ioutil.ReadAll 也不对呀,不是读完这次就 EOF,后面还有呢

udp 是一次一个包的吧. tcp 才是流式的, 按理说可以知道这次这个 udp 包的大小,

ioutil.ReadAll 能一次性读完, 他里面也是调用你说的 Read 方法啊, 省去了你自己在 buf 不够的时候扩容 buf 这些代码. 它里面都处理好了.

我现在只能确定 udp 响应包的最大长度,但是真实的响应可能没这么长,我看了 ioutil.ReadAll 源码,是要遇到 io.EOF 或者其它异常(比如超时)才能返回,但是 udp 协议并不存在关闭连接也就不会有 io.EOF ,所以我这里调用的话应该是一直阻塞着直到超时

嗷, ioutil.ReadAll udp 我也没试过…

你知道了 udp 包最大字节数, 先就申请一个最大字节数这么大的 buf, 然后 io.ReadFull, 再看 err 是否时 io.EOF, 这样不知是否可行? 只调用 conn.Read() 它的语义不保证一次能读完吧, 虽然一般都没出现过问题.

#12 我说的 512 是楼主帖子里写的”一次性读满 512 个字节没问题” 推测为他的一个包为 512 字节然后 flush 一次


#10 为什么一定要用 ReadFull(),用标准 Reader 的 Read 就可以了,当作流式用也一样没问题

PacketConn 第一次返回的就是整个包的长度,你还要从哪里去找 body 的长度?

udp 读估计每次都是一个完整的 udp 报文,不存在和 tcp 那样用 Read()一次性读不完,所以我现在只要用最大字节数的 buf 去 Read()一次就行了,不用 io.ReadFull 了,毕竟响应的报文不一定有这么长,这个方法肯定阻塞。

现在没有 ReadFull()了,可能是 TCP 写多了,之前以为 UDP 和 TCP 一样可以这样读。 🤓

看了一圈,没找到啥可以知道要接收的 udp 包大小的… 只能先 new 一个足够大的 buf 去接收了…


这个返回的 n 应该也是读取到 buf 里的字节数.

话说你这个 io.ReadFull(r, buf) 中的 r 是哪里来的呢.

瞎猜有什么用,UDP 协议基本特性都不知道,回去补习网络基础知识。

r 就是一个*net.UDPConn

UDP 是面向报文(packet)的协议,一次读取对应一个网络包(packet),不存在一次读取不完或多次读取一个报文。理论上 UDP 报文最大长度是 65507 字节,实际一般不会这么多,要看具体的应用层协议。

我 google… 不是调用 UDPConn 的 ReadFromUDP 方法吗.

都一样的,read()底层也是调用的 ReadFrom(),这个 就是个睿智,说了半天就一直在那秀优越,前面说的也全是错的,真的是 talk is cheap

一次读 1M,绝不会有问题

这个是 ReadFull 的一个局限,它调用 Read 时,如果返回的数据比缓冲区还长,那就会丢弃超出的

所以第二次就读不到了

可以套一个 bufio.Reader,这样超出的部分会缓存起来

udp 一般不都是缓冲区 1024 直接读么…udp 分包特别麻烦,你要用 quic 这种高级一点的协议才行。

#18 你这逻辑,先后次序都颠倒了

Excuse me? 给你指了方向不去想自己哪里没搞明白,解决问题全靠瞎猜?

你又想说啥

我看别人的实现都是初始化一个最大长度的包
直接读取一个包

buf := make([]byte, 1500)

go<br><br>buf := make([]byte, 1500)<br><br>for {<br> n, src, err := packet.ReadFrom(buf)<br><br> ... buff[:n]<br>}<br><br>

刚试了下并不会这样,buf 给多大,就只读多大的,有多余的下次能读到。

我试的是 tcp 的情况,楼主说的 udp 应该是要给一个足够的 buf,一次性读完所有内容。

最大传输单元( MTU )大概是 1500,具体记不清了。所以在底层读的这里用这么大的 buffer 就够了。如果一个数据大于 1500,在传输层也会分包,所以在接收的地方还需要将数据重组。根据 n, src, err := packet.ReadFrom(buf) 的 src 判断来源。即 的方式。

那你试的和这里讨论的,根本就不一样啊。现在说的是读 UDP 时,缓冲区不够大的情况。请重新试验。

这个 src 不可信的,很多 NAT 只要回过一次包就会 DROP rule。

确实是不一样,但是楼主读 udp 消息为什么要用 ReadFull 呢,buf 给少了会丢数据(就算是加了 bufio.Reader 也是不符合场景的,造成一次消息多次读取,本来都是用 udp 了还要自己做消息分隔符,而且消息的内容长度必须是预设 buf 的整数倍,不然会后那点消息会被卡住),如果一开始预设 buf 给多了也会卡住。

所以读 udp 直接用 ReadFrom 给一个满足业务需求的 buf 大小就行了。

应该不用考虑 ether 的 MTU 吧,给的 buffer 大于 1500 应该也会在内核缓冲区拼接的(我猜的)

楼主问当不知道 UDP packet 长度时怎么分配 buffer 大小,buffer 给小了导致后面数据被 drop,你答 UDPConn.ReadFrom 第一个返回值就是 body 长度?你听明白他问啥了?你答的又是啥?

一个是 stream 一个 packet,TCP 一次读不完还存在协议栈缓冲区,UDP 按报文划分,buffer 过小后面数据直接 drop 了

读出来之后,解析内容时,就要用到 ReadFull 了,例如 header 固定是 16 字节,那用 16 字节的缓冲区去读,也是正常做法。当然也可以直接用下标去处理,但如果协议发生变化,下标就可能全部变化,基于 io.Reader 的代码,就不需要调整下标,只需要插入多一些读的代码。

因为他的根本问题是他对 UDP 的基本原理不清楚,才会问出这么奇怪的问题。如果理解 UDP 的语义,自然就会知道不能分多次去读同一个包,也不会出现什么不知道包的长度还需要去读一个 header 来判断 body 长度的问题。举个例子,为什么 DNS over TCP 需要两个字节的头部而 DNS over UDP 不需要?因为 UDP 的包长度在接受的时候就是已知的,根本就不可能会使用 io.ReadFull 这个调用。你不去找他的根本问题在哪里,只给一个解决表面问题的答案,他也不会意识到自身的问题在哪里,以后继续犯类似的错误。

你觉得你听明白了问题?你自己说的「 不知道 UDP packet 长度时怎么分配 buffer 大?」你自己想想正确答案应该是啥。

说白了,这个问题和本站之前出现的「 TCP 粘包问题」如出一辙:不去研究底层原理,一切全靠瞎猜。


你的回复在说net.PacketConn.ReadFrom返回值的第一个就是 body 长度。假设我给个 1024 bytes 的 buffer,实际 packet 有 1400 bytes,ReadFrom 给我返回的一定是 1024。packet 长度确实是确定的,但 ReadFrom 传进去的 buffer 该分配的大小在事先是不知道的(在没有提前协商的前提下)。就好比我需要知道 length 才能调用 Read,你告诉我你调用 Read 就知道 length 了一样。楼主确实看起来没有 UDP 编程经验,所以直接告诉他分配一个足够大的 buffer,不需要像 stream 一样去做额外的上层分包就行了

你还是没理解这里的问题到底在哪。

「假设我给个 1024 bytes 的 buffer,实际 packet 有 1400 bytes,ReadFrom 给我返回的一定是 1024。」
「我需要知道 length 才能调用 Read,你告诉我你调用 Read 就知道 length 了一样。」

如果知道 UDP 的原理,就根本不会出现用 1024-byte buffer 去读 1400-byte packet 这种情况,也不会需要在这个包头加一个 header 记录 body 的长度,更不会出现不合时宜的用 io.ReadFull 去读取 body 全文。

你前面其实也自己把这个问题总结出来了,「当不知道 UDP packet 长度时怎么分配 buffer 大小?」你这里给的答案是「分配一个足够大的 buffer,不需要像 stream 一样去做额外的上层分包」,但其实并没有真的回答问题:多大才是足够大?为什么不需要做分层?这两个问题都需要对 UDP 底层有基本的理解才能解释。这才是楼主的根本问题。

stackoverflow 上有几乎一模一样的问题,read data from a udp socket with an unknown length,解释得更清楚哈哈

我觉得可以理解成,缓冲区长度不够,read 再读就读不到, 这是 UDP 协议的行为,跟 go 语言以及 go 语言的库函数没有关系;所以只能和发送方先约定好 包的大小

又看到一个回答,recv (或 recvfrom) 系统调用有 flag (MSG_TRUNC 或 MSG_PEEK) ,是可以知道 packet 大小的


我明白你的意思,楼主用 io.ReadFull 处理 packet,用 header 记录 body length 确实是不理解 UDP,也肯定没有看过 io.ReadFull 的实现,所以楼主的问题是需要补一下计网的知识
至于多大才是足够大,如果是收发自己设计的应用协议,那就按约定的格式来,设计尽量处于 ethernet MTU 的大小范围内。但假如是收发未协商大小的包,比如做 UDP 端口转发,每个 buffer 需要给到 65507

所以你看这个帖子下最开始的那些「讨论」,根本就是在盲人摸象。

我最开始是心情好给他点一下,稍微聪明点的人会去想自己哪里理解错了。但显然楼主意识不到自己的问题,那就……随他去吧~ 再也不指点这些萌新了。

golang.google.cn/src/io/io.go?h=io 看了一下 ReadAtLeast 意思是 读的总字节数超过了你定的阀值 也正常返回

在Go语言中处理UDP协议读取报文时,通常涉及使用net包中的net.UDPConnnet.PacketConn接口。以下是一些关键点,帮助你理解和解决UDP报文读取的问题:

  1. 创建UDP连接: 使用net.ListenUDPnet.DialUDP函数来创建服务器或客户端的UDP连接。net.ListenUDP返回一个*net.UDPConn,用于监听传入的UDP报文;net.DialUDP返回一个*net.UDPConn,用于发送UDP报文到指定的地址。

  2. 读取报文: 使用UDPConn.ReadFromUDP方法读取数据。这个方法会阻塞,直到有数据到达。返回的net.UDPAddr包含发送方的地址信息,数据则存储在返回的[]byte中。

  3. 处理并发: UDP是无连接的协议,每个报文都是独立的,因此处理每个报文通常需要在goroutine中进行,以避免阻塞整个程序。使用go关键字启动新的goroutine来处理每个接收到的报文。

  4. 缓冲区大小: 确保读取缓冲区足够大,以容纳可能接收到的最大UDP报文。Go的默认UDP缓冲区大小可能较小,必要时可以通过系统调用调整。

  5. 错误处理: 总是检查读取操作是否返回错误,并适当处理,如重试、记录日志或关闭连接。

通过上述步骤,你应该能够有效地在Go语言中处理UDP协议读取报文的问题。如果遇到具体错误或性能瓶颈,可能需要进一步分析报文数据或调整网络配置。

回到顶部