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

栏目: Go · 发布时间: 6年前

此项目有对应的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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Nature of Code

The Nature of Code

Daniel Shiffman / The Nature of Code / 2012-12-13 / GBP 19.95

How can we capture the unpredictable evolutionary and emergent properties of nature in software? How can understanding the mathematical principles behind our physical world help us to create digital w......一起来看看 《The Nature of Code》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试