【Golang】网络开发:TCP粘包

粘包

说到TCP就必须得处理它的粘包了。关于粘包的知识可以查看往期的内容:

【swoole.1.04】udp协议通信和粘包问题

制造一个粘包情景

粘包的产生情况就是TCP发送数据太快在缓冲区积累导致的。所以要制造一个粘包,只要将之前的发送数据操作模拟并发多次即可

修改

_, err = conn.Write([]byte(cname + ": " + inputInfo)) // 发送数据

for i := 0; i < 10; i++ {
	_, err = conn.Write([]byte(cname + ": " + inputInfo)) // 发送数据
}
输出

server:

2020/03/17 15:52:11 开始创建服务器
2020/03/17 15:52:11 监听完成
2020/03/17 15:52:11 开始阻塞监听连接事件
2020/03/17 15:52:14 有新的连接
2020/03/17 15:52:16 收到客户端发来的数据: 1584431534: 我要粘包
2020/03/17 15:52:16 发送数据成功: 1584431534: 我要粘包
2020/03/17 15:52:16 收到客户端发来的数据: 1584431534: 我要粘包1584431534: 我要粘包1584431534: 我要粘包1584431534: 我要粘包1584431534: 我要粘包15844315
2020/03/17 15:52:16 发送数据成功: 1584431534: 我要粘包1584431534: 我要粘包1584431534: 我要粘包1584431534: 我要粘包1584431534: 我要粘包15844315

client:

2020/03/17 15:52:14 开始创建客户端
2020/03/17 15:52:14 创建客户端完成,当前客户端名: 1584431534
2020/03/17 15:52:14 等待新的指令
我要粘包
2020/03/17 15:52:16 已发送: 我要粘包
2020/03/17 15:52:16 开始阻塞等待服务端返回数据
2020/03/17 15:52:16 已发送: 1584431534: 我要粘包
2020/03/17 15:52:16 等待新的指令

粘包的处理

EOF就跳过了,这里主要学习一下怎么封装包头和拆包

为不了解的人简单说一下,粘包处理在这里是比php简单的。上面说过,粘包的原因是在传输数据的过程中缓冲区写入过快来不及读取或发送导致的。解决办法之一就是将数据进行封装,客户端在发送数据前封装一段固定长度的包头来标示这个数据有多长;服务端在socket可写回调时,先取包头查看真正的数据究竟有多长,然后取固定长度的缓冲区数据进行收包,以保证能分开黏住的数据包。

创建封包和拆包的函数
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
}
修改服务端和客户端发包和收包的代码,使用封包工具和拆包工具进行发包和收包

server:

func TcpServer(address string) {
	//	创建连接
	listener, err := net.Listen("tcp", address) // 监听端口连接
	if err != nil {
		log.Println("监听端口出错:", err)
	} else {
		log.Println("监听完成")
	}
	log.Println("开始阻塞监听连接事件")

	for {
		// 阻塞监听连接事件
		conn, err := listener.Accept() // 阻塞监听连接事件
		if err != nil {
			log.Println("接受socket连接出错:", err)
			return
		} else {
			log.Println("有新的连接")
		}
		go doRead(conn)
	}
}

func doRead(conn net.Conn) {

	// 数据交互过程
	reader := bufio.NewReader(conn) // 创建io缓冲区
	for {
		msg, err := Decode(reader) // 使用分包工具拆包
		if err != nil {
			log.Println("读取数据出错:", err)
			break
		}
		log.Println("收到客户端发来的数据:", msg)
		res, err := Encode(msg) // 使用封包工具打包数据
		_, err = conn.Write(res)
		if err != nil {
			log.Println("发送数据出错:", err)
		} else {
			log.Println("发送数据成功:", msg)
		}
	}
}

client:

func TcpClient(address string) {
	var cname string
	conn, err := net.Dial("tcp", address) // 创建一个连接用的客户端
	if err != nil {
		log.Println("err :", err)
		return
	} else {
		cname = strconv.Itoa(int(time.Now().Unix()))
		log.Println("创建客户端完成,当前客户端名:", cname)
		msg, err := Encode(cname + ": 我走了")
		if err != nil {
			fmt.Println("转码失败")
		}
		defer func() {
			_, _ = conn.Write(msg)
			err := conn.Close() // 关闭连接
			if err != nil {
				log.Println("关闭连接失败:", err)
			} else {
				log.Println("退出了")
			}
		}()
	}
	inputReader := bufio.NewReader(os.Stdin) // 创建一个命令行输入的缓冲读取器
	for {
		log.Println("等待新的指令")
		input, _ := inputReader.ReadString('\n') // 读取用户输入
		inputInfo := strings.Trim(input, "\r\n")
		if strings.ToLower(inputInfo) == "exit" { // 输入exit退出
			return
		}
		for i := 0; i < 10; i++ {
			msg,_ := Encode(cname + ": " + inputInfo)
			_, err = conn.Write(msg) // 发送数据
		}
		if err != nil {
			return
		} else {
			log.Println("已发送:", inputInfo)
		}
		log.Println("开始阻塞等待服务端返回数据")
		reader := bufio.NewReader(conn)
		msg, err := Decode(reader)
		if err != nil {
			log.Println("获取数据失败:", err)
			return
		} else {
			log.Println("已发送:", msg)
		}
	}
}

binary操作

使用binary进行字节序封包

// LittleEndian is the little-endian implementation of ByteOrder.
var LittleEndian littleEndian // 小端

// BigEndian is the big-endian implementation of ByteOrder.
var BigEndian bigEndian // 大端

func Read(r io.Reader, order ByteOrder, data interface{}) error {}

从一个数据包内读取数据

func Write(w io.Writer, order ByteOrder, data interface{}) error {}

写入一个io.writer

小结

golang-网络编程

程序幼儿员-龚学鹏
请先登录后发表评论
  • latest comments
  • 总共0条评论