Releases: arloor/HttpProxy
使用配置文件
轻量、稳定、高性能的翻墙方式
重要bugfix:修复因上传/下载过快导致的直接内存泄漏
问题
自己用netty实现的代理,在测速、下载(跑满网速)的情况下总是会报OutOfDirectMemory异常。
原因及解决
在github netty项目下有这样一个issue。描述了这样一个问题。
总结一下里面说的。出现这个异常有两种情况,pooled buf没有release;写太快,超过了极限。
重要摘录:
If i'm right, Netty in NIO-mode tries to offer a "unlimited" layer above a actually limited layer (socket, network). Netty uses DirectMemory to buffer performance differences between both layers. Means "writing to fast" that the buffer capacity in DirectMemory is overcharged by to much Write&Flush on a Netty context?
How can I balance this? Is there an API, where i can request the current loading and use this information to slow down my writes on Netty?
yes... you may try writing faster then the remote peer accepts.... You can check Channel.isWritable() to see if it is writable atm. ChannelInboundHandler.channelWritabilityChanged(...) will be triggered whenever the writability state of the Channel changes
设想的解决方案
既然是写太快了,就控制写的速度,主要就是监控isWritable了。
实现:因为是代理,所以场景是channel A读,转发给channel B(写)。这里的问题就是,A读的太快,B来不及写,全挤在(直接)内存中。所以需要反馈机制。代码如下:
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
boolean canWrite = ctx.channel().isWritable();
logger.warn(ctx.channel()+" 可写性:"+canWrite);
//流量控制,不允许继续读
remoteChannel.config().setAutoRead(canWrite);
super.channelWritabilityChanged(ctx);
}
通过setAutoRead实际上就表示,selector不在关注该channel的op_read。也就不会再读了。
以下来自http://www.cnblogs.com/yuyijq/p/4431798.html
isWritable其实在上一篇文章已经介绍了一点,不过这里我想结合网络层再啰嗦一下。上面我们讲的autoread一般是接收端的事情,而发送端也有速率控制的问题。Netty为了提高网络的吞吐量,在业务层与socket之间又增加了一个ChannelOutboundBuffer。在我们调用channel.write的时候,所有写出的数据其实并没有写到socket,而是先写到ChannelOutboundBuffer。当调用channel.flush的时候才真正的向socket写出。因为这中间有一个buffer,就存在速率匹配了,而且这个buffer还是无界的。也就是你如果没有控制channel.write的速度,会有大量的数据在这个buffer里堆积,而且如果碰到socket又『写不出』数据的时候,很有可能的结果就是资源耗尽。而且这里让这个事情更严重的是ChannelOutboundBuffer很多时候我们放到里面的是DirectByteBuffer,什么意思呢,意思是这些内存是放在GC Heap之外。如果我们仅仅是监控GC的话还监控不出来这个隐患。
那么说到这里,socket什么时候会写不出数据呢?在上一节我们了解到接收端有一个read buffer,其实发送端也有一个send buffer。我们调用socket的write的时候其实是向这个send buffer写数据,如果写进去了就表示成功了(所以这里千万不能将socket.write调用成功理解成数据已经到达接收端了),如果send buffer满了,对于同步socket来讲,write就会阻塞直到超时或者send buffer又有空间(这么一看,其实我们可以将同步的socket.write理解为半同步嘛)。对于异步来讲这里是立即返回的。
那么进入send buffer的数据什么时候会减少呢?是发送到网络的数据就会从send buffer里去掉么?也不是这个样子的。还记得TCP有重传机制么,如果发送到网络的数据都从send buffer删除了,那么这个数据没有得到确认TCP怎么重传呢?所以send buffer的数据是等到接收端回复ACK确认后才删除。那么,如果接收端非常慢,比如CPU占用已经到100%了,而load也非常高的时候,很有可能来不及处理网络事件,这个时候send buffer就有可能会堆满。这就导致socket写不出数据了。而发送端的应用层在发送数据的时候往往判断socket是不是有效的(是否已经断开),而忽略了是否可写,这个时候有可能就还一个劲的写数据,最后导致ChannelOutboundBuffer膨胀,造成系统不稳定。
所以,Netty已经为我们考虑了这点。channel有一个isWritable属性,可以来控制ChannelOutboundBuffer,不让其无限制膨胀。至于isWritable的实现机制可以参考前一篇。
扶墙功能已经实现
实现了不可描述的功能
2018-11-18 08:44:54.446 [nioEventLoopGroup-4-1] INFO c.a.p.r.DefaultHttpMessageDecoderAdapter - 处理请求 HttpRequest{body='null', method='CONNECT', host='adservice.google.co.jp', port=443, path=adservice.google.co.jp:443}
2018-11-18 08:44:54.457 [nioEventLoopGroup-2-1] INFO c.a.p.proxyconnection.ProxyConnectionHandler - 连接成功: adservice.google.co.jp:443
2018-11-18 08:44:54.630 [nioEventLoopGroup-4-1] INFO c.a.p.r.DefaultHttpMessageDecoderAdapter - 处理请求 HttpRequest{body='null', method='CONNECT', host='ogs.google.com', port=443, path=ogs.google.com:443}
2018-11-18 08:44:54.650 [nioEventLoopGroup-2-2] INFO c.a.p.proxyconnection.ProxyConnectionHandler - 连接成功: ogs.google.com:443
2018-11-18 08:45:45.016 [nioEventLoopGroup-4-1] INFO c.a.p.r.DefaultHttpMessageDecoderAdapter - 处理请求 HttpRequest{body='null', method='CONNECT', host='r1---sn-a5meknek.googlevideo.com', port=443, path=r1---sn-a5meknek.googlevideo.com:443}
2018-11-18 08:45:45.139 [nioEventLoopGroup-2-1] INFO c.a.p.proxyconnection.ProxyConnectionHandler - 连接成功: r1---sn-a5meknek.googlevideo.com:443
2018-11-18 08:45:54.703 [nioEventLoopGroup-4-1] INFO c.a.p.r.DefaultHttpMessageDecoderAdapter - 处理请求 HttpRequest{body='null', method='CONNECT', host='www.google.co.jp', port=443, path=www.google.co.jp:443}
2018-11-18 08:45:54.754 [nioEventLoopGroup-2-1] INFO c.a.p.proxyconnection.ProxyConnectionHandler - 连接成功: www.google.co.jp:443
可以看到,代理服务器成功穿过墙收到了一些“不合法”的请求,并且成功连接到“不合法”的服务器,返回响应到墙内的过程没有打日志。
如何运行以fq
1.在一台ip没有被墙的服务器上运行proxyserver.jar
2.用winrar等解压工具修改proxyserver.jar中的proxy.properties文件,将server.host
修改为上一步机器的ip
3.在本机运行proxyclient.jar
4.配置本机代理设置,可以通过chrome的switchyOmega插件修改。
5.可以正常使用了
另外,如果是windows电脑,并且没有装java,可以使用proxyclient.exe作为客户端。这是我用go写的代理客户端,也很好用。最大的优点是占用内存小。
同时,go写的客户端还可以在http://127.0.0.1:8080/pac
提供了pac文件(pac.txt和exe要在同一文件下才可以),会自动设置windows注册表为使用此pac
使用效果
使用了香港的vps,此代理很好,youtube 1080p很稳定。
已知问题
netty的直接内存好像无法回收。。运行较久或者持续大流量下载内部报OOM。通过jmc看,堆内存占据不多。但top命令显示java进程占用res达到1.几g。
代理客户端—加/解密—代理服务器
当前更新
现在proxyclient和proxyserver配合使用,实现:
浏览器—代理客户端—加解密—代理服务器—web服务器
当前加解密十分简单:按字节取反
下一步工作
实现更加复杂的加密,比如DES(虽然也只是很简单的工作。。)
稳定的http代理服务器
稳定可用
java -jar proxyserver-1.0-jar-with-dependencies.jar
2018-11-17 00:25:59.883 [main] INFO com.arloor.proxyserver.ServerProxyBootStrap - 开启代理 端口:8080
2018-11-17 00:26:48.851 [nioEventLoopGroup-4-1] INFO c.a.p.r.DefaultHttpMessageDecoderAdapter - 处理请求 HttpRequest{method='CONNECT', host='github.com', port=443, path=github.com:443}
2018-11-17 00:26:49.031 [nioEventLoopGroup-2-1] INFO c.a.p.proxyconnection.ProxyConnectionHandler - 连接成功: github.com:443
2018-11-17 00:26:49.053 [nioEventLoopGroup-4-1] INFO c.a.p.r.DefaultHttpMessageDecoderAdapter - 处理请求 HttpRequest{method='null', host='null', port=0, path=null}
2018-11-17 00:26:49.246 [nioEventLoopGroup-4-1] INFO c.a.p.r.DefaultHttpMessageDecoderAdapter - 处理请求 HttpRequest{method='null', host='null', port=0, path=null}
2018-11-17 00:26:49.250 [nioEventLoopGroup-4-1] INFO c.a.p.r.DefaultHttpMessageDecoderAdapter - 处理请求 HttpRequest{method='null', host='null', port=0, path=null}