首页 > 解决方案 > 如何使用 gzip 压缩和分块传输编码修复来自 c 套接字 http 服务器的图像中的奇怪失真

问题描述

我目前正在编写一个支持 gzip 和分块传输的简单 c 套接字 HTTP 服务器。

gzip 和分块写入套接字的代码片段如下:

    // MAXLINE is the buffer size for out and in, which MAXLINE = 1000
    fd = open(filePath, O_RDONLY, 0);

    s.zalloc = s.zfree = s.opaque = NULL;
    deflateInit2(&s, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY);
    while ((s.avail_in = read(fd, in, MAXLINE)) > 0) {
      s.avail_out = MAXLINE;
      s.next_out = out;
      s.next_in = in;
      deflate(&s, Z_SYNC_FLUSH);
      sprintf(header, "%X\r\n", MAXLINE - s.avail_out);
      write(new_socket, header, strlen(header));
      write(new_socket, out, MAXLINE - s.avail_out);
      write(new_socket, "\r\n", 2);
    }

当请求的文件是 pdf、html、pptx 时,上面的代码可以正常工作。并且它们可以由浏览器下载而不会出现任何问题或损坏。

但是,当我尝试请求图像时,显示/下载的图像失真如下:

原图: 在此处输入图像描述

下载图片: 在此处输入图像描述

我怀疑使用 gzip 和分块传输写入套接字的代码存在一些问题,但我似乎无法找出问题所在。

知道为什么会这样吗?为什么它会导致图像问题而不是其他文件类型(如 pdf)?知道如何解决这个问题吗?谢谢你。

更新:

我已经使用 user253751 从评论中建议的大文本文件对此进行了测试,下载的文本文件具有相同的内容。

因此,使用 gzip 和分块发送文本文件不会发生失真。

此外,在添加 gzip 压缩(即仅分块)之前,图像根本没有失真。

所以很可能是导致这个问题的 gzip 压缩。但是,我不确定为什么以及如何解决这个问题。

通过使用十六进制编辑器比较原始图像和下载图像,我发现:

  1. 最后有很多字节丢失,如下图所示(左边是下载的,右边是原始的):

在此处输入图像描述

  1. 有些行是相同的,而有些则不是。

例如,偏移量为 0551980 的行(第一行,01 44 87 ... DA E0 B4)在两个文件中是相同的,但偏移量为 0552000 的下一行(7C 92 77 ... 34 2E 4B; 0C C5 8F .. . 1F CD 08) 是不同的。

我不确定如何解释这个比较的结果,因为这是我第一次使用十六进制编辑器,而且比较突出显示让我感到困惑。

由于上述差异没有被 wxHexEditor 突出显示,而在偏移量为 0552380 的不同行中,仅突出显示了相同的 C7。那么当有相同的数据时,编辑器会高亮突出显示?但是为什么它没有突出显示第一行呢?

在此处输入图像描述

此外,通过尝试不同的设置。修改缓冲区大小时,如果畸变改变宽度,如下图MAXLINE = 2000:

在此处输入图像描述

MAXLINE = 7000,失真消失,但底部有一条白线:

在此处输入图像描述

所以看来这里的问题可能是由于读取缓冲区循环可能导致一些字节被交换或省略?

解决方案:

感谢 user253751 解决问题。事实证明:

如果放气没有读取所有输入字节?(如果 s.avail_in > 0)它只是忽略它没有读取的字节,并用文件中的下一个字节覆盖它们!所以这些字节永远不会被压缩和发送!

因此,为了缓解这个问题,需要围绕 deflate() 循环并检查可用的输出缓冲区(s.avail_out)是否为空。如果 deflate 之后 s.avail_out == 0,这意味着压缩用尽了 out 缓冲区的所有空间,我们需要调用 deflate() 来处理它没有读取/压缩的字节。

或者检查 s.avail_in != 0 的 while 循环。

工作代码如下:

    // MAXLINE is the buffer size for out and in, which MAXLINE = 1000
    fd = open(filePath, O_RDONLY, 0);

    s.zalloc = s.zfree = s.opaque = NULL;
    deflateInit2(&s, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY);
    while ((s.avail_in = read(fd, in, MAXLINE)) > 0) {
      s.next_in = in;
      do {
        s.avail_out = MAXLINE;
        s.next_out = out;
        deflate(&s, Z_SYNC_FLUSH);
        sprintf(header, "%X\r\n", MAXLINE - s.avail_out);
        write(new_socket, header, strlen(header));
        write(new_socket, out, MAXLINE - s.avail_out);
        write(new_socket, "\r\n", 2);
      //} while (s.avail_out == 0);
      } while (s.avail_in != 0);
    }

标签: csocketsgziphttpserverchunked

解决方案


deflate从输入缓冲区读取一些未压缩的字节并将一些压缩字节写入输出缓冲区。您的代码会小心地将所有压缩字节发送到套接字,即使套接字不会一次全部发送它们。但是您的代码对未压缩的字节小心!

如果deflate先填满输出缓冲区,则返回时仍有输入字节剩余。您的代码会忽略那些剩余的输入字节,而不是尝试再次压缩它们,而是用文件中的下一个字节覆盖它们。

您看到 JPEG 文件而不是文本文件的原因是 JPEG 文件已经被压缩,因此它们不能再被压缩。这意味着压缩后的 JPEG 输出原始 JPEG 大,因此输出缓冲区会在输入缓冲区为空之前填满。使用文本文件,它可以很好地压缩,并且输出缓冲区中有足够的空间。


推荐阅读