TCP粘包原理及解决方案


一、粘包是什么

​ 两个程序能够互相通信是采用了套接字(socket)技术,socket在发送端和接收端都有个缓存机制,发送端在把需要发送的数据先放在缓存上,等数据超过缓存大小时,就会打包发给接收端;接收端接到数据也会先放到缓存,再根据应用程序(recv/read)去读取这些数据,直到读完缓存上的数据。

​ TCP是一个流协议,TCP只保证把要发送的数据按包序号完整的发送给接收端,接收端读取数据的时候会按应用程序上设置好的大小去读取,假如接收端每次按照300字节大小去读,实际业务需要数据600个字节,那么接收端读取一次是没有把应用需要的数据读取完整,而需读取两次;再假如实际业务需要数据100个字节,接收端按照300字节大小去读取,会把业务上分为两个含义数据一次就读取出来了,这就产生了粘包的问题,所以粘包问题的本质就是数据读取边界错误所致。

二、字节序

​ 字节序是在内存中字节存储顺序,分为大端序(网络字节序)和小端序(主机字节序);

​ 高位字节:在ASCII上开始的字节,如0x00

​ 低位字节:在ASCII上最后的字节,如0x7F

​ 大端序:高位字节存入低地址;
​ 小端序:低位字节存入低地址;

三、解决方案

​ 上面所说的应用数据,是在TCP数据包里面的,应用数据的数据格式由用户自己协定,比如http协议,有头部和包体,头部有设定数据长度,包体则是实际数据 。

​ 那么解决粘包问题思路:

​ 1,设置头部信息,长度固定;

​ 2,使用特定字符作为分隔符,用来分割数据边界问题;

​ 3,添加除头部信息的数据长度。

下面用golang代码结合上面3点思路解决粘包问题:

发送端封装数据 client.go

package main

import (
	"encoding/binary"
	"fmt"
	"net"
)

func main() {
	//定义头部信息固定长度为10,分隔符两个字符:0x43, 0x53
	headData := []byte{0x43, 0x53, 0, 0, 0, 0, 0, 0, 0, 0}

	//实际数据
	context := "测试内容"

	//实际数据长度用4个字节,小端序方式来储存
	binary.LittleEndian.PutUint32(headData[2:6], uint32(len(context)))

	//数据包(头部信息+实际数据)
	data := make([]byte, 0)
	data = append(data, headData...)
	data = append(data, context...)
	fmt.Println(data) //输出 [67 83 12 0 0 0 0 0 0 0 230 181 139 232 175 149 229 134 133 229 174 185]

	//67 83 12 0 0 0 0 0 0 0 为头部信息;230 181 139 232 175 149 229 134 133 229 174 185 为实际数据

	conn, err := net.Dial("tcp", "192.168.3.116:9010")
	if err != nil {
		fmt.Println("connect err=", err)
		return
	}
	_, err = conn.Write(data)
	if err != nil {
		fmt.Println("send data failed, err= ", err)
		return
	}
}

接收端拆分数据 server.go

package main

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"net"
)

func main() {
	fmt.Println("服务器开始监听......")
	listen, err := net.Listen("tcp", "192.168.3.116:9010")
	if err != nil {
		fmt.Println("listen err=", err)
		return
	}

	// 延迟关闭
	defer listen.Close()

	// 循环等待客户端连接
	for {
		fmt.Println("循环等待客户端连接...")
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("Accept() err=", err)
		}
		// 创建goroutine处理客户端连接
		go process(conn)
	}
}

//处理客户端连接
func process(conn net.Conn) {
	defer func() {
		fmt.Println("conn.Close")
		conn.Close()
	}()
	reader := bufio.NewReader(conn)
	for {
		msg, err := Decode(reader)
		if err != nil {
			fmt.Println("decode failed, err = ", err)
			return
		}
		fmt.Println("收到数据", msg)
	}
}

func Decode(reader *bufio.Reader) (string, error) {
  var err error
  // 读取前10个字节的数据(表示数据包头部的信息)
  // peek操作只读数据,但不会移动数据位置!(没有数据则阻塞)
  headData, err := reader.Peek(10)
  fmt.Println(headData)//[67 83 12 0 0 0 0 0 0 0]
  if err != nil {
    return "", err
  }

  //验证头部分割符
  if headData[0] != 0x43 || headData[1] != 0x53 {
    err = errors.New(fmt.Sprintf("headData[0]: %d,headData[1]: %d", headData[0], headData[1]))
    return "", err
  }

  headLen := len(headData)
  //验证头部长度
  if headLen < 10 {
    err = errors.New(fmt.Sprintf("headData len :%d", headLen))
    return "", err
  }

  //获取实际数据长度byte
  lengthByte := headData[2:6]//[12 0 0 0]

  lengthBuf := bytes.NewBuffer(lengthByte)
  var dataLen int32
  //小端序方式转换实际数据长度数字
  err = binary.Read(lengthBuf, binary.LittleEndian, &dataLen)
  if err != nil {
    return "", err
  }

  //数据包长度(头部信息长度+实际数据长度)
  packLen := 10 + dataLen
  pack := make([]byte, packLen)

  // 判断缓存数据是否满足一个完整数据包
  buffLen := int32(reader.Buffered())
  if buffLen < packLen {
    cacheBuff := make([]byte, 0)
    dataBuff := make([]byte, buffLen, buffLen)
    for {
      n, err := reader.Read(dataBuff)
      cacheBuff = append(cacheBuff, dataBuff[0:n]...)
      if err == nil {
        if int32(len(cacheBuff)) < packLen {
          num := packLen - int32(len(cacheBuff))
          dataBuff = make([]byte, num, num)
          continue
        }
      } else {
        return "", err
      }
      pack = cacheBuff
      break
    }
  } else {
    // 读取整个数据包
    _, err = reader.Read(pack)
    if err != nil {
      return "", err
    }
  }
  // str, _ := DecodeData(pack[10:])
  str := pack[10:]
  fmt.Println(string(str)//输出:测试内容
  return string(str), nil
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM