最近搜了下,粘包这个词并不是一个术语,这个词是国内对于应用层中对 stream 进行 decode 时一种现象的描述。从一贯的角度来看的话,粘这个字在用于动词属性的时候,读作 zhān,而用于形容词属性的时候,读作 nián。当然,现在对于两种区别已经渐渐模糊了,所以怎么读都可以,只要不影响理解即可。
粘包这个问题总是会和 TCP 在一起,但实际和 TCP 毫无关系。为什么总有那么多人会认为粘包是 TCP 问题,那是因为在 UDP 中不会有这个问题,所以会认为这是 TCP 协议的问题。
TCP 的数据传输是流模式,在连接未断开的情况下可以持续的收和发。对于 TCP 而言,数据就是流,它不关心有多少条消息,每个消息是多大,它只负责可靠地将数据传递到目的地。
但在应用层,我们必须要想办法将这些数据拆分,还原为可读的信息。
这个时候,我们需要知道每一条消息的边界,这才让流中的数据有了“包”的概念。这个时候,如果使用的是 UDP,由于 UDP 协议定义了边界,所以在应用层直接读取消息即可,就不需要去处理数据的拆分问题。于是,有人会下意识的认为 TCP 没有做好,它帮助应用层去进行消息边界的划分,便说 TCP 会产生“粘包”问题。
需要明确的是,“粘包”问题自始至终都是应用层该去解决的问题,传输层需要的是尽可能的增加吞吐,而不是为应用层去服务。换句话说,UDP 只是恰好符合了应用层的使用方式,而不是专门为了应用层才这么设计的。这种错觉是典型的用上层的视角去理解底层。
下面,就如标题所说的那样,用
golang
来展示什么是粘包。先准备一个
server.go
,用来接收 TCP 数据。package main import ( "log" "net" ) func main() { l, err := net.Listen("tcp", ":80") if err != nil { log.Fatalln(err) } defer l.Close() buf := make([]byte, 1024) // buffer for { conn, err := l.Accept() if err != nil { log.Println(err) continue } // read data from tcp connection len := 0 for err == nil { len, err = conn.Read(buf) log.Println("print recv content:") log.Printf("%s", buf[:len]) } conn.Close() } }
然后,准备一个
client.go
,用来发送 TCP 数据。package main import ( "log" "net" "time" ) func main() { conn, err := net.Dial("tcp", ":80") if err != nil { log.Fatalln(err) } for { conn.Write([]byte("hello")) time.Sleep(time.Microsecond * 10) } }
下面看一下现象:
2024/11/27 11:39:08 print recv content: 2024/11/27 11:39:08 hellohellohellohello 2024/11/27 11:39:08 print recv content: 2024/11/27 11:39:08 hellohello 2024/11/27 11:39:08 print recv content: 2024/11/27 11:39:08 hellohellohello 2024/11/27 11:39:08 print recv content: 2024/11/27 11:39:08 hello 2024/11/27 11:39:08 print recv content: 2024/11/27 11:39:08 ^Csignal: interrupt
可以发现每次都输出数量不一的
hello
这些连在一起输出的
hello
就是所谓的粘包问题。至于如何处理这些粘包,那就下一次再谈了。