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

时间:2018-07-23 05:24:46

标签: go tcp

问题

我有一个用例,我需要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数据包,这意味着,如果在等待250ms之后仍未收到任何数据包,则服务器将假定该特殊协议正在使用并发送其你好。

因此,最好是不事先进行任何Read()Peek()就能知道底层缓冲区中是否存在数据。

2 个答案:

答案 0 :(得分:4)

  

我有一个用例,我需要精确查看第一个TCP数据包,无论长度如何。

TCP是一种流协议,而不是像UDP这样的数据报协议。这意味着从TCP角度来看数据包是无关紧要的。它们仅临时存在于电线上。

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

在线上收到的类似数据包将在OS内核中重新组合,并将放入连续读取缓冲区中。这样做时,所有可能存在于网络上的数据包边界都将丢失。因此,应用程序无法找到包边界在哪里。

    n = bufConn.Buffered()

bufConn不是操作系统套接字缓冲区。 bufConn.Buffered()仅会看到从底层套接字读取到Go进程中的数据,但尚未由应用程序逻辑使用bufConn.Read()检索到的数据:如果尝试使用{{1 }}实际上,它将尝试从基础套接字读取更多字节,返回您请求的单个字节,并将其余字节保留在bufConn.Read()缓冲区中,以供以后读取。这样做是为了为应用程序逻辑提供更有效的接口。如果您不希望这样做,请不要使用缓冲的I / O。

答案 1 :(得分:0)

更新:无法使用net.Conn

实际上,在没有阅读的情况下不可能“窥视” net.Conn。但是,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数据包)的特定情况,我必须将连接的状态存储在其他地方,以便经过足够的时间(例如250ms)而没有任何时间我知道现在接收到的数据会跳过第一个数据包“偷看”的检测。