kerryjiang / SuperSocket

SuperSocket is a light weight, cross platform and extensible socket server application framework.
Apache License 2.0
3.95k stars 1.15k forks source link

Add GZip Support. #522

Closed dameng324 closed 3 years ago

dameng324 commented 3 years ago

通过使用GZipStream封装了一层GzipReadWriteStream来使链路压缩。

原理与SslStream的处理类似 接收: networkStream=>sslStream=>GzipStream解压 发送:GzipStream压缩=>sslStream=>networkStream

唯一一点不够完美的是 GzipStream使用Read读取内容时,会出现有数据了但仍不返回的现象。使用ReadAsync等异步方法接受时OK的,所以GzipReadWriteStream中将改方法设置为Obsolete。

会导致的结果是某些TestCase中直接使用Stream封装成TextReader来读取,导致读不到东西,使用EasyClient是没有这个问题的。

dameng324 commented 3 years ago

增加了GzipHostConfigurator和GzipSecureHostConfigurator 来对不加密压缩和加密压缩两种方式进行单元测试。 单元测试已通过。

dameng324 commented 3 years ago

经过调查,这个问题在.net6中已经修复了。https://github.com/dotnet/runtime/issues/57893

kerryjiang commented 3 years ago

代码看起来不错,不过我有两个关注: 1) Gzip 这个需不需要把它作为SuperSocket的核心功能,比如SuperSocket.WebSocket有自己的压缩算法,这一块和你加的Gzip有点重复。扩展开来思考,具体协议可能会有自己的压缩算法,可能不采用传输层压缩,而是应用层消息压缩。可不可以加一个SuperSocket.Gzip的project,把你加的Gzip相关的代码放到里面去。是否这样会更好? 2) 我不确定 netcore/net5+ 里面的 NetworkStream 的性能是否和async socket相差不大,啃个需要加余个benchmark验证一下。

dameng324 commented 3 years ago

明白你的顾虑,是否可以这样设计:

  1. SuperSocket核心功能中增加传输层压缩的选项,具体的压缩算法通过接口提供,SuperSocket.Gzip项目中提供Gzip链路压缩的实现。
  2. 至于应用层压缩的问题,使用者的IPackageDecoder/Encoder可以来决定是否对数据包进行压缩。具体使用哪种压缩方式最好由使用者根据自己数据包大小评估哪种方式更合适。通过链路压缩理论上可以减少应用层压缩时的内存分配和GC压力。
  3. 增加TcpPipeChannel和基于NetworkStream的StreamPipeChannel的Benchmark性能测试对比。
dameng324 commented 3 years ago

我做了一个benchmark来验证 tcpchannel和streamTcpchannel的性能对比, repo:https://github.com/BigDream324/SuperSocket/tree/channelBenchMark 结果如下:


BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1165 (21H1/May2021Update)
AMD Ryzen 7 5800X, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.100-preview.7.21379.14
  [Host]     : .NET 5.0.9 (5.0.921.35908), X64 RyuJIT
  DefaultJob : .NET 5.0.9 (5.0.921.35908), X64 RyuJIT
Method PackageCount Mean Error StdDev
TcpPipeChannelTest 100 7.734 ms 0.5062 ms 1.493 ms
StreamPipeChannelTest 100 7.646 ms 0.4571 ms 1.348 ms
TcpPipeChannelTest 100000 399.321 ms 9.3090 ms 27.007 ms
StreamPipeChannelTest 100000 401.558 ms 9.6951 ms 28.281 ms
kerryjiang commented 3 years ago

What about memory usage, gc and cpu usage?

kerryjiang commented 3 years ago
  1. SuperSocket核心功能中增加传输层压缩的选项,具体的压缩算法通过接口提供,SuperSocket.Gzip项目中提供Gzip链路压缩的实现。

SuperSocket.Channel 不知道压缩这东西的存在, 只在引用了SuperSocket.Gzip并使用了其中的类库的时候才加载相关的逻辑,是否这样更合适?

dameng324 commented 3 years ago

在Gzip项目中提供 GzipChannelCreatorFactory 的实现,来实现Gzip相应的功能,是这样理解对吧?

dameng324 commented 3 years ago

使用benchmarkDotNet测试CPU使用率时跑不出结果,原因未明。 其他memory和GC在这里


BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1165 (21H1/May2021Update)
AMD Ryzen 7 5800X, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.100-preview.7.21379.14
  [Host]     : .NET 5.0.9 (5.0.921.35908), X64 RyuJIT
  DefaultJob : .NET 5.0.9 (5.0.921.35908), X64 RyuJIT
Method PackageCount Mean Error StdDev Gen 0 Gen 1 Allocated
TcpPipeChannelTest 100 7.639 ms 0.5838 ms 1.721 ms 39.0625 39.0625 664 KB
StreamPipeChannelTest 100 7.512 ms 0.3743 ms 1.103 ms 39.0625 - 659 KB
TcpPipeChannelTest 100000 393.736 ms 9.9431 ms 28.847 ms 6000.0000 1000.0000 104,571 KB
StreamPipeChannelTest 100000 382.756 ms 9.3283 ms 27.505 ms 6000.0000 1000.0000 105,408 KB
dameng324 commented 3 years ago

从NetworkStream的源码可以看到,Read和Write也是调用的Socket的Read和Write方法,理论上性能差别不会很大。https://source.dot.net/#System.Net.Sockets/System/Net/Sockets/NetworkStream.cs

dameng324 commented 3 years ago

等我重新梳理下这块代码再重新提PR,TcpPipeChannel和StreamPipeChannel的问题你这边可以考虑,Gzip这块不再涉及对这块的修改。