【Go】详解TCP黏包

目录

1、TCP黏包现象

2、为什么会出现粘包


1、TCP黏包现象

服务端代码如下:

package main

import (
	"bufio"
	"fmt"
	"io"
	"net"
)

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	var buf [1024]byte
	for {
		n, err := reader.Read(buf[:])
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Println("read from client failed, err:", err)
			break
		}
		recvStr := string(buf[:n])
		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"
)

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?`
		conn.Write([]byte(msg))
	}
}

 多条数据“粘”到了一起。

2、为什么会出现粘包

主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。

TCP黏包是指在使用TCP协议进行数据传输时,发送方所发送的数据包在接收方接收时被粘成一个或多个大的数据块。这种情况通常发生在发送端连续发送多个小数据包,而接收端并非一次性接收所有数据包,而是以较大的数据块进行接收的时候。

造成TCP黏包的主要原因有两个:

  1. 数据发送速度和接收速度不匹配:发送方可能会连续发送多个小数据包,但接收方可能无法及时接收所有的数据包,导致多个数据包被合并在一起接收。

  2. TCP协议的运行机制:TCP是基于字节流的协议,它不保留消息的边界信息。当发送方的数据包较小且发送速度较快时,接收方可能会将多个数据包合并成一个较大的数据块进行接收。

为了解决TCP黏包问题,可以采取以下几种方法:

  1. 消息边界标识:在数据包中添加特定的字符或者标识符作为消息的边界,接收方通过查找边界标识来分割数据包,从而正确解析消息。

  2. 固定长度消息:发送方在发送数据前,将数据按照固定的长度进行切割,接收方按照相同的长度进行接收和解析。

  3. 消息头部长度字段:在消息的头部添加一个字段来表示消息的长度,接收方首先读取长度字段,然后按照该长度进行接收和解析。

  4. 使用应用层协议:可以在TCP之上使用应用层协议,如HTTP、WebSocket等。这些协议通常有明确定义的消息格式,可以通过协议自身的机制来解决黏包问题。

采取以上方法之一或者结合使用,可以有效地解决TCP黏包问题。具体选择哪种方法要根据实际情况和需求进行考虑。

我们可以自己定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度。

// socket_stick/proto/proto.go
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)
	// 写入消息头
	err := binary.Write(pkg, binary.LittleEndian, length)
	if err != nil {
		return nil, err
	}
	// 写入消息实体
	err = binary.Write(pkg, binary.LittleEndian, []byte(message))
	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
}

服务端代码如下:

package main

import (
	"bufio"
	"fan/proto"
	"fmt"
	"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 msg failed, err:", err)
			return
		}
		fmt.Println("收到client发来的数据:", msg)
	}
}

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 (
	"fan/proto"
	"fmt"
	"net"
)

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?`
		data, err := proto.Encode(msg)
		if err != nil {
			fmt.Println("encode msg failed, err:", err)
			return
		}
		conn.Write(data)
	}
}

猜你喜欢

转载自blog.csdn.net/fanjufei123456/article/details/132021082