自定义协议/解决tcp粘包问题(golang版本)

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

内容简介:Tcp是字节流协议, 数据传输像流水一样没有边界, 那么对等方在一次数据读取后,无法分辨读取是一个消息还是多个,或者是不足一个, 那么对等方拿到"残缺"消息就不知道如何处理.Udp是基于消息的传输服务,每个消息就是一个报文,是有边界的,对等方每次接收都是一个完整的消息.

Tcp/Udp介绍

Tcp是字节流协议, 数据传输像流水一样没有边界, 那么对等方在一次数据读取后,无法分辨读取是一个消息还是多个,

或者是不足一个, 那么对等方拿到"残缺"消息就不知道如何处理.

Udp是基于消息的传输服务,每个消息就是一个报文,是有边界的,对等方每次接收都是一个完整的消息.

这样就需要我们在应用层,

自己来区分.

粘包是如何出现的?

  • 用户进程write消息, 但内核缓存区不足以容乃这个完整的消息, 一个消息分多次发送出去, 接收的时候就可能一个消息分多次接收
  • Tcp的报文段有大小限制(MSS)
  • IP层最大传输单元(MTU), 会对包进行分片,
  • 其他, Tcp流量控制, 拥塞控制

一般有三种常见的方式

1. 定长消息

发送端和接收端约定消息长度, 缺点: 消息很短时, 效率很低, 浪费带宽

2. 特殊标志作为结束标志

ftp协议就是这种方式, 缺点: 消息内容不能含有这种特殊标志, 会提前终止消息。 redis是如何解决类似的问题的呢, redis自定义

了动态字符串, 里面提到是二进制安全的, 意思就是字符串里面可以含有空字符(assic码为0), 原因就是它记录了这个字符串的长度,

其实也就是下面说的第三种方式

3. 定长的包头 + 变长的包体, 包头中写入包体的长度, 本文主要介绍这种方式:

自定义协议/解决tcp粘包问题(golang版本)

每次都要尽可能的去读数据, 读到之后分析:

先取包头, 在包头里分析出包体的长度, 如果包头都不够, 要继续读数据拼接在已有的数据后面, 继续分析包体的长度, 拿到包体的长度就从包头结束的问题截取包体, 依次递归, 直到对等方关闭

代码

// 读取消息, 可导出的方法
func (buffer *Buffer) Read(msg chan string) (error) {
    for {
        buffer.grow()                     // 移动数据
        _, err := buffer.readFromReader() // 读数据拼接到定额缓存后面
        if err != nil {
            fmt.Println(err)
            return err
        }
        // 检查定额缓存里面的数据有几个消息(可能不到1个,可能连一个消息头都不够,可能有几个完整消息+一个消息的部分)
        isBreak := buffer.checkMsg(msg)
        // 只要读到有完整的消息, isBreak就为true, 跳出去处理
        if (isBreak) {
            return nil
        }
    }
}
// grow 将有用的字节前移, 不可导出
func (b *Buffer) grow() {
    if b.start == 0 {
        return
    }
    copy(b.buf, b.buf[b.start:b.end])
    b.end -= b.start
    b.start = 0
}
// 检查应用层缓存区是否包含完整的消息, 不可导出
func (buffer *Buffer) checkMsg(msg chan string) (bool) {
    var isBreak bool
    HEADER_LENG := HEAD_SIZE + len(buffer.header)
    headBuf, err1 := buffer.seek(HEADER_LENG)
    if err1 != nil { // 一个消息头都不够, 跳出去继续读吧
        return false
    }
    if (string(headBuf[:len(buffer.header)]) == buffer.header) { // 判断消息头正确性

    } else {

    }
    contentSize := int(binary.BigEndian.Uint16(headBuf[len(buffer.header):]))
    if (buffer.len() >= contentSize-HEADER_LENG) { // 一个消息体也是够的
        contentBuf := buffer.read(HEADER_LENG, contentSize) // 把消息读出来,把start往后移
        msg <- string(contentBuf)
        // 递归,看剩下的还够一个消息不
        isBreak = true
        buffer.checkMsg(msg)
    } else { // 一个消息体不够的, 跳出去继续读吧
        isBreak = false
    }
    return isBreak
}

演示

go get github.com/weiwenwang/DiyProtocol

cd $GOPATH/github.com/weiwenwang/DiyProtocol/example/server/

go run server.go

自定义协议/解决tcp粘包问题(golang版本)

cd $GOPATH/github.com/weiwenwang/DiyProtocol/example/client/

go run client.go

自定义协议/解决tcp粘包问题(golang版本)

详情

源码请移步: github , 本人附上一个demo, 代码注释详细.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

数学与泛型编程

数学与泛型编程

[美]亚历山大 A. 斯捷潘诺夫(Alexander A. Stepanov)、[美]丹尼尔 E. 罗斯(Daniel E. Rose) / 爱飞翔 / 机械工业出版社 / 2017-8 / 79

这是一本内容丰富而又通俗易懂的书籍,由优秀的软件设计师 Alexander A. Stepanov 与其同事 Daniel E. Rose 所撰写。作者在书中解释泛型编程的原则及其所依据的抽象数学概念,以帮助你写出简洁而强大的代码。 只要你对编程相当熟悉,并且擅长逻辑思考,那么就可以顺利阅读本书。Stepanov 与 Rose 会清晰地讲解相关的抽象代数及数论知识。他们首先解释数学家想要解决......一起来看看 《数学与泛型编程》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

在线进制转换器
在线进制转换器

各进制数互转换器

URL 编码/解码
URL 编码/解码

URL 编码/解码