golang UDP 协议读取报文问题

2020-02-27 13:30:17 +08:00
 monkeyWie

一次性读满 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)
7717 次点击
所在节点    Go 编程语言
54 条回复
dawniii
2020-02-28 12:03:40 +08:00
@reus 确实是不一样,但是楼主读 udp 消息为什么要用 ReadFull 呢,buf 给少了会丢数据(就算是加了 bufio.Reader 也是不符合场景的,造成一次消息多次读取,本来都是用 udp 了还要自己做消息分隔符,而且消息的内容长度必须是预设 buf 的整数倍,不然会后那点消息会被卡住),如果一开始预设 buf 给多了也会卡住。

所以读 udp 直接用 ReadFrom 给一个满足业务需求的 buf 大小就行了。
whoami9894
2020-02-28 15:00:06 +08:00
@ydongd 应该不用考虑 ether 的 MTU 吧,给的 buffer 大于 1500 应该也会在内核缓冲区拼接的(我猜的)

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

@dawniii 一个是 stream 一个 packet,TCP 一次读不完还存在协议栈缓冲区,UDP 按报文划分,buffer 过小后面数据直接 drop 了
reus
2020-02-28 15:45:50 +08:00
@dawniii 读出来之后,解析内容时,就要用到 ReadFull 了,例如 header 固定是 16 字节,那用 16 字节的缓冲区去读,也是正常做法。当然也可以直接用下标去处理,但如果协议发生变化,下标就可能全部变化,基于 io.Reader 的代码,就不需要调整下标,只需要插入多一些读的代码。
rio
2020-03-01 12:21:16 +08:00
@whoami9894 因为他的根本问题是他对 UDP 的基本原理不清楚,才会问出这么奇怪的问题。如果理解 UDP 的语义,自然就会知道不能分多次去读同一个包,也不会出现什么不知道包的长度还需要去读一个 header 来判断 body 长度的问题。举个例子,为什么 DNS over TCP 需要两个字节的头部而 DNS over UDP 不需要?因为 UDP 的包长度在接受的时候就是已知的,根本就不可能会使用 io.ReadFull 这个调用。你不去找他的根本问题在哪里,只给一个解决表面问题的答案,他也不会意识到自身的问题在哪里,以后继续犯类似的错误。

你觉得你听明白了问题?你自己说的「 不知道 UDP packet 长度时怎么分配 buffer 大?」你自己想想正确答案应该是啥。
rio
2020-03-01 12:25:05 +08:00
@whoami9894 说白了,这个问题和本站之前出现的「 TCP 粘包问题」如出一辙:不去研究底层原理,一切全靠瞎猜。
whoami9894
2020-03-01 13:55:59 +08:00
@rio
你的回复在说`net.PacketConn.ReadFrom`返回值的第一个就是 body 长度。假设我给个 1024 bytes 的 buffer,实际 packet 有 1400 bytes,ReadFrom 给我返回的一定是 1024。packet 长度确实是确定的,但 ReadFrom 传进去的 buffer 该分配的大小在事先是不知道的(在没有提前协商的前提下)。就好比我需要知道 length 才能调用 Read,你告诉我你调用 Read 就知道 length 了一样。楼主确实看起来没有 UDP 编程经验,所以直接告诉他分配一个足够大的 buffer,不需要像 stream 一样去做额外的上层分包就行了
rio
2020-03-01 14:32:04 +08:00
@whoami9894 你还是没理解这里的问题到底在哪。

「假设我给个 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 底层有基本的理解才能解释。这才是楼主的根本问题。
5thcat
2020-03-01 14:57:20 +08:00
stackoverflow 上有几乎一模一样的问题,read data from a udp socket with an unknown length,解释得更清楚哈哈
5thcat
2020-03-01 15:05:55 +08:00
我觉得可以理解成,缓冲区长度不够,read 再读就读不到, 这是 UDP 协议的行为,跟 go 语言以及 go 语言的库函数没有关系;所以只能和发送方先约定好 包的大小
5thcat
2020-03-01 15:16:52 +08:00
又看到一个回答,recv (或 recvfrom) 系统调用有 flag (MSG_TRUNC 或 MSG_PEEK) ,是可以知道 packet 大小的
whoami9894
2020-03-01 15:52:25 +08:00
@rio
我明白你的意思,楼主用 io.ReadFull 处理 packet,用 header 记录 body length 确实是不理解 UDP,也肯定没有看过 io.ReadFull 的实现,所以楼主的问题是需要补一下计网的知识
至于多大才是足够大,如果是收发自己设计的应用协议,那就按约定的格式来,设计尽量处于 ethernet MTU 的大小范围内。但假如是收发未协商大小的包,比如做 UDP 端口转发,每个 buffer 需要给到 65507
rio
2020-03-01 18:15:33 +08:00
@whoami9894 所以你看这个帖子下最开始的那些「讨论」,根本就是在盲人摸象。
rio
2020-03-01 18:21:02 +08:00
@whoami9894 我最开始是心情好给他点一下,稍微聪明点的人会去想自己哪里理解错了。但显然楼主意识不到自己的问题,那就……随他去吧~ 再也不指点这些萌新了。
hankai17
2020-03-01 21:26:14 +08:00
golang.google.cn/src/io/io.go?h=io 看了一下 ReadAtLeast 意思是 读的总字节数超过了你定的阀值 也正常返回

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/648023

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX