基于 Java 的 NIO 的 luminati.io 代理方案客户端填坑记录 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
gouchaoer
V2EX    Java

基于 Java 的 NIO 的 luminati.io 代理方案客户端填坑记录

  •  
  •   gouchaoer Mar 24, 2017 5690 views
    This topic created in 3320 days ago, the information mentioned may be changed or developed.

    由于厂里爬虫业务需要,我一直想复制国外的初创公司 luminati.io 的代理方案,魔改一下可以应用到厂里的一些业务上。这玩意儿也没啥大不了的,本质上是就是个服务器端转发了 1 次+客户端反向连接转发 1 次的代理隧道之类的东东,我断断续续研究了几个月以后终于打通了。和一般的 http 代理服务器原理一样,服务器端和客户端本质上都是异步并发的 tcp 操作,它们用一个随机数字相互 tcp 握手以后爬虫(浏览器或者 httpclient )设置服务器端为代理,并且在 header 里面加上这个随机数字(为了支持浏览器+https ,这个随机数字似乎只能放在 Proxy-Authorization 中),最后通过爬虫<-->服务器端<-->客户端<-->互联网这样来访问网站。 demo 都是用 php 来实现的,虽然服务器端可以继续用 php ,但是客户端我需要用 java 重写。本来只有 70 行的 php 客户端代码,结果硬生生的花了我几个星期的时间才翻译成了 java 。也许是我 java 水平不够,也许是 NIO 太坑了,总之今天要来记录这些个坑。

    由于必须同时保持几十条的 tcp 连接,所以客户端必须是异步的、单线程的和并发的,我在 github 上翻了很久终于找了个安卓的代理的 Demo : https://github.com/dawsonice/KissProxy 。看他的介绍很不错: NIO based 就可以不依赖 netty 之类的(我的业务需要尽量不依赖第三方的库)、 Single Thread 单线程(这是必然的,我肯定不接受线程池方案)、支持 HTTPS 那是必须的,总之我觉得这个 demo 不错于是就打算照着他的例子用 NIO 来写了,然后开启了漫漫的填坑之旅。

    我照着这个 KissProxy 就慢慢魔改起来,结果遇到 2 个坑。第一个就是在发起 TCP 连接的时候用了同步的方式: https://github.com/dawsonice/KissProxy/blob/master/src/me/dawson/proxyserver/core/ChannelPair.java#L177 ,单线程情况下这就阻塞了,所以这个代理服务器实现是不对的。解决方法当然是把发起 tcp 请求的 SocketChannel 操作弄成异步的,可是这个 NIO 并没有办法直接对 SocketChannel 设置回调,需要通过 Selector 机制来注册 OP_CONNECT 和 OP_READ 之类的,搞起了虽然麻烦了点不过还是搞定了。

    第二个就是 NIO 的 SocketChannel 在写的时候写缓存可能是满的写不进去,需要注册 OP_WRITE 事件等待写缓存可写,他没有考虑这一情况就会导致数据丢失: https://github.com/dawsonice/KissProxy/blob/master/src/me/dawson/proxyserver/core/ChannelPairjava#L235 。我在实际使用的时候就因为 SocketChannel 的写缓存经常满导致出错(因为我的代理相当于经过了 2 次转发,服务器端接收数据包缓存满了的话客户端也发不出去,导致客户端写缓存满容易触发)。总之又注册上了 OP_WRITE 事件,把缓存满的情况考虑进去,但是这个 OP_WRITE 的触发条件是“只要写缓存没满就触发”,而不是“写缓存从满的状态到可以写才触发”这样,这就导致每次 select 就立刻返回了。然后我就怒了这 NIO 居然暴露这么底层的细节给开发者就算了,这 API 设计太反人类了,搞定了之后现在代码已经成了一锅粥了。

    然后问题又来了,我发现整个事件循环是吃满 CPU 的, select 如果没有事件返回不是可以阻塞么(我把 OP_WRITE 事件去掉了,因为这个事件总是触发的,然后设置一个超时时间),一看似乎是 JDK 的一个 bug : http://stackoverflow.com/questions/35858537/selector-selecttimeout-returns-0-before-timeout 。为了保险我稍微魔改了一下 select 的机制,如果 select 到的事件为空(排除 OP_WRITE )就 sleep 一小会儿,虽然比较 dirty 不过能 work 就好了。

    半个月前的问题: t/346155 ,我终于搞定了

    原文: http://qsalg.com/?p=557

    13 replies    2017-04-20 09:37:02 +08:00
    coolcfan
        1
    coolcfan  
       Mar 24, 2017 via Android
    敢于直接对着那个 Selector API 编程的人都是猛士……
    话说尝试过研究 Java.NIO2 里 AIO 的部分么,好像直接提供了基于回调的机制(CompletionHandler 什么的)。
    sagaxu
        2
    sagaxu  
       Mar 24, 2017
    1. 几十个连接直接上多线程就行了,单机 1 万个以内线程的 IO 型应用,调度开销忽略不计。
    2. 我们 Java 码农一般是不会直接用 NIO 的,我们喜欢用 mina/netty/vertx 。
    sagaxu
        3
    sagaxu  
       Mar 24, 2017
    70 行 PHP 翻译成功能一样的 Java ,如果超过 100 行,就要想一下是不是姿势有问题了
    gouchaoer
        4
    gouchaoer  
    OP
       Mar 24, 2017
    @coolcfan 草,我刚看了一下 AIO ,发现这个正是我需要的,可惜 NIO 的屎已经吃下去了

    @sagaxu mina 和 netty 太重了,我不能使用第三方库,这可能放到 app 里面,至于几十 tcp 连接用几十个线程池更新不可能

    写了 500 行,如果早点发现 AIO 估计要好很多
    coolcfan
        5
    coolcfan  
       Mar 24, 2017
    @gouchaoer #4 不过 AIO 背后要有线程池,不过 whatever ,线程池配置成 1 就好了。(其实 Netty 也可以)
    hiro0729
        6
    hiro0729  
       Mar 24, 2017
    netty 把 nio 的坑都填了,你竟然不用而去选择把坑扒开了往里跳,何苦呢
    sagaxu
        7
    sagaxu  
       Mar 24, 2017
    @gouchaoer 你可以试试 Go 语言写个 lib , ios 和 android 通吃,比 AIO 还能简单不少
    gouchaoer
        8
    gouchaoer  
    OP
       Mar 24, 2017
    @sagaxu Go 语言是垃圾,不用
    SoloCompany
        9
    SoloCompany  
       Mar 25, 2017
    这个和你那 70 行 php 代码完全没关系好吧
    NIO 本身就是个底层的玩意儿,不封装没法用的
    要封装到可用,那可不是几百行能完成的事情
    moyang
        10
    moyang  
       Mar 25, 2017 via Android
    兄弟,你好!我是在群里跟你聊过的。近期我们有计划用一个新方式获取中国大陆 ip ,来突破之前的问题(ip 太少,全中国只有 40k)。有进展的时候我 qq 上通知你
    gouchaoer
        11
    gouchaoer  
    OP
       Mar 25, 2017 via Android
    @moyang 多谢,贵厂的技术让人印象深刻,比如 chrome 插件机制根本没提供 socket 权限,还是可以通过 hack 来搞定很多东西
    moyang
        12
    moyang  
       Mar 25, 2017 via Android
    @gouchaoer 我司跟做黑产的差不多,全都是 hack
    carrotuestc
        13
    carrotuestc  
       Apr 20, 2017
    膜拜超哥
    About     Help     Advertise     Blog     API     FAQ     Solana     3841 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 152ms UTC 05:05 PVG 13:05 LAX 22:05 JFK 01:05
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86