经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Rust » 查看文章
13. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP中的压缩gzip,deflate,brotli算法
来源:cnblogs  作者:问蒙服务框架  时间:2023/10/17 11:02:22  对本文有异议

用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP中的压缩gzip,deflate,brotli算法

项目 ++wmproxy++

gite: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

HTTP中压缩的意义

HTTP中压缩的意义在于降低了网络传输的数据量,从而提高客户端浏览器的访问速度。当然,同时也会增加一点服务器的负担。
HTTP/1.1协议中压缩主要包括gzip压缩和deflate压缩两种方法。其中gzip压缩使用的是LZ77和哈夫曼编码,而deflate压缩使用的是LZ77和哈夫曼编码以及霍夫曼编码。
此外在2015年由Google公司开发的Brotli算法是也基本全面普及开来,Brotli算法的核心原理包括两个部分:预定义的字典和无损压缩算法。预定义的字典是Brotli算法中的一项关键技术,它包含了一些常见的字符序列,例如Web标记、HTML、CSS和JavaScript代码等。Brotli算法的无损压缩算法采用了一种基于模式匹配的压缩方法。它通过预测数据中出现的重复模式,对数据进行压缩。
在HTTP的压缩协议中,这三种压缩算法基本上可以全部被支持。

gzip,deflate,brotli的优劣势

gzip、deflate和brotli这三种压缩算法都有各自的优势和劣势,具体如下:

  1. gzip
  • 优势:是Web上最常见的压缩算法之一,具有较高的压缩效率和广泛的支持程度,可以被几乎所有的浏览器和服务器支持。
  • 劣势:算法的压缩比相对较低,可能会增加文件的大小。
  1. deflate
  • 优势:具有较高的压缩效率和广泛的支持程度,同时算法的实现在不同的浏览器和服务器之间非常一致。
  • 劣势:由于某些实现上的缺陷,可能会导致一些浏览器和服务器无法正常解压缩。
  1. brotli
  • 优势:具有更高的压缩效率和更快的压缩速度,可以进一步减少传输数据的大小,从而提高页面加载速度,并且被较新版本的浏览器和服务器支持。
  • 劣势:由于算法目前仅被较新版本的浏览器和服务器支持,因此需要根据实际情况进行选择。

以下是压缩解压的数率图:


数据来源src

可以看出brotli的压缩比大概在9左右,gzip大概在7左右,deflate也大概在7左右,压缩比brotli最高,适应网络传输慢的情况,压缩速度gzip和deflate相对较快,解压缩deflate较快,brotli和gzip差不多。

rust中三种压缩方式库的选择

通常寻找rust中的第三方库的时候,可以通过https://crates.io/进行选择,这里公开的第三方库都会在这里显示,包括使用次数,流行热度,最近下载量,最近更新时间等,可以从多维度的知道该库的好与坏再进行相应的选择。

  • flate2

    该库支持三种压缩格式的算法,deflate, zlib, gzip,我们选择用他来做deflate, gzip的支持。

  • brotli
    image.png
    该库如库名一般,只支持brotli算法,相对热度较高,算是支持brolti里最好的一个,我们进行选择。

三种方式的压缩实现

三种方式均可实现流式的压缩,即边写入数据,边读出压缩数据,不用完全的写入所有数据,完整的实现方法在 RecvStream里,将压缩的数据缓存到self.cache_body_data

定义压缩方法值

  1. pub const COMPRESS_METHOD_NONE: i8 = 0;
  2. pub const COMPRESS_METHOD_GZIP: i8 = 1;
  3. pub const COMPRESS_METHOD_DEFLATE: i8 = 2;
  4. pub const COMPRESS_METHOD_BROTLI: i8 = 3;
  • gzip

此处利用的是类use flate2::write::GzEncoder,定义为GzEncoder<BinaryMut>,其中BinaryMut为压缩后的数据,需要具备std::io::Write方法。

  1. Consts::COMPRESS_METHOD_GZIP => {
  2. // 数据结束,需要主动调用结束以导出全部结果
  3. if data.len() == 0 {
  4. self.compress.open_write_gz();
  5. let gz = self.compress.write_gz.take().unwrap();
  6. let value = gz.finish().unwrap();
  7. if value.remaining() > 0 {
  8. Self::inner_encode_data(&mut self.cache_body_data, &value, self.is_chunked)?;
  9. }
  10. if self.is_chunked {
  11. Helper::encode_chunk_data(&mut self.cache_body_data, data)
  12. } else {
  13. Ok(0)
  14. }
  15. } else {
  16. self.compress.open_write_gz();
  17. let gz = self.compress.write_gz.as_mut().unwrap();
  18. gz.write_all(data).unwrap();
  19. // 每次写入,在尝试读取出数据
  20. if gz.get_mut().remaining() > 0 {
  21. let s =
  22. Self::inner_encode_data(&mut self.cache_body_data, &gz.get_mut().chunk(), self.is_chunked);
  23. gz.get_mut().clear();
  24. s
  25. } else {
  26. Ok(0)
  27. }
  28. }
  29. }
  • deflate

