【问题标题】:bufio readbytes buffer sizebufio readbytes 缓冲区大小
【发布时间】:2018-04-21 19:10:14
【问题描述】:

我试图了解 bufio ReadBytes 在接收大数据包时的行为。我在 MTU=9001 的 unix 机器的 eth0 上运行一个简单的 Golang TCP 服务器。客户端是一台单独的机器(不直接连接到服务器)在 eth0 上运行一个 Python 客户端程序,MTU=1500。我的客户端 python 程序正在尝试发送一些大型数据包,这些数据包在客户端机器中按预期分片,并以最大 TCP MSS=1440 的 IP 数据包发送出去。到此为止,一切都很好。数据包到达服务器机器,我希望服务器机器在 OSI 第 4 层重新组装数据包。所以,据我了解,我的 Golang 套接字缓冲区应该得到 1 个大尺寸数据包(已经重新组装)。我的 Golang 服务器程序使用 bufio.ReadBytes('\x04') 读取消息中的 EOT 字符。我的客户端程序在每个数据包的有效负载末尾显式添加了一个 EOT 字符。

在服务器中,我看到接收到的数据包大小不一致。根据 ReadBytes() 的官方文档,它应该读取输入缓冲区中的所有数据,直到读入“delim”字符。我无法理解最大值。 bufio 包中用于读取器对象的缓冲区容量,希望任何人的帮助。

我的客户端程序 sn-p:

while True:
    l = random.randint(1400, 10000)
    data=("t"*l + '\x04').encode()
    try:
        if sock.send(data)==0:
            print("Server closed connection")
        else:
            print("Data send done. Intended length=", l, " bytes")
    except:
       print ("Exception when sending data. Trace=" + traceback.format_exc())
    time.sleep(10)
    sock.close()

服务器程序sn-p:

reader := bufio.NewReader(conn)
readbuf := make([]byte, 1500)
err := io.EOF
for sockConnected {
    conn.SetReadDeadline(time.Now().Add(10 * time.Millisecond))
    readbuf, err = reader.ReadBytes('\x04')
    switch {
    case err == io.EOF || err == io.ErrUnexpectedEOF:
        log.Println("Socket closed. EOF / ErrUnexpectedEOF read in")
        sockConnected = false
    case err == nil:
        //log.Println("No error on read")
    case strings.HasSuffix(err.Error(), "i/o timeout"):
        //log.Println("Timed out read")
    default:
        log.Println("Some other error occurred.Reason=" + err.Error())
    }
    if len(readbuf) == 0 {
        continue
    } else {
        //log.Printf("Received from client=%v", string(readbuf))
        log.Printf("Recvd Bytes count=%v", len(readbuf))
    }
}

从客户端发送到服务器的一个示例数据包:

  1. 来自客户:

    数据发送完成。预期长度= 8267 字节

    => 8268 字节,包括尾随 EOT 字符。

  2. 在服务器上:

    2017/11/08 21:55:42.551604 Recvd Bytes count=1440

    2017/11/08 21:55:42.561897 Recvd Bytes count=4096

    2017/11/08 21:55:42.569405 Recvd Bytes count=2732

    => 3 个不同的 ReadBytes() 被触发消耗 8268 字节。

    => 第一次和第二次调用返回不同大小的数据。我希望它们是相同的,如果有 1 个单个常量缓冲区用作 bufio 的输入缓冲区。

这里有什么帮助吗?

【问题讨论】:

  • 这是 TCP 吗?使用 TCP,从用户空间应用程序的角度来看,没有数据包。无论您认为数据包的排列方式如何,网络堆栈都可以并且将以任何字节数将 TCP 数据传送到您的应用程序。
  • 另外,如果您进行大量网络编程,您应该习惯使用 Wireshark 或 Ethereal 或其他网络分析器。例如,在这种情况下,我敢打赌,由于 TCP 启动缓慢、MSS 发现等原因,您将达到 10 毫秒的超时时间。就像这里:en.wikipedia.org/wiki/Path_MTU_Discovery

标签: go network-programming


【解决方案1】:

ReadBytes方法读取的数据量不受bufio.Reader的读取缓冲区大小限制。

问题不在于缓冲,而在于 i/o 超时。 ReadBytes 函数读取直到找到分隔符或在底层 io.Reader 上读取时返回错误。在问题中给出的示例中,ReadBytes 在读取整个消息之前返回了两次 i/o 超时错误:

ReadBytes 
  read 1440 bytes
  timeout on read to underlying io.Reader, return error
ReadBytes
  read 4096 bytes
  timeout on read to underlying i.Reader, return error
ReadBytes
  read 2732 bytes to EOT
  return success

通过增加或消除读取期限来解决问题。例如:

conn.SetReadDeadline(time.Now().Add(time.Second))

这是一个按预期工作的更简单的服务器应用程序版本。在此示例中删除了读取截止日期。

func main() {
    ln, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }
    conn, err := ln.Accept()
    if err != nil {
        log.Fatal(err)
    }
    reader := bufio.NewReader(conn)
    for {
        buf, err := reader.ReadBytes('\x04')
        if err != nil {
            log.Fatal(err)
        }
        log.Printf("Received %d bytes\n", len(buf))
    }
}

【讨论】:

  • 您好,感谢您的回答。我正在尝试制作一个非阻塞 i/o 服务器。所以我需要尽快让读取超时。 @zan lynx 恰当地提到 10 毫秒超时的原因可能是 tcp 启动缓慢。 Mss 发现可能不是因为我正在检查的数据包是现有 tcp 会话的一部分。所以这个数据包不应该再次发生 mss 发现。您是否有其他建议,以便我仍然可以进行非阻塞读取 () ?
  • Go 中没有天生的非阻塞 IO,但你可以做异步 IO,以免阻塞程序的其他部分。请参阅this answer 以获得很好的解释。
  • @AnirbanMukherjee 使用 goroutines 创建不会阻塞单个连接的 i/o 的服务器。如前所述,这回答了您的问题。或许就您的服务器设计提出不同的问题。
猜你喜欢
  • 1970-01-01
  • 2020-09-05
  • 2014-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-26
  • 2021-09-18
  • 2012-04-11
相关资源
最近更新 更多