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

go 处理 tcp 长连接丢失数据原因是什么

  •  
  •   Peakday · 2020-12-09 10:17:18 +08:00 · 3079 次点击
    这是一个创建于 1470 天前的主题,其中的信息可能已经有所发展或是发生改变。

    直接上代码

    listen, err := net.Listen("tcp", cfg.Network.IpP)
    if err != nil {
    	panic("can't listen port!")
    }
    
    for {
    	conn, err := listen.Accept()
    	if err != nil {
    		fmt.Println(err)
    		continue
    	}
    
    	go func(cn net.Conn) {
    		buffer := make([]byte, 1448)
    		for {
    			n, err := cn.Read(buffer)
    			if err != nil {
    				fmt.Println("tcp read error\t", err)
    				continue
    			}
    			//丢数据问题待解决
    			dataChanel <- buffer[:n]
    		}
    	}(conn)
    

    使用 tcpdump 抓取报文有 200 条,但是程序只收到 190 条左右,丢数据的原因是什么,目前我怀疑是接收性能不足,在向通道传数据的过程中第二条数据就过来了,导致第二条数据直接被丢弃

    codehz
        1
    codehz  
       2020-12-09 10:20:33 +08:00 via Android   ❤️ 2
    粘包警察👮预警
    misaka19000
        2
    misaka19000  
       2020-12-09 10:24:32 +08:00
    你的想法是错误的,OS 会把未读的数据放在缓冲区
    misaka19000
        3
    misaka19000  
       2020-12-09 10:25:51 +08:00
    此外,没懂你的「 tcpdump 抓取报文有 200 条」是什么意思,建议把另一端的代码也放出来
    chazyu1996
        4
    chazyu1996  
       2020-12-09 10:29:55 +08:00
    tcp 基于字节流,没有包的概念吧
    zunceng
        6
    zunceng  
       2020-12-09 10:33:08 +08:00
    err == EOF n != 0 的情况处理了么
    no1xsyzy
        7
    no1xsyzy  
       2020-12-09 10:34:13 +08:00
    请提供完整可复现的代码
    rochek
        8
    rochek  
       2020-12-09 10:35:40 +08:00
    你的 buffer 是 1448 。
    所以有可能一个 tcp 段由 2 个 ip 片承载。

    建议先了解一下基本网络原理,再考虑抓包之类的事情。
    写网络的化建议先从简单的问题开始。
    BingoXuan
        9
    BingoXuan  
       2020-12-09 10:39:47 +08:00   ❤️ 1
    不要看包數量,看字節數,tcp 每一個數據包大小都不是固定的。

    用的都是同一段 buf 接收數據並通過 chan 轉發我記得是有問題的。
    server
        10
    server  
       2020-12-09 10:40:22 +08:00
    icexin
        11
    icexin  
       2020-12-09 10:40:39 +08:00   ❤️ 1
    你复用了 buffer,新数据过来之后老的 buffer 就被覆盖了。拷贝一份 buffer 发送到 channel 里面。
    labulaka521
        12
    labulaka521  
       2020-12-09 10:45:28 +08:00
    @icexin 这个不需要的 复用 buffer 是可以的
    icexin
        13
    icexin  
       2020-12-09 10:49:37 +08:00   ❤️ 1
    @labulaka521 在一个 goroutine 里面复用是没问题的,但楼主把数据发送到 channel 里面,在另外一个 goroutine 里面处理,两者会发生不同步,就会产生消费 goroutine 没处理完,新的数据又写入到 buffer 里面了。即使不考虑 buffer 覆盖问题,两个 goroutine 同时访问一块内存也会出现数据竞争。
    JackieChoi
        14
    JackieChoi  
       2020-12-09 10:56:57 +08:00
    不懂 go,但是 tcpdump 的数据 200 条,是在网卡抓取的数据,和应用层的数据并不是完全对应的。
    程序收到的是协议栈处理完全后的程序,而 tcpdump 里可能有未被丢弃的超时重传包、未被合并的分包等等其他数据包。
    建议算字节数,应用层接收的字节数,理论上等于最后一个 tcp 的 seq - 第一个 tcp 包的 seq + 最后一个 tcp 的 datalen
    hasdream
        15
    hasdream  
       2020-12-09 10:58:40 +08:00
    如果是在一个局域网的话:
    1. 网卡 mtu 是多少
    2. 发送的数据是多少 (mtu 一般为 1500 tcp payload 一般为 66byte 后 payload 大于(mtu - (66-12) = payload 最大值) 如果超过 payload 最大值就会自动分片为下一个 packet)
    如果就在本机测试那没有 1500 的限制,lo MTU 为 65535
    keepeye
        16
    keepeye  
       2020-12-09 11:00:35 +08:00
    @icexin +1 特别需要注意这点
    gamexg
        17
    gamexg  
       2020-12-09 11:05:49 +08:00
    buffer 的内容被覆盖了
    改成这样:

    ```
    go func(cn net.Conn) {
    for {
    buffer := make([]byte, 1448)
    n, err := cn.Read(buffer)
    if err != nil {
    fmt.Println("tcp read error\t", err)
    continue
    }
    //丢数据问题待解决
    dataChanel <- buffer[:n]
    }
    }(conn)

    ```
    djoiwhud
        18
    djoiwhud  
       2020-12-09 11:29:09 +08:00
    不知道是练手的 demo 还是给企业用。这代码不全,而且看起来问题很多。

    tcpdump 检测的是 os 层级的接收写入到 tcp rec buffer window 的操作,实际上 cn.Read(buffer) 作为应用层代码,这种代码是从 tcp rec buffer window 读取数据。两者不一致是可能的。

    “目前我怀疑是接收性能不足,在向通道传数据的过程中第二条数据就过来了,导致第二条数据直接被丢弃”

    你这个说法表明你没有理解网络。个人建议你先看看计算机网络卷 1

    发送方可以在一秒钟内发 10 次数据,总共发几千字节,而你这边可以用 cn.Read(buffer) 一次全部读出来,只要 buffer 足够大,你不读,数据也在 tcp rec buffer window 里面了。
    djoiwhud
        19
    djoiwhud  
       2020-12-09 11:36:22 +08:00
    dataChanel <- buffer[:n]

    这行代码,看起来,dataChanel 里面保存的 buffer 地址指向的数据会被后面的数据覆盖。你需要重新 new 一个切片再丢进 channel 。大半年没写 go 了,有些遗忘,最好测试一下我这个说法。

    ps ,你的 chanel 拼写是错的。

    功底有点差。是学生?
    Peakday
        20
    Peakday  
    OP
       2020-12-09 11:49:11 +08:00
    @djoiwhud 不是学生,第一次写,感谢您的解答
    Peakday
        21
    Peakday  
    OP
       2020-12-09 11:50:29 +08:00
    感谢大家的回复!我再去学习学习
    djoiwhud
        22
    djoiwhud  
       2020-12-09 11:58:44 +08:00
    你对 tcpdump 使用的也有问题。tcpdump 这类工具不是给你数报文条数的。

    你应该逐 bit 分析通信协议代码是否有 bug,tcpdump 保存数据到 wireshark 分析,或者直接用 wireshark 。发收方的 tcpdump 的数据串是一致的,通常有一方代码有 bug,应用层处理出错了。
    namaketa
        23
    namaketa  
       2020-12-09 13:27:05 +08:00
    也在学习 go 语言。
    目前来看 lz 主要的问题是直接把数据缓存完之后直接传了个 slice 。
    而 slice 本质就是一个指针,后续还进行了并发操作,导致指针引用的对象变化了。
    可以了解一下深拷贝浅拷贝的概念。
    gesse
        24
    gesse  
       2020-12-09 15:50:24 +08:00
    tcp 是基于字节流、而不是基于消息包的协议。比如 tcpdump 抓到两个 IP 包,包含两个 tcp 数据,但是这两个数据可以在运输层被一次性 copy 到应用层。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3456 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 05:00 · PVG 13:00 · LAX 21:00 · JFK 00:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.