Golang 使用TCP并解决TCP粘包的问题

howie_鲸落 · · 1783 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

此项目有对应的Android端demo有需求请联系下方QQ

TCP粘包和拆包产生的原因

  • 应用程序写入数据的字节大小大于套接字发送缓冲区的大小
  • 进行MSS大小的TCP分段。MSS是最大报文段长度的缩写。MSS是TCP报文段中的数据字段的最大长度。数据字段加上TCP首部才等于整个的TCP报文段。所以MSS并不是TCP报文段的最大长度,而是:MSS=TCP报文段长度-TCP首部长度
  • 以太网的payload大于MTU进行IP分片。MTU指:一种通信协议的某一层上面所能通过的最大数据包大小。如果IP层有一个数据包要传,而且数据的长度比链路层的MTU大,那么IP层就会进行分片,把数据包分成托干片,让每一片都不超过MTU。注意,IP分片可以发生在原始发送端主机上,也可以发生在中间路由器上。

TCP粘包和拆包的解决策略

  • 消息定长。例如100字节。
  • 在包尾部增加回车或者空格符等特殊字符进行分割,典型的如FTP协议
  • 将消息分为消息头和消息尾。(len+data模式)
  • 其它复杂的协议,如RTMP协议等。

废话不多说直接上代码

服务端

package network

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

type TcpClient struct {
    tag  string
    conn net.Conn
    r    *bufio.Reader
}

func NewTcpClint(conn net.Conn) *TcpClient {
    return &TcpClient{conn: conn, r: bufio.NewReader(conn)}
}

func (c *TcpClient) LocalAddr() net.Addr {
    return c.conn.LocalAddr()
}

func (c *TcpClient) RemoteAddr() net.Addr {
    return c.conn.RemoteAddr()
}

func (c *TcpClient) Close() error {
    return c.conn.Close()
}

func (c *TcpClient) Write(message []byte) (int, error) {
    // 读取消息的长度
    var length = int32(len(message))
    var pkg = new(bytes.Buffer)
    //写入消息头
    err := binary.Write(pkg, binary.BigEndian, length)
    if err != nil {
        return 0, err
    }
    //写入消息体
    err = binary.Write(pkg, binary.BigEndian, message)
    if err != nil {
        return 0, err
    }
    nn, err := c.conn.Write(pkg.Bytes())
    if err != nil {
        return 0, err
    }
    return nn, nil
}

func (c *TcpClient) Read() ([]byte, error) {
    // Peek 返回缓存的一个切片,该切片引用缓存中前 n 个字节的数据,
    // 该操作不会将数据读出,只是引用,引用的数据在下一次读取操作之
    // 前是有效的。如果切片长度小于 n,则返回一个错误信息说明原因。
    // 如果 n 大于缓存的总大小,则返回 ErrBufferFull。
    lengthByte, err := c.r.Peek(4)
    if err != nil {
        return nil, err
    }
    //创建 Buffer缓冲器
    lengthBuff := bytes.NewBuffer(lengthByte)
    var length int32
    // 通过Read接口可以将buf中得内容填充到data参数表示的数据结构中
    err = binary.Read(lengthBuff, binary.BigEndian, &length)
    if err != nil {
        return nil, err
    }
    // Buffered 返回缓存中未读取的数据的长度
    if int32(c.r.Buffered()) < length+4 {
        return nil, err
    }
    // 读取消息真正的内容
    pack := make([]byte, int(4+length))
    // Read 从 b 中读出数据到 p 中,返回读出的字节数和遇到的错误。
    // 如果缓存不为空,则只能读出缓存中的数据,不会从底层 io.Reader
    // 中提取数据,如果缓存为空,则:
    // 1、len(p) >= 缓存大小,则跳过缓存,直接从底层 io.Reader 中读
    // 出到 p 中。
    // 2、len(p) < 缓存大小,则先将数据从底层 io.Reader 中读取到缓存
    // 中,再从缓存读取到 p 中。
    _, err = c.r.Read(pack)
    if err != nil {
        return nil, err
    }
    return pack[4:], nil
}


package controller

import (
    "fmt"
    "test/tcp/network"
    "io"
    "net"
)

func ServerRun() {
    lister, err := net.Listen("tcp", "192.168.2.28:8888")
    fmt.Println("服务启动成功:192.168.2.28:8888")
    CheckErr(err)
    defer lister.Close()
    for {
        conn, err := lister.Accept()
        CheckErr(err)
        fmt.Println("用户接入")
        client := network.NewTcpClint(conn)
        go func() {
            defer client.Close()
            for {
                data, err := client.Read()
                if err == io.EOF {
                    fmt.Println("断开链接")
                    return
                }
                if err != nil {
                    continue
                }
                switchController(data, client)
            }
        }()
    }
}
func CheckErr(err error) {
    if err != nil {
        panic(err)
    }
}

func switchController(data []byte, c *network.TcpClient) {
    fmt.Println("读到的数据: " + string(data))
    switch string(data) {
    case "ping":
        c.Write([]byte("pong"))
        fmt.Println("发出的数据: pong")
        break
    }
}


客户端

package main

import (
    "testing"
    "net"
    "log"
    "fmt"
    "encoding/binary"
    "bytes"
    "time"
)

func Test(t *testing.T) {
    conn, err := net.Dial("tcp", "192.168.2.28:8888")
    if err != nil {
        log.Println("dial error:", err)
        return
    }
    defer conn.Close()
    for {
        data, _ := Encode("1")
        time.Sleep(time.Second * 4)
        _, err := conn.Write(data)
        fmt.Println(err)
    }
}
func Encode(message string) ([]byte, error) {
    // 读取消息的长度
    var length = int32(len(message))
    var pkg = new(bytes.Buffer)
    // 写入消息头
    err := binary.Write(pkg, binary.BigEndian, length)
    if err != nil {
        return nil, err
    }
    // 写入消息实体
    err = binary.Write(pkg, binary.BigEndian, []byte(message))
    if err != nil {
        return nil, err
    }
    return pkg.Bytes(), nil
}

联系 QQ: 3355168235


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:howie_鲸落

查看原文:Golang 使用TCP并解决TCP粘包的问题

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1783 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传