首先,关于字节序的大端与小端的意思,此处不再解释,可以执行百度。
在项目中遇到一个需求,就是将同一结构类型的消息持久化到文件中,这里就遇到一个问题,把结构消息写入文件,在读取怎么避免读取的消息粘包,导致消息解析错误。
这里我就用到二进制的字节序,每次写消息将消息体的长度也写入消息前面,每次读取文件时候,先将字节序存放的长度读取出来,再读取到buffer里面
写入文件
func writeToFile() {
data := []byte("你好,江苏!")
f, er := os.OpenFile("test.dat", os.O_RDWR|os.O_CREATE, 0600)
checkError(er)
defer f.Close()
var err error
var buf bytes.Buffer
//写入消息体长度
err = binary.Write(&buf, binary.BigEndian, int32(len(data)))
checkError(err)
_, err = buf.Write(data)
checkError(err)
_, err = f.Write(buf.Bytes())
checkError(err)
}
读取文件
func readFromFile() {
f, err := os.OpenFile("test.dat", os.O_RDONLY, 0600)
checkError(err)
defer f.Close()
// 建立缓冲读取流,防止数据过大导致内存溢出
reader := bufio.NewReader(f)
var msgSize int32
var er error
//解码大端编码获取数据体长度
er = binary.Read(reader, binary.BigEndian, &msgSize)
checkError(er)
readBuf := make([]byte, msgSize)
_, er = io.ReadFull(reader, readBuf)
checkError(er)
fmt.Println(string(readBuf))
}
如果多次读取,只有每次打开文件,把文件读取指针偏移到上次读取的位置就可以
Seek(readPos, 0)
拓展
在tcp数据交互中,我们除了可以指定读取的分割符号('\n')办法解决数据粘包,我们也可以使用字节序指定消息体长度来读取数据
发送消息前面先发送长度
func main() {
conn, err := net.DialTimeout("tcp", "127.0.0.1:8001", time.Second)
if err != nil {
panic(err)
}
defer conn.Close()
data := []byte("你好,苏州")
errw := binary.Write(conn, binary.BigEndian, int32(len(data)))
if errw != nil {
return
}
conn.Write(data)
// conn.Write([]byte("你好"))
// conn.Write([]byte("中国\n"))
// conn.Write([]byte("\n"))
}
解析消息体长度
func handle(conn net.Conn){
defer func () {
fmt.Println("客户端断开")
conn.Close()
}()
reader := bufio.NewReader(conn)
for {
var err error
var bodyLen int32
// 逻辑每次读取获取数据长度,读取完指定数据长度数据,下次循环,则读取下次数据体长度
err = binary.Read(reader, binary.BigEndian, &bodyLen)
if err != nil {
fmt.Println(err)
return
}
body := make([]byte, bodyLen)
_, err = io.ReadFull(reader, body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
}
网友评论