此处利用的是类use flate2::write::DeflateEncoder,定义为DeflateEncoder<BinaryMut>,其中BinaryMut为压缩后的数据,需要具备std::io::Write方法。

  1. Consts::COMPRESS_METHOD_DEFLATE => {
  2. // 数据结束,需要主动调用结束以导出全部结果
  3. if data.len() == 0 {
  4. self.compress.open_write_de();
  5. let de = self.compress.write_de.take().unwrap();
  6. let value = de.finish().unwrap();
  7. if value.remaining() > 0 {
  8. Self::inner_encode_data(&mut self.cache_body_data, &value, self.is_chunked)?;
  9. }
  10. if self.is_chunked {
  11. Helper::encode_chunk_data(&mut self.cache_body_data, data)
  12. } else {
  13. Ok(0)
  14. }
  15. } else {
  16. self.compress.open_write_de();
  17. let de = self.compress.write_de.as_mut().unwrap();
  18. de.write_all(data).unwrap();
  19. // 每次写入,在尝试读取出数据
  20. if de.get_mut().remaining() > 0 {
  21. let s =
  22. Self::inner_encode_data(&mut self.cache_body_data, &de.get_mut().chunk(), self.is_chunked);
  23. de.get_mut().clear();
  24. s
  25. } else {
  26. Ok(0)
  27. }
  28. }
  29. }
  • brotli

此处利用的是类use brotli::CompressorWriter;,定义为CompressorWriter<BinaryMut>,其中BinaryMut为压缩后的数据,需要具备std::io::Write方法。

  1. Consts::COMPRESS_METHOD_BROTLI => {
  2. // 数据结束,需要主动调用结束以导出全部结果
  3. if data.len() == 0 {
  4. self.compress.open_write_br();
  5. let mut de = self.compress.write_br.take().unwrap();
  6. de.flush()?;
  7. let value = de.into_inner();
  8. if value.remaining() > 0 {
  9. Self::inner_encode_data(&mut self.cache_body_data, &value, self.is_chunked)?;
  10. }
  11. if self.is_chunked {
  12. Helper::encode_chunk_data(&mut self.cache_body_data, data)
  13. } else {
  14. Ok(0)
  15. }
  16. } else {
  17. self.compress.open_write_br();
  18. let de = self.compress.write_br.as_mut().unwrap();
  19. de.write_all(data).unwrap();
  20. // 每次写入,在尝试读取出数据
  21. if de.get_mut().remaining() > 0 {
  22. let s =
  23. Self::inner_encode_data(&mut self.cache_body_data, &de.get_mut().chunk(), self.is_chunked);
  24. de.get_mut().clear();
  25. s
  26. } else {
  27. Ok(0)
  28. }
  29. }
  30. }

三种方式的解压实现

和压缩不同的是,解压的时候必须将完整的数据进行解压,所以需要收到全部的数据的时候才尝试进行解压,可能我的理解有误,欢迎指出,当下的实现方式可能会占用大量的内存,非我所愿。主要源码在 SendStream中实现。

三种方式均类似,以下

  1. // 收到数据进行缓存,只有到结束时才进行解压缩
  2. match self.compress_method {
  3. Consts::COMPRESS_METHOD_GZIP => {
  4. self.cache_body_data.put_slice(data);
  5. if self.is_end {
  6. self.compress.open_reader_gz(self.cache_body_data.clone());
  7. let gz = self.compress.reader_gz.as_mut().unwrap();
  8. let s = Self::read_all_data(&mut self.cache_buf, &mut self.real_read_buf, gz);
  9. self.cache_body_data.clear();
  10. s
  11. } else {
  12. Ok(0)
  13. }
  14. }
  15. Consts::COMPRESS_METHOD_DEFLATE => {
  16. self.cache_body_data.put_slice(data);
  17. if self.is_end {
  18. self.compress.open_reader_de(self.cache_body_data.clone());
  19. let de = self.compress.reader_de.as_mut().unwrap();
  20. let s = Self::read_all_data(&mut self.cache_buf, &mut self.real_read_buf, de);
  21. self.cache_body_data.clear();
  22. s
  23. } else {
  24. Ok(0)
  25. }
  26. }
  27. Consts::COMPRESS_METHOD_BROTLI => {
  28. self.cache_body_data.put_slice(data);
  29. if self.is_end {
  30. self.compress.open_reader_br(self.cache_body_data.clone());
  31. let br = self.compress.reader_br.as_mut().unwrap();
  32. let s = Self::read_all_data(&mut self.cache_buf, &mut self.real_read_buf, br);
  33. self.cache_body_data.clear();
  34. s
  35. } else {
  36. Ok(0)
  37. }
  38. }
  39. _ => {
  40. self.real_read_buf.put_slice(data);
  41. Ok(data.len())
  42. },
  43. }

如果数据包非常的巨大的时候,可能需要将内存内容写入缓存文件来缓解内存的压力。

结语

压缩为了可以更好的存储,也可以更好的传输,是我们日常生活中必不可少的存在,虽然现在比以前带宽更高,存储比之前的更便宜,但是现在的数据更多,传输延时要求更少,所以高压缩的能力依然非常受欢迎。

原文链接:https://www.cnblogs.com/luojiawaf/p/17768820.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号