Golang中compress/flate包可能存在bug?

Golang中compress/flate包可能存在bug? 使用 compress/flate 压缩数据包流时,解压缩器在 32767 (2^15) 次迭代后失败,并报告遇到意外的 EOF。

以下是演示该问题的示例代码:

package main

import (
	"bytes"
	"compress/flate"
	"fmt"
	"io"
)

func main() {
	var buf bytes.Buffer
	w, _ := flate.NewWriter(&buf, flate.DefaultCompression)
	w.Write([]byte("hello, world\n"))
	w.Close()

	r := flate.NewReader(&buf)
	defer r.Close()

	for i := 0; i < 32768; i++ {
		var out bytes.Buffer
		_, err := io.Copy(&out, r)
		if err != nil {
			fmt.Printf("Iteration %d: %v\n", i, err)
			break
		}
		fmt.Printf("Iteration %d: %s", i, out.String())
	}
}

示例代码链接


更多关于Golang中compress/flate包可能存在bug?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

我不认为在解压缩器下重置读取器是受支持的操作。解压缩器拥有自己的状态。有一个方法可以将解压缩器重置为使用新的底层读取器,您应该改用这个方法。

// 代码示例应放置在此处

更多关于Golang中compress/flate包可能存在bug?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


它无法知道这一点,但它能注意到你的操作不符合预期。32768这个数字并非随机。也许flate数据流预期在该间隔处有额外的块头或其他内容(我对此并不确定,但这不难想象),而你无意中将其放入了下一条"消息"。将单个数据流分割成多条消息并在其间插入EOF显然不是正确的使用方式。

如果你需要独立的消息,压缩器和解压器都应当针对每条消息进行重置。

您提到可能有多余数据被添加到流中,这让我思考在解压缩步骤后是否会有残留数据。果然存在这种情况,只需要在下一条消息之前将其重新输入,实际上就形成了一个滑动字节缓冲区。

https://play.golang.org/p/TysU48fSMZQ

我希望避免重置压缩器/解压缩器,因为那样会降低压缩率,特别是对于较小的消息。而且compress/flate看起来就是为支持这种情况而设计的。

感谢您的查看。

也许,但它怎么会知道呢?解压缩器只知道它实现了 io.Reader 接口,并且可以通过调用 Read 方法来获取更多字节。

这是解压缩器上的 Reset 方法吗?https://golang.org/pkg/compress/flate/#Resetter 它会丢弃当前状态,因此将无法继续从发送端解压缩数据流。

话虽如此,底层读取器似乎确实存在问题。我尝试修改 compress/flate 中的同步示例,使其更类似于这个示例 https://play.golang.org/p/IAgu8wkwqIC,它成功完成了 32768 次解码。我尝试了 1000 万次,它也没有任何问题。

我将尝试在原始示例中将 bytes.Reader 替换为其他读取器。

这引出了你最初关于使用重置功能的评论。

我将 bytes.Reader 切换为 bytes.Buffer,并使用 Write 方法直接追加新数据 https://play.golang.org/p/gzzmc-2KA5A

我最初对使用 Write 追加数据感到担忧,因为我不想将整个网络流都保留在内存中,但查看 bytes.Buffer.Read 的实现,发现如果缓冲区为空,它会在内部调用 Reset 方法。https://github.com/golang/go/blob/master/src/bytes/buffer.go#L297

正如 Go 语言中常见的情况,简单的解决方案往往就是正确的解决方案。

这是一个已知的行为,而不是 compress/flate 包的 bug。问题在于 flate.NewReader 创建的读取器在遇到 EOF 后无法自动重置,导致后续读取失败。

在您的代码中,第一次调用 io.Copy 后,读取器 r 已经到达数据流的末尾。后续迭代尝试从已耗尽的读取器读取数据,因此返回 EOF 错误。

以下是正确的实现方式,每次迭代都创建新的读取器:

package main

import (
	"bytes"
	"compress/flate"
	"fmt"
	"io"
)

func main() {
	var buf bytes.Buffer
	w, _ := flate.NewWriter(&buf, flate.DefaultCompression)
	w.Write([]byte("hello, world\n"))
	w.Close()

	compressedData := buf.Bytes()

	for i := 0; i < 32768; i++ {
		r := flate.NewReader(bytes.NewReader(compressedData))
		var out bytes.Buffer
		_, err := io.Copy(&out, r)
		r.Close()
		
		if err != nil {
			fmt.Printf("Iteration %d: %v\n", i, err)
			break
		}
		fmt.Printf("Iteration %d: %s", i, out.String())
	}
}

如果您需要重复使用同一个读取器,可以使用 Reset 方法:

package main

import (
	"bytes"
	"compress/flate"
	"fmt"
	"io"
)

func main() {
	var buf bytes.Buffer
	w, _ := flate.NewWriter(&buf, flate.DefaultCompression)
	w.Write([]byte("hello, world\n"))
	w.Close()

	r := flate.NewReader(&buf)
	defer r.Close()

	compressedData := buf.Bytes()

	for i := 0; i < 32768; i++ {
		var out bytes.Buffer
		
		// 重置读取器以重新开始解压缩
		r.(flate.Resetter).Reset(&buf, nil)
		
		_, err := io.Copy(&out, r)
		if err != nil {
			fmt.Printf("Iteration %d: %v\n", i, err)
			break
		}
		fmt.Printf("Iteration %d: %s", i, out.String())
	}
}

关键点在于理解 flate.Reader 在到达 EOF 后不会自动回滚,需要显式重置或重新创建才能再次读取相同的数据。

回到顶部