一次性读满 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)
1
back0893 2020-02-27 13:36:28 +08:00
不会像 tcp 一样是流形式发送.
每次接受都是接受发送的一个包. |
3
BOYPT 2020-02-27 13:41:00 +08:00
io.ReadFull 设计是你预知长度,要读取确定长度的数据,超出部分就不要了(这句我猜的)。
如果读未知长度的要弄个 Writer 用 io.Copy |
5
rio 2020-02-27 14:41:53 +08:00
网络基础知识太差又不看文档,net.PacketConn.ReadFrom 返回值的第一个是啥?
|
7
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 了 |
8
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 就好了 |
9
ma6254 2020-02-27 15:42:24 +08:00
@monkeyWie #6
n, err =io.ReadFull(r,head) 如果你把这个 n 打出来看看,会发现是 512 而不是 20,表示他实际读了 512 字节,而不是你想要的只读 20 字节 |
10
monkeyWie OP @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()接收,只能这样做了 |
11
monkeyWie OP @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 } ``` |
12
BBCCBB 2020-02-27 16:17:47 +08:00
|
14
BBCCBB 2020-02-27 16:30:48 +08:00 1
udp 是一次一个包的吧. tcp 才是流式的, 按理说可以知道这次这个 udp 包的大小,
ioutil.ReadAll 能一次性读完, 他里面也是调用你说的 Read 方法啊, 省去了你自己在 buf 不够的时候扩容 buf 这些代码. 它里面都处理好了. |
15
monkeyWie OP @BBCCBB 我现在只能确定 udp 响应包的最大长度,但是真实的响应可能没这么长,我看了 ioutil.ReadAll 源码,是要遇到 io.EOF 或者其它异常(比如超时)才能返回,但是 udp 协议并不存在关闭连接也就不会有 io.EOF ,所以我这里调用的话应该是一直阻塞着直到超时
|
16
BBCCBB 2020-02-27 16:46:49 +08:00
嗷, ioutil.ReadAll udp 我也没试过...
你知道了 udp 包最大字节数, 先就申请一个最大字节数这么大的 buf, 然后 io.ReadFull, 再看 err 是否时 io.EOF, 这样不知是否可行? 只调用 conn.Read() 它的语义不保证一次能读完吧, 虽然一般都没出现过问题. |
17
ma6254 2020-02-27 16:51:55 +08:00
|
18
rio 2020-02-27 17:04:17 +08:00
PacketConn 第一次返回的就是整个包的长度,你还要从哪里去找 body 的长度?
|
19
monkeyWie OP |
20
BBCCBB 2020-02-27 17:17:31 +08:00
|
21
BBCCBB 2020-02-27 17:33:06 +08:00
话说你这个 io.ReadFull(r, buf) 中的 r 是哪里来的呢.
|
22
rio 2020-02-27 17:35:42 +08:00
瞎猜有什么用,UDP 协议基本特性都不知道,回去补习网络基础知识。
|
24
XiaoxiaoPu 2020-02-27 17:52:09 +08:00
UDP 是面向报文(packet)的协议,一次读取对应一个网络包(packet),不存在一次读取不完或多次读取一个报文。理论上 UDP 报文最大长度是 65507 字节,实际一般不会这么多,要看具体的应用层协议。
|
25
BBCCBB 2020-02-27 17:53:20 +08:00
我 google.. 不是调用 UDPConn 的 ReadFromUDP 方法吗.
|
26
monkeyWie OP |
27
mightofcode 2020-02-27 18:53:34 +08:00
一次读 1M,绝不会有问题
|
28
reus 2020-02-27 19:02:21 +08:00
这个是 ReadFull 的一个局限,它调用 Read 时,如果返回的数据比缓冲区还长,那就会丢弃超出的
所以第二次就读不到了 可以套一个 bufio.Reader,这样超出的部分会缓存起来 |
29
tairan2006 2020-02-27 19:22:41 +08:00
udp 一般不都是缓冲区 1024 直接读么…udp 分包特别麻烦,你要用 quic 这种高级一点的协议才行。
|
30
whoami9894 2020-02-27 19:46:39 +08:00
@rio #18 你这逻辑,先后次序都颠倒了
|
32
rio 2020-02-27 19:50:27 +08:00
@whoami9894 你又想说啥
|
33
back0893 2020-02-27 23:22:28 +08:00
我看别人的实现都是初始化一个最大长度的包
直接读取一个包 |
34
p2p 2020-02-28 01:03:48 +08:00
buf := make([]byte, 1500)
|
35
p2p 2020-02-28 01:06:47 +08:00
```go
buf := make([]byte, 1500) for { n, src, err := packet.ReadFrom(buf) ... buff[:n] } ``` |
38
ydongd 2020-02-28 10:35:29 +08:00
最大传输单元( MTU )大概是 1500,具体记不清了。所以在底层读的这里用这么大的 buffer 就够了。如果一个数据大于 1500,在传输层也会分包,所以在接收的地方还需要将数据重组。根据 n, src, err := packet.ReadFrom(buf) 的 src 判断来源。即 @p2p 的方式。
|
41
dawniii 2020-02-28 12:03:40 +08:00
@reus 确实是不一样,但是楼主读 udp 消息为什么要用 ReadFull 呢,buf 给少了会丢数据(就算是加了 bufio.Reader 也是不符合场景的,造成一次消息多次读取,本来都是用 udp 了还要自己做消息分隔符,而且消息的内容长度必须是预设 buf 的整数倍,不然会后那点消息会被卡住),如果一开始预设 buf 给多了也会卡住。
所以读 udp 直接用 ReadFrom 给一个满足业务需求的 buf 大小就行了。 |
42
whoami9894 2020-02-28 15:00:06 +08:00
|
43
reus 2020-02-28 15:45:50 +08:00
@dawniii 读出来之后,解析内容时,就要用到 ReadFull 了,例如 header 固定是 16 字节,那用 16 字节的缓冲区去读,也是正常做法。当然也可以直接用下标去处理,但如果协议发生变化,下标就可能全部变化,基于 io.Reader 的代码,就不需要调整下标,只需要插入多一些读的代码。
|
44
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 大?」你自己想想正确答案应该是啥。 |
45
rio 2020-03-01 12:25:05 +08:00
@whoami9894 说白了,这个问题和本站之前出现的「 TCP 粘包问题」如出一辙:不去研究底层原理,一切全靠瞎猜。
|
46
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 一样去做额外的上层分包就行了 |
47
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 底层有基本的理解才能解释。这才是楼主的根本问题。 |
48
5thcat 2020-03-01 14:57:20 +08:00
stackoverflow 上有几乎一模一样的问题,read data from a udp socket with an unknown length,解释得更清楚哈哈
|
49
5thcat 2020-03-01 15:05:55 +08:00
我觉得可以理解成,缓冲区长度不够,read 再读就读不到, 这是 UDP 协议的行为,跟 go 语言以及 go 语言的库函数没有关系;所以只能和发送方先约定好 包的大小
|
50
5thcat 2020-03-01 15:16:52 +08:00
又看到一个回答,recv (或 recvfrom) 系统调用有 flag (MSG_TRUNC 或 MSG_PEEK) ,是可以知道 packet 大小的
|
51
whoami9894 2020-03-01 15:52:25 +08:00
@rio
我明白你的意思,楼主用 io.ReadFull 处理 packet,用 header 记录 body length 确实是不理解 UDP,也肯定没有看过 io.ReadFull 的实现,所以楼主的问题是需要补一下计网的知识 至于多大才是足够大,如果是收发自己设计的应用协议,那就按约定的格式来,设计尽量处于 ethernet MTU 的大小范围内。但假如是收发未协商大小的包,比如做 UDP 端口转发,每个 buffer 需要给到 65507 |
52
rio 2020-03-01 18:15:33 +08:00
@whoami9894 所以你看这个帖子下最开始的那些「讨论」,根本就是在盲人摸象。
|
53
rio 2020-03-01 18:21:02 +08:00
@whoami9894 我最开始是心情好给他点一下,稍微聪明点的人会去想自己哪里理解错了。但显然楼主意识不到自己的问题,那就……随他去吧~ 再也不指点这些萌新了。
|
54
hankai17 2020-03-01 21:26:14 +08:00
golang.google.cn/src/io/io.go?h=io 看了一下 ReadAtLeast 意思是 读的总字节数超过了你定的阀值 也正常返回
|