1. 服务端
package main import ( "bufio" "fmt" "net" "os" "strings" ) // tcp server端 func processConn(conn net.Conn) { defer conn.Close() // 3. 与客户端通信 var tmp [128]byte reader := bufio.NewReader(os.Stdin) for { n, err := conn.Read(tmp[:]) if err != nil { fmt.Println("read from conn failed,err:", err) return } fmt.Println(string(tmp[:n])) fmt.Print("请回复:") msg, _ := reader.ReadString(' ') // 读到换行 msg = strings.TrimSpace(msg) if msg == "exit" { break } conn.Write([]byte(msg)) } } func main() { // 1. 本地端口启动服务 listener, err := net.Listen("tcp", "127.0.0.1:20000") if err != nil { fmt.Println("start tcp server on 127.0.0.1:20000 failed, err:", err) return } // 2. 等待别人来跟我建立连接 for { conn, err := listener.Accept() if err != nil { fmt.Println("accept failed,err:", err) return } go processConn(conn) } }
2. 客户端
package main import ( "bufio" "fmt" "net" "os" "strings" ) // tcp client func main() { // 1. 与server端建立连接 conn, err := net.Dial("tcp", "127.0.0.1:20000") if err != nil { fmt.Println("dial 127.0.0.1:20000 failed,err:", err) return } // 2. 发送数据 reader := bufio.NewReader(os.Stdin) for { fmt.Print("请说话:") msg, _ := reader.ReadString(' ') // 读到换行 msg = strings.TrimSpace(msg) if msg == "exit" { break } conn.Write([]byte(msg)) } conn.Close() }
3. 黏包
为什么会出现粘包
主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。
“粘包”可发生在发送端也可发生在接收端:
- 由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
- 接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。
解决办法
出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。
封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入”包尾”内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。
我们可以自己定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度。
package main import ( "bufio" "fmt" "io" "net" proto "code.oldboyedu.com/day08/09nianbao_jiejue/protocol" ) // socket_stick/server/main.go func process(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) for { recvStr, err := proto.Decode(reader) if err == io.EOF { return } if err != nil { fmt.Println("decode failed,err:", err) return } fmt.Println("收到client发来的数据:", recvStr) } } func main() { listen, err := net.Listen("tcp", "127.0.0.1:30000") if err != nil { fmt.Println("listen failed, err:", err) return } defer listen.Close() for { conn, err := listen.Accept() if err != nil { fmt.Println("accept failed, err:", err) continue } go process(conn) } }
package main import ( "fmt" "net" proto "code.oldboyedu.com/day08/09nianbao_jiejue/protocol" ) // 黏包 client // socket_stick/client/main.go func main() { conn, err := net.Dial("tcp", "127.0.0.1:30000") if err != nil { fmt.Println("dial failed, err", err) return } defer conn.Close() for i := 0; i < 20; i++ { msg := `Hello, Hello. How are you?` // 调用协议编码数据 b, err := proto.Encode(msg) if err != nil { fmt.Println("encode failed,err:", err) return } conn.Write(b) // time.Sleep(time.Second) } }
package proto import ( "bufio" "bytes" "encoding/binary" ) // Encode 将消息编码 func Encode(message string) ([]byte, error) { // 读取消息的长度,转换成int32类型(占4个字节) var length = int32(len(message)) var pkg = new(bytes.Buffer) //Buffer缓冲区 // 写入消息头 err := binary.Write(pkg, binary.LittleEndian, length) if err != nil { return nil, err } // 写入消息实体 err = binary.Write(pkg, binary.LittleEndian, []byte(message)) //binary.LittleEndian小段(低位在最右边) if err != nil { return nil, err } return pkg.Bytes(), nil } // Decode 解码消息 func Decode(reader *bufio.Reader) (string, error) { // 读取消息的长度 lengthByte, _ := reader.Peek(4) // 读取前4个字节的数据 lengthBuff := bytes.NewBuffer(lengthByte) var length int32 err := binary.Read(lengthBuff, binary.LittleEndian, &length) if err != nil { return "", err } // Buffered返回缓冲中现有的可读取的字节数。 if int32(reader.Buffered()) < length+4 { return "", err } // 读取真正的消息数据 pack := make([]byte, int(4+length)) _, err = reader.Read(pack) if err != nil { return "", err } return string(pack[4:]), nil }