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

How to fix weird distortion in image from c socket http server with gzip compression and chunked transfer encoding

我目前正在编写一个支持 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 和 chunked transfer 写入套接字的代码有问题,但我似乎无法找出问题所在。

知道为什么会这样吗?为什么它会导致图像问题而不是其他文件类型(如 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被高亮。所以当有相同数据时,编辑会突出显示?但是为什么不突出显示第一行呢?

此外,通过尝试不同的设置。修改buffer size时,width if distortion发生变化,如下图,MAXLINE = 2000:

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

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

解法:

感谢 user253751 解决了这个问题。结果是:

if deflate doesn't read all the input bytes? (if s.avail_in > 0) It just ignores the bytes it didn't read, and overwrites them with the next bytes in the file! So those bytes never get compressed and sent!

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

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

工作代码如下:

    // 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);
    }

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

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

您看到 JPEG 文件而不是文本文件的原因是 JPEG 文件已经压缩,因此不能再压缩。这意味着压缩后的 JPEG 输出比原始 JPEG ,因此输出缓冲区会在输入缓冲区为空之前填满。对于文本文件,它压缩得很好,输出缓冲区中有足够的空间。