1.tcp和udp的golang服务端示例代码
udp https://github.com/fwhezfwhez/TestX/tree/master/test_udp
tcp https://github.com/fwhezfwhez/TestX/tree/master/test_tcp/basic
2. 一些需要注意到的区别
udp与tcp的一些概念定义和使用场景不是本文的讨论范畴,自行百度。
2.1 tcp需要自主拆包组包
net.Conn
func NewByte(byts ...byte) []byte {
var rs = make([]byte, 0 , 512)
rs = append(rs, byts...)
return rs
}
func main(){
...
// var con net.Conn
conn.Write([]byte(NewByte(1,2,3,4,5,6)))
conn.Write([]byte(NewByte(1,2,3,4,5,6)))
conn.Write([]byte(NewByte(1,2,3,4,5,6)))
conn.Write([]byte(NewByte(1,2,3,4,5,6)))
}
另一端收到的数据包是:
[1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6]
所以,对协议的定义和拆组,都需要有相应的手段,常见的是:
- 消息定长
- 消息头记录消息长度
这两种方法,第一种因为消息定长,截取时按照长度截取即可,协议简单,缺点是消息长度取消息可能达到的最大值,未达到长度的消息将会补0,即很浪费空间。第二种需要设定好协议的内容,比如
[4]byte 消息总长度(不包括消息总长度本身的4)
[]byte 消息实体
读取时,先读[4]byte,再读 [4]byte 经过大端/ 小端转换成的int长度,对应的消息实体。
相比较之下,udp不需要拆组包
udp的接口不包含常用的Read()和Write(),而是ReadFrom()和WriteTo(),每次读取,都会把单次的消息读完。
假设连续发送和tcp相同的情形.
func NewByte(byts ...byte) []byte {
var rs = make([]byte, 0 , 512)
rs = append(rs, byts...)
return rs
}
func main(){
...
// var conn net.PackConn
conn.WriteTo([]byte(NewByte(1,2,3,4,5,6)))
conn.WriteTo([]byte(NewByte(1,2,3,4,5,6)))
conn.WriteTo([]byte(NewByte(1,2,3,4,5,6)))
conn.WriteTo([]byte(NewByte(1,2,3,4,5,6)))
}
另一端会收到:
[1,2,3,4,5,6],[1,2,3,4,5,6],[1,2,3,4,5,6],[1,2,3,4,5,6]
看起来很美,但是有两个致命问题,将在后面描述。
i. 预设定的buffer一旦小了,直接抛错,不可循环ReadFrom:
ii. 收到的消息虽然分块好了,但是顺序不保证,且不保证收到
2.2 udp的乱序描述
常说udp不保证顺序可靠,这句话讲的还不够细致。
当一端发送:
[1,2,3,4,5,6], [7,8,9]
另一端可能得到:
[7,8,9], [1,2,3,4,5,6]
乱序指的就是这种,最小单元为消息块的乱序,而不是像这样:
[1, 2, 5,6,4,3], [7,8,9]
又或是:
[1,2,7,8] [3,4,5,9,6]
消息块内部的混乱,不管是udp还是tcp都不存在的,tcp里遇到这种情况,会重发,udp里这种情况会丢弃,也就是常说的丢包
2.3 udp与tcp的连接,后者支持分段读取,前者不支持
net.ConnRead([]byte) error
// tcp 可以这样分段读,先读头部信息[4]byte,再读载荷
var info = make([]byte,4, 216)
conn.Read(info)
length := binary.BigEndian.Uint32(info)
var content = make([]byte, lengh, length)
conn.Read(content)
udp的读取方式
以tcp的读取方式来读取udp消息,会抛出 buffer size too small error,正确的读法是:
var buffer = make([]byte, 4096, 4096)
n,_ := conn.Read(buffer)
buffer = buffer[0:n]
和前面说的一致,udp的读取,一直需要预设一个buffer大小,不可过小