如何使用gzip压缩和分块传输编码来修复来自c socket http服务器的图像中的怪异变形

问题描述

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

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,"\r\n",2);
    }

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

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

原始图片

enter image description here

下载的图片

enter image description here

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

知道为什么会这样吗?以及为什么它会引起图像问题,而不引起其他文件类型(例如pdf)的问题? 任何想法如何解决这个问题? 谢谢。

更新

我已经使用user253751从注释中建议的大文本文件对它进行了测试,并且下载的文本文件具有相同的内容

因此,发送带有gzip和分块的文本文件不会发生任何失真。

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

最有可能是导致该问题的gzip压缩。但是,我不确定为什么以及如何解决此问题。

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

  1. 末尾有很多字节丢失,如下面的屏幕截图所示(左为下载,右为原始):

enter image description here

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

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

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

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

enter image description here

此外,通过尝试不同的设置。修改缓冲区大小时,如果失真发生变化,则宽度,如下所示,MAXLINE = 2000:

enter image description here

在MAXLINE = 7000的情况下,失真消失了,但底部有一条白线:

enter image description here

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

解决方案:

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

如果deflate不能读取所有输入字节? (如果s.avail_in> 0)它将忽略未读取的字节,并用文件中的下一个字节覆盖它们!因此,这些字节永远不会被压缩和发送!

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

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

工作代码如下:

    // MAXLINE is the buffer size for out and in,MAXLINE)) > 0) {
      s.next_in = in;
      do {
        s.avail_out = MAXLINE;
        s.next_out = out;
        deflate(&s,Z_SYNC_FLUSH);
        sprintf(header,MAXLINE - s.avail_out);
        write(new_socket,strlen(header));
        write(new_socket,2);
      //} while (s.avail_out == 0);
      } while (s.avail_in != 0);
    }

解决方法

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

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

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...