V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
awanganddong
V2EX  ›  Go 编程语言

go 分隔符处理粘包拆包问题

  •  
  •   awanganddong · 2023-07-12 21:45:01 +08:00 · 2106 次点击
    这是一个创建于 486 天前的主题,其中的信息可能已经有所发展或是发生改变。
    定义一个 buffer 来临时存放消息
    从 conn 里面读取固定字节大小内容,判断当前内容里面有没有分隔符
    如果没有找到分隔符,把当前内容追加到 buffer 里面,然后重复第 2 步
    如果找到分隔符,把当前内容里面分隔符之前的内容追加到 buffer 后输出
    然后重置 buffer ,把分隔符之后的内容追加到 buffer ,重复第 2 步
    
    把分隔符之后的内容追加到 buffer 这个环节我不知道处理
    
    
    package main
    
    import (
    	"bufio"
    	"io"
    	"log"
    	"net"
    )
    
    func main() {
    	listener, err := net.Listen("tcp", "127.0.0.1:8866")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer listener.Close()
    
    	for {
    		con, err := listener.Accept()
    		if err != nil {
    			log.Println(err)
    			continue
    		}
    		defer con.Close()
    		reader := bufio.NewReader(con)
    		for {
    			data, err := reader.ReadSlice('\n')
    			if err != nil {
    				if err != io.EOF {
    					// 分隔符之后
    				} else {
    					//读取结束
    					break
    				}
    			}
    			log.Println("received msg", len(data), "bytes:", string(data))
    		}
    	}
    }
    
    package main
    
    import (
    	"log"
    	"net"
    	"time"
    )
    
    func main() {
    	conn, err := net.Dial("tcp", "127.0.0.1:8866")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer conn.Close()
    	_, err = conn.Write([]byte("bbbb\ndfdfdfdfd"))
    	if err != nil {
    		panic(err)
    	}
    	time.Sleep(time.Second)
    }
    
    
    21 条回复    2023-08-03 20:54:09 +08:00
    ggvoking
        1
    ggvoking  
       2023-07-12 21:47:20 +08:00   ❤️ 2
    消息包头带个长度不就行了。
    awanganddong
        2
    awanganddong  
    OP
       2023-07-12 21:53:05 +08:00
    @ggvoking 主要是想搞明白采用分格符这种方式是怎么处理的
    oneisall8955
        3
    oneisall8955  
       2023-07-12 21:57:29 +08:00 via Android   ❤️ 4
    粘包警察马上到!
    awanganddong
        4
    awanganddong  
    OP
       2023-07-12 21:59:39 +08:00
    TCP 流数据边界问题 这个没人吐槽了吧。
    LeegoYih
        5
    LeegoYih  
       2023-07-12 22:01:22 +08:00
    粘包警察来咯!

    可以参考一下这个项目处理边界的代码,他实现了好几种方式: https://github.com/go-netty/go-netty
    awanganddong
        6
    awanganddong  
    OP
       2023-07-12 22:04:32 +08:00
    bufio.NewReader 这个概念,理解错了。这个得出来的结果比较清晰一些
    ```
    func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:8866")
    if err != nil {
    log.Fatal(err)
    }
    defer conn.Close()
    _, err = conn.Write([]byte("bbbb\ndfdfdfdfd\nsdsdsd"))
    _, err = conn.Write([]byte("aaa\n"))
    if err != nil {
    panic(err)
    }
    time.Sleep(time.Second)
    }

    ```
    Vegetable
        7
    Vegetable  
       2023-07-12 22:10:14 +08:00
    你这个 TCP 结束之前,不会收到 EOF ,你就一直读不就行了吗,有什么需要处理的
    awanganddong
        8
    awanganddong  
    OP
       2023-07-12 22:10:25 +08:00
    这个文章可以参考

    消息长度固定,提前确定包长度,读取的时候也安固定长度读取,适合定长消息包。
    使用特殊的字符或字符串作为消息的边界,例如 HTTP 协议的 headers 以“\r\n”为字段的分隔符
    自定义协议,将消息分为消息头和消息体,消息头中包含表示消息总长度

    https://wangbjun.site/2019/coding/golang/golang-tcp-package.html

    谢谢各位
    Vegetable
        9
    Vegetable  
       2023-07-12 22:12:47 +08:00
    最后 err==EOF 的时候,data 是有值的,你不能直接丢掉,其他没什么问题
    awanganddong
        10
    awanganddong  
    OP
       2023-07-12 22:14:31 +08:00
    @Vegetable 好的,谢谢了。
    iceheart
        11
    iceheart  
       2023-07-12 22:31:36 +08:00 via Android
    一直收不到分隔符,内存不是要爆掉啦
    nightwitch
        12
    nightwitch  
       2023-07-12 22:40:40 +08:00
    这种方案也就在内网环境下用用,本质上和用换行符做分割没有什么区别。
    一旦被人摸清楚方案,无限给你发不带分割符的包,轻轻松松 oom
    deorth
        13
    deorth  
       2023-07-12 23:15:17 +08:00 via Android
    出警怎么这么慢啊
    rrfeng
        14
    rrfeng  
       2023-07-12 23:38:22 +08:00 via Android
    这种适合搞一个 ringbuffer
    MoYi123
        15
    MoYi123  
       2023-07-13 10:09:58 +08:00
    @nightwitch OOM 是另一个问题, http 协议的 content-length 也没有机制保证你不 oom 吧.
    oneisall8955
        16
    oneisall8955  
       2023-07-13 12:42:28 +08:00 via Android
    @MoYi123 类似 nginx 的 client_max_body_size ,默认 0 无限制,设置最大长度,超过了就拒绝?
    mcfog
        17
    mcfog  
       2023-07-13 12:57:51 +08:00
    直接 bufio.NewScanner 搞定,用 https://pkg.go.dev/bufio#Scanner.Split 配置一个,分隔符策略几乎可以照抄默认的 ScanLines
    trzzzz
        18
    trzzzz  
       2023-07-15 23:02:35 +08:00
    @awanganddong 确实,像 scp 和 sftp 底层的实现都是基于 tcp 基础上自己规定客户端和服务端间的协议
    voidmnwzp
        19
    voidmnwzp  
       2023-07-21 02:53:27 +08:00 via iPhone
    建议用 CRLF 分割
    wkong
        20
    wkong  
       2023-07-25 10:54:39 +08:00
    分享一下我开源的单机百万的通用实时服务。里面有关于拆包分包这块。

    https://github.com/WuKongIM/WuKongIM
    ace12
        21
    ace12  
       2023-08-03 20:54:09 +08:00
    怎么还没有喷沾包的啊,我好难受
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2992 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 00:15 · PVG 08:15 · LAX 16:15 · JFK 19:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.