tcp服务是一个流数据的双工服务,tcp client 端会源源不断的把二进制字节流发发给服务端.假设有下面一个这样的需求,我们需要发送一个用户信息给服务端.我们怎么样知道到那里是一个客户端发来的一个完整的用户信息呢,如果我们不认真处理这个问题就会产生粘包的问题,对于这个问题一般会有下面几种方式来解决.
- 使用分格符
如果我们约定一个分隔符作为我们2个用户数据之间的分隔,如果读到这个符号我们就约定一个完整的数据结构读完了. - 给数据头部添加数据部分长度,先读取长度,再根据这个长度数据剩下的数据.
定义协议
type Packet struct {
Version [2]byte
Length int16
Timestamp int64
Message []byte
}
在这个协议中我们使用前2个字节表示协议版本,2个字节表示数据长度,8个字节表示时间戳,前12字节是属于协议头部部分,剩下的是数据部分.
server端
package main
import (
"net"
"log"
"fmt"
"encoding/binary"
"time"
)
const (
ProtocolVersion = "V1"
)
func main() {
l, err := net.Listen("tcp", ":8090")
if err != nil {
log.Println(err)
}
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
log.Println(err)
}
go handlerConn(conn)
}
}
func handlerConn(conn net.Conn) {
defer conn.Close()
header := make([]byte, 12)
//var u user.User
for {
//read packet header
_, err := conn.Read(header)
if err != nil {
return
}
if fmt.Sprintf("%s", header[:2]) != ProtocolVersion {
log.Println("valid protoc version")
return
}
timestamp := binary.BigEndian.Uint64(header[2:10])
t := time.Unix(int64(timestamp), 0).Format("2006-01-02 03:04:05 PM")
log.Printf("client send data time %s", t)
length := int16(binary.BigEndian.Uint16(header[10:]))
log.Println("data length", length)
//read data
databuf := make([]byte, length)
_, err = conn.Read(databuf)
if err != nil {
return
}
fmt.Printf("%s\n", databuf)
conn.Write(databuf)
}
}
在server端先读取12个字节,然后分别读取出版本号和时间戳,数据长度.然后读取数据.
client 端
package main
import (
"net"
"log"
"time"
"math/rand"
"encoding/binary"
"github.com/myonlyzzy/go-exmaple/example5/pkg/user"
"encoding/json"
)
type User struct {
Name string `json:"name"`
Age int64 `json:"age"`
Msg string `json:"msg"`
}
func main() {
addr := ":8090"
conn, err := net.Dial("tcp", addr)
if err != nil {
log.Println(err)
}
for {
msg := getRandString()
u := user.User{
Name: "john",
Age: 24,
Msg: msg,
}
data, err := json.Marshal(u)
if err != nil {
log.Fatal(err)
}
dataLen := len(data)
b := make([]byte, dataLen+12)
b[0] = 'V'
b[1] = '1'
binary.BigEndian.PutUint64(b[2:10], uint64(time.Now().Unix()))
binary.BigEndian.PutUint16(b[10:12], uint16(dataLen))
copy(b[12:], data)
_, err = conn.Write(b)
if err != nil {
log.Fatal(err)
}
time.Sleep(time.Second)
}
}
func getRandString() string {
length := rand.Intn(5000)
strBytes := make([]byte, length)
for i := 0; i < length; i++ {
strBytes[i] = byte(rand.Intn(26) + 97)
}
return string(strBytes)
}
client端先将表示用户数据的json转成字节流然后取的长度,然后再分别填入头部信息。
总结
这是一个最简单的tcp数据包的结构定义,但基本原理就是这样。