粘包产生的缘由有发送方和接收方两方面:
服务端代码以下:html
// socket_test/server/main.go func process(conn net.Conn) { defer conn.Close() // 使用bufio的读缓冲区(防止系统缓冲区溢出) reader := bufio.NewReader(conn) var buf [1024]byte for { n, err := reader.Read(buf[:]) if err == io.EOF { break } if err != nil { fmt.Println("读取客户数据失败,err:", err) break } recvData := string(buf[:n]) fmt.Println("收到client发来的数据:", recvData) } } func main() { listen, err := net.Listen("tcp", "127.0.0.1:12345") if err != nil { fmt.Println("监听失败, err:", err) return } defer listen.Close() for { conn, err := listen.Accept() if err != nil { fmt.Println("创建会话失败, err:", err) continue } // 单独建一个goroutine来维护客户端的链接(不会阻塞主线程) go process(conn) } }
客户端代码以下:算法
// socket_test/client/main.go func main() { conn, err := net.Dial("tcp", "127.0.0.1:12345") if err != nil { fmt.Println("链接服务器失败, err", err) return } defer conn.Close() for i := 0; i < 20; i++ { msg := `Hello, Hello. How are you?` conn.Write([]byte(msg)) } }
将以上服务器和客户端的代码分别编译并运行,结果以下:后端
收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you? 收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you? 收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you? 收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you? 收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?
咱们在客户端发送了10次数据,但在服务器在输出了5次,多条数据“粘”到了一块儿。bash
基于以前的分析,咱们只要给每一个数据包封装一个包头,包头里只需包含数据包长度的信息,这样接收方在收到数据时就能够先读取包头的数据,经过包头就能够定位数据包的边界,粘包问题也就迎刃而解。说白了,就是须要咱们定义一个应用之间通讯的协议,完成对每一个消息的编码和解码。代码以下:服务器
// socket_test/proto/proto.go package proto import ( "bufio" "bytes" "encoding/binary" ) func Encode(msg string) ([]byte, error) { length := int32(len(msg)) // 建立一个数据包 pkg := new(bytes.Buffer) // 写入数据包头,表示消息体的长度 err := binary.Write(pkg, binary.LittleEndian, length) if err != nil { return nil, err } // 写入消息体 err = binary.Write(pkg, binary.LittleEndian, []byte(msg)) if err != nil { return nil, err } return pkg.Bytes(), nil } func Decode(reader *bufio.Reader) (string, error) { // 读取前4个字节的数据(表示数据包长度的信息) // peek操做只读数据,但不会移动读取位置!!! lengthByte, _ := reader.Peek(4) // 将前4个字节数据读入字节缓冲区 lengthBuf := bytes.NewBuffer(lengthByte) var dataLen int32 // 读取数据包长度 err := binary.Read(lengthBuf, binary.LittleEndian, &dataLen) if err != nil { return "", err } // 判断数据包的总长度是否合法 if int32(reader.Buffered()) < dataLen + 4 { return "", err } pack := make([]byte, 4 + dataLen) // 读取整个数据包 _, err = reader.Read(pack) if err != nil { return "", err } return string(pack[4:]), nil }
客户端在发送消息时调用编码函数对消息进行编码,代码以下:
// socket_test/client2/main.go网络
package main import ( "fmt" "../proto" "net" ) func main() { conn, err := net.Dial("tcp", "127.0.0.1:8888") if err != nil { fmt.Println("connect err=", err) return } fmt.Println("conn suc=", conn) for i := 0; i < 10; i++ { data, err := proto.Encode("hello, server!") if err != nil { fmt.Println("Encode failer, err = ", err) return } _, err = conn.Write(data) if err != nil { fmt.Println("send data failed, err= ", err) return } } }
服务器接收消息时调用解码函数对消息进行解码,代码以下:socket
// socket_test/server2/main.go package main import ( "bufio" "fmt" "../proto" "io" "net" ) func process(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) for { msg, err := proto.Decode(reader) if err == io.EOF { return } if err != nil { fmt.Println("decode failed, err = ", err) return } fmt.Println("收到数据", msg) } } func main() { fmt.Println("服务器开始监听......") listen, err := net.Listen("tcp", "127.0.0.1:8888") if err != nil { fmt.Println("listen err=", err) return } fmt.Printf("listen suc=%v\n", listen) // 延迟关闭 defer listen.Close() // 循环等待客户端链接 for { fmt.Println("循环等待客户端链接...") conn, err := listen.Accept() if err != nil { fmt.Println("Accept() err=", err) } else { fmt.Printf("Accept() suc=%v, 客户端ip=%v\n", conn, conn.RemoteAddr().String()) } // 建立goroutine处理客户端链接 go process(conn) } }
测试结果以下:tcp
收到client发来的数据: hello, server! 收到client发来的数据: hello, server! 收到client发来的数据: hello, server! 收到client发来的数据: hello, server! 收到client发来的数据: hello, server! 收到client发来的数据: hello, server! 收到client发来的数据: hello, server! 收到client发来的数据: hello, server! 收到client发来的数据: hello, server! 收到client发来的数据: hello, server!
能够看到,此时服务器接收的数据已经没有了粘包。函数
我是lioney,年轻的后端攻城狮一枚,爱钻研,爱技术,爱分享。
我的笔记,整理不易,感谢阅读、点赞和收藏。
文章有任何问题欢迎你们指出,也欢迎你们一块儿交流后端各类问题!