34

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

 5 years ago
source link: https://studygolang.com/articles/16494?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK