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)
7331 次点击
所在节点    Go 编程语言
54 条回复
back0893
2020-02-27 13:36:28 +08:00
不会像 tcp 一样是流形式发送.
每次接受都是接受发送的一个包.
monkeyWie
2020-02-27 13:40:26 +08:00
@back0893 那如果不知道包的大小咋办,一般 body 的长度都放在 head 里,所以先读了一次 head
BOYPT
2020-02-27 13:41:00 +08:00
io.ReadFull 设计是你预知长度,要读取确定长度的数据,超出部分就不要了(这句我猜的)。

如果读未知长度的要弄个 Writer 用 io.Copy
monkeyWie
2020-02-27 13:42:40 +08:00
@BOYPT 不会的,TCP 协议的话读满之后还可以接着读后面的报文
rio
2020-02-27 14:41:53 +08:00
网络基础知识太差又不看文档,net.PacketConn.ReadFrom 返回值的第一个是啥?
monkeyWie
2020-02-27 15:14:45 +08:00
@rio 大佬,第一个返回值不是读取到的字节数吗,和这个问题有啥关联啊?还请赐教
ma6254
2020-02-27 15:29:08 +08:00
查下文档 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 了
ma6254
2020-02-27 15:36:39 +08:00
@monkeyWie 你就用普通的 read 就可以了

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

假设你的 head 长度固定,body 长度存在 head 的某个字段里,那就先 make 一个和 head 等长的[]byte
然后 read,得到长度,再 make 一个对应长度的 body []byte,就可以了,如果末尾还有定长校验字就再 read 就好了
ma6254
2020-02-27 15:42:24 +08:00
@monkeyWie #6

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

如果你把这个 n 打出来看看,会发现是 512 而不是 20,表示他实际读了 512 字节,而不是你想要的只读 20 字节
monkeyWie
2020-02-27 15:55:25 +08:00
@ma6254 谢谢老哥耐心解答,我找到原因了,原来一直有 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()接收,只能这样做了
monkeyWie
2020-02-27 15:58:29 +08:00
@ma6254 n 我打印出来过了,都是 20 😂,其实是因为有 err,io.ReadFull()里面把 err 忽略掉了。
```
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {
if len(buf) < min {
return 0, ErrShortBuffer
}
for n < min && err == nil {
var nn int
nn, err = r.Read(buf[n:])
// nn=20,err!=nil
n += nn
}
if n >= min {
//读到了指定的字节数就置空 err
err = nil
} else if n > 0 && err == EOF {
err = ErrUnexpectedEOF
}
return
}
```
BBCCBB
2020-02-27 16:17:47 +08:00
@ma6254 你说返回 512 吓的我又去翻了一遍资料...

@monkeyWie 你可以直接用 ioutil.ReadAll
monkeyWie
2020-02-27 16:25:14 +08:00
@BBCCBB 用 ioutil.ReadAll 也不对呀,不是读完这次就 EOF,后面还有呢
BBCCBB
2020-02-27 16:30:48 +08:00
udp 是一次一个包的吧. tcp 才是流式的, 按理说可以知道这次这个 udp 包的大小,

ioutil.ReadAll 能一次性读完, 他里面也是调用你说的 Read 方法啊, 省去了你自己在 buf 不够的时候扩容 buf 这些代码. 它里面都处理好了.
monkeyWie
2020-02-27 16:36:05 +08:00
@BBCCBB 我现在只能确定 udp 响应包的最大长度,但是真实的响应可能没这么长,我看了 ioutil.ReadAll 源码,是要遇到 io.EOF 或者其它异常(比如超时)才能返回,但是 udp 协议并不存在关闭连接也就不会有 io.EOF ,所以我这里调用的话应该是一直阻塞着直到超时
BBCCBB
2020-02-27 16:46:49 +08:00
嗷, ioutil.ReadAll udp 我也没试过...

你知道了 udp 包最大字节数, 先就申请一个最大字节数这么大的 buf, 然后 io.ReadFull, 再看 err 是否时 io.EOF, 这样不知是否可行? 只调用 conn.Read() 它的语义不保证一次能读完吧, 虽然一般都没出现过问题.
ma6254
2020-02-27 16:51:55 +08:00
@BBCCBB #12 我说的 512 是楼主帖子里写的”一次性读满 512 个字节没问题” 推测为他的一个包为 512 字节然后 flush 一次


@monkeyWie #10 为什么一定要用 ReadFull(),用标准 Reader 的 Read 就可以了,当作流式用也一样没问题
rio
2020-02-27 17:04:17 +08:00
PacketConn 第一次返回的就是整个包的长度,你还要从哪里去找 body 的长度?
monkeyWie
2020-02-27 17:06:59 +08:00
@BBCCBB udp 读估计每次都是一个完整的 udp 报文,不存在和 tcp 那样用 Read()一次性读不完,所以我现在只要用最大字节数的 buf 去 Read()一次就行了,不用 io.ReadFull 了,毕竟响应的报文不一定有这么长,这个方法肯定阻塞。

@ma6254 现在没有 ReadFull()了,可能是 TCP 写多了,之前以为 UDP 和 TCP 一样可以这样读。 🤓
BBCCBB
2020-02-27 17:17:31 +08:00
看了一圈,没找到啥可以知道要接收的 udp 包大小的... 只能先 new 一个足够大的 buf 去接收了...


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

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

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

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

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

© 2021 V2EX