大佬们都说tcp有黏包的问题,tcp却说:我冤枉!

2023-03-01  本文已影响0人  张清柏

相关参考添加链接描述
相关参考

什么是tcp

TCP,全称Transmission Control Protocol,是一种传输控制协议,TCP协议也是计算机网络中非常复杂的一个协议

tcp的特点

tcp粘包

tcp有这么多的特点,但是为什么还会出现粘包呢?其实这是对tcp传输的一种优化而引起的一些问题。

tcp粘包的演示

服务端

package main

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

func main() {
    network:="tcp"
    address:="127.0.0.1:30000"
    //绑定和监听tpc和端口
    listen, err := net.Listen(network, address)
    if err != nil {
        fmt.Println("listen err")
    }
    //关闭监听
    defer listen.Close()
    for{
        //等待连接
        conn,err:=listen.Accept()
        if err != nil {
            fmt.Println("accept error")
        }
        //从连接里面读取数据
        go process(conn)
    }
}

func process(conn net.Conn){
    defer conn.Close()//关闭连接
    //读取连接数据
    reader:=bufio.NewReader(conn)
    //定义每次接收的长度
    buf:=make([]byte, 7)
    for  {
        //用buf接收连接发送的内容
        read, err := reader.Read(buf)
        //读完了
        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Println("read conn err")
        }
        fmt.Printf("the msg i read length is %d \n",read)
        str:=string(buf[:read])
        fmt.Println(str)
    }
}

客户端代码

package main

import (
    "fmt"
    "net"
)

func main() {
    network:="tcp"
    address:="127.0.0.1:30000"
    //拨号 请求创建tcp连接
    conn, err := net.Dial(network,address )
    if err != nil {
        fmt.Println("connect err")
    }
    //关闭连接
    defer conn.Close()
    //想tcp写入数据
    conn.Write([]byte("123456789"))
}

tcp粘包的解决

粘包解决方案相关参考
package tcp_code

import (
    "bufio"
    "bytes"
    "encoding/binary"
)

// Encode 将消息编码后返回byte类型
func Encode(msg string)([]byte,error){
    //1.读取消息的长度,用int32存放消息长度,这个长度大概能支持4G的数据传输,如果用int64就代表16777216T
    length:=int32(len(msg))
    //定义一个Buffer结构体用来存储数据,Buffer是一个变长缓冲区,可读可写
    var pkg =new(bytes.Buffer)
    //把长度以二进制的形式写入消息头
    err := binary.Write(pkg, binary.LittleEndian, length)
    if err != nil {
        return nil, err
    }
    //把消息以二进制的形式写入pkg
    err = binary.Write(pkg, binary.LittleEndian, []byte(msg))
    if err != nil {
        return nil, err
    }
    //将缓冲区的数据返回
    return pkg.Bytes(),nil
}

// Decode 参数是从连接中获取的原始消息,用这个方法将消息体解码
func Decode(reader bufio.Reader)(string,error){
    //1.获取消息的长度
    //按照约定,读取前32的长度
    //Peek是返回字节类型,一个字节是8个bit,所以是4个字节即代表32位的长度的数据
    lengthByte,_:=reader.Peek(4)
    //转换为buff类型
    lengthBuff:=bytes.NewBuffer(lengthByte)

    //这个长度是指消息体的长度
    var length int32
    //将长度赋值给length
    err := binary.Read(lengthBuff, binary.LittleEndian, &length)
    if err != nil {
        return "", err
    }
    //消息体的长度加上4个字节 就是完整的消息体了
    totalLen:=length+4
    //查看当前缓存区中消息的长度,如果消息还没有传输完毕,先不处理
    if int32(reader.Buffered())<totalLen{
        return "", err
    }
    //定义一个切片从缓冲区获取数据
    pack:=make([]byte,totalLen)
    _, err = reader.Read(pack)
    if err != nil {
        return "", err
    }
    //返回消息 ,注意不要返回前4个byte,前4个byte代表的是消息体的长度
    return string(pack[4:]),nil
}

package main

import (
    tcp_code "acurd.com/pkg/pkg/tcp-code"
    "bufio"
    "fmt"
    "io"
    "net"
)

func main() {
    network:="tcp"
    address:="127.0.0.1:30000"
    //绑定和监听tpc和端口
    listen, err := net.Listen(network, address)
    if err != nil {
        fmt.Println("listen err")
    }
    //关闭监听
    defer listen.Close()
    for{
        //等待连接
        conn,err:=listen.Accept()
        if err != nil {
            fmt.Println("accept error")
        }
        //从连接里面读取数据
        go process(conn)
    }
}

func process(conn net.Conn){
    defer conn.Close()//关闭连接
    //读取连接数据
    reader:=bufio.NewReader(conn)
    //定义每次接收的长度
    for  {
        //使用decode解码消息
        msg, err := tcp_code.Decode(reader)
        //读完了
        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Println("read conn err")
        }
        fmt.Println(msg)
    }
}
package main

import (
    tcp_code "acurd.com/pkg/pkg/tcp-code"
    "bufio"
    "fmt"
    "io"
    "net"
)

func main() {
    network:="tcp"
    address:="127.0.0.1:30000"
    //绑定和监听tpc和端口
    listen, err := net.Listen(network, address)
    if err != nil {
        fmt.Println("listen err")
    }
    //关闭监听
    defer listen.Close()
    for{
        //等待连接
        conn,err:=listen.Accept()
        if err != nil {
            fmt.Println("accept error")
        }
        //从连接里面读取数据
        go process(conn)
    }
}

func process(conn net.Conn){
    defer conn.Close()//关闭连接
    //读取连接数据
    reader:=bufio.NewReader(conn)
    //定义每次接收的长度
    for  {
        //使用decode解码消息
        msg, err := tcp_code.Decode(reader)
        //读完了
        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Println("read conn err")
        }
        fmt.Println(msg)
    }
}

总结

通过上面,我们了解到了原来粘包的问题,并不属于tcp的锅。tcp是基于数据流的传输,保证数据流的顺序,但是正式由于这种数据流的传输模式,对于tcp来说,自己就像一个传送带,传递的是一个个的快递包裹,源源不断。具体包裹到是什么,到哪里去,就需要接收端和发送端通过定制的协议来编码和解码解决。

上一篇下一篇

猜你喜欢

热点阅读