代码之家  ›  专栏  ›  技术社区  ›  coolaj86

如何获取可用TCP数据的大小?

  •  0
  • coolaj86  · 技术社区  · 6 年前

    问题

    我有一个用例需要 Peek 在第一个TCP包上,不管它的长度是多少。

    片段

    我本以为这会奏效的:

    conn, err := sock.Accept()
    if nil != err {
        panic(err)
    }
    
    // plenty of time for the first packet to arrive
    time.Sleep(2500 * 1000000)
    
    bufConn := bufio.NewReader(conn)
    n := bufConn.Buffered()
    fmt.Fprintf(os.Stdout, "Size of Buffered Data %d\n", n)
    

    然而,即使我确信数据已经到达,它仍然显示0字节被缓冲。

    全面测试应用程序

    下面是一个完整的测试程序:

    package main
    
    import (
        "bufio"
        "fmt"
        "net"
        "os"
        "strconv"
        "time"
    )
    
    func main () {
        addr := ":" + strconv.Itoa(4080)
        sock, err := net.Listen("tcp", addr)
        if nil != err {
            panic(err)
        }
        conn, err := sock.Accept()
        if nil != err {
            panic(err)
        }
    
        bufConn := bufio.NewReader(conn)
        var n int
        for {
            n = bufConn.Buffered()
            fmt.Fprintf(os.Stdout, "Size of Buffered Data %d\n", n)
            if 0 != n {
                break
            }
            time.Sleep(2500 * 1000000)
        }
        first, err := bufConn.Peek(n)
        if nil != err {
            panic(err)
        }
        fmt.Fprintf(os.Stdout, "[Message] %s\n", first)
    }
    

    测试

    以及我是如何测试的:

    telnet localhost 4080
    
    Hello, World!
    

    这同样有效:

    echo "Hello, World!" | nc localhost -p 4080
    

    不过,如果我打电话 Peek(14) 很明显数据就在那里。

    为什么?

    我正在处理一个应用程序特定的用例——在单个端口上复用多个协议时的神奇字节检测。

    理论上,包的大小是不可靠的,但实际上,路径中的任何路由器都不会使几个字节的小hello包变小,应用程序在收到握手响应之前不会发送更多的数据。

    唱反调者

    我只支持一个协议,它要求服务器先发送hello数据包,这意味着如果在等待250毫秒后没有收到任何数据包,服务器将假设正在使用这个特殊的协议并发送hello。

    因此,如果我能知道数据是否存在于底层缓冲区而不做任何操作,那将是最好的。 Read() Peek() 事先。

    2 回复  |  直到 6 年前
        1
  •  4
  •   Steffen Ullrich    6 年前

    我有一个用例,需要查看第一个tcp包,不管它的长度是多少。

    TCP是一种流协议,而不是像UDP那样的数据报协议这意味着从tcp的角度来看,数据包是不相关的。它们只是暂时存在于电线上。

    应用程序发送的任何数据都将放入连续发送缓冲区,然后由操作系统打包以便传输这意味着应用程序的多次写入可能导致一个数据包、一次写入多个数据包等。如果数据在传输过程中丢失(即没有ACK),发送方OS甚至可以使用不同大小的数据包进行重新传输。

    在线路上接收到的类似数据包将重新组装到操作系统内核中,并放入连续读取缓冲区所有可能存在于线缆上的包边界将在这样做时丢失。因此,应用程序无法发现分组边界在何处。

        n = bufConn.Buffered()
    

    bufConn 不是操作系统套接字缓冲区。 bufConn.Buffered() 将只看到从底层套接字读入go进程但应用程序逻辑尚未使用 bufConn.Read() :如果试图读取单个字节 bufconn.read() 它实际上会尝试从底层套接字读取更多字节,返回您请求的单个字节,并将其余字节保存在 布冯 用于以后读取的缓冲区。这样做是为了为应用程序逻辑提供更有效的接口。如果您不想这样做,请不要使用缓冲I/O。

        2
  •  0
  •   coolaj86    6 年前

    更新:无法使用net.conn完成

    事实上,是的 可以“偷看”一个 net.Conn 没有阅读然而 净连接 可以包装,包装可以在任何地方传递 净连接 被接受。

    可行半溶液

    理想的解决方案是在第一次尝试时能够立即偷看在四处搜索时,我发现了一些自定义的go-TCP库但我还没有足够的冒险精神去尝试。

    根据@steffenullrich所说,事实证明 buffConn.Peek(1) 将使缓冲区充满可用的数据之后 buffConn.Buffered() 返回预期的字节数,可以继续 buffConn.Peek(n) :

    // Cause the bufConn with the available data
    firstByte, err = bufConn.Peek(1)
    if nil != err {
        panic(err)
    }
    
    // Check the size now
    n = bufConn.Buffered()
    fmt.Fprintf(os.Stdout, "Size of Buffered Data %d\n", n)
    
    // Peek the full amount of available data
    firstPacket, err = bufConn.Peek(n)
    if nil != err {
        panic(err)
    }
    

    我想我之前试过这个,看到缓冲区只有1个字节,但是读了上面的答案,我创建了一个特定的测试用例来确定,并且它工作了。

    缺点

    这仍然需要 Read() / Peek() 之前 知道数据的大小。

    这意味着,对于我的特定情况,在支持单个协议(需要服务器发送第一个hello数据包)的情况下,我必须将有关连接的状态存储在其他地方,以便在没有收到任何数据的情况下,如果经过足够的时间(比如说250毫秒),我知道现在可以跳过第一个数据包进入时的检测。