V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gouchaoer
V2EX  ›  Java

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

  •  
  •   gouchaoer · 2017-03-24 15:46:04 +08:00 · 5159 次点击
    这是一个创建于 2804 天前的主题,其中的信息可能已经有所发展或是发生改变。

    由于厂里爬虫业务需要,我一直想复制国外的初创公司 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/ChannelPair.java#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 就好了。

    半个月前的问题: https://www.v2ex.com/t/346155 ,我终于搞定了

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

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

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

    写了 500 行,如果早点发现 AIO 估计要好很多
    coolcfan
        5
    coolcfan  
       2017-03-24 16:28:03 +08:00
    @gouchaoer #4 不过 AIO 背后要有线程池,不过 whatever ,线程池配置成 1 就好了。(其实 Netty 也可以)
    hiro0729
        6
    hiro0729  
       2017-03-24 16:29:28 +08:00
    netty 把 nio 的坑都填了,你竟然不用而去选择把坑扒开了往里跳,何苦呢
    sagaxu
        7
    sagaxu  
       2017-03-24 16:33:19 +08:00
    @gouchaoer 你可以试试 Go 语言写个 lib , ios 和 android 通吃,比 AIO 还能简单不少
    gouchaoer
        8
    gouchaoer  
    OP
       2017-03-24 16:37:43 +08:00
    @sagaxu Go 语言是垃圾,不用
    SoloCompany
        9
    SoloCompany  
       2017-03-25 01:02:04 +08:00
    这个和你那 70 行 php 代码完全没关系好吧
    NIO 本身就是个底层的玩意儿,不封装没法用的
    要封装到可用,那可不是几百行能完成的事情
    moyang
        10
    moyang  
       2017-03-25 04:27:16 +08:00 via Android
    兄弟,你好!我是在群里跟你聊过的。近期我们有计划用一个新方式获取中国大陆 ip ,来突破之前的问题(ip 太少,全中国只有 40k)。有进展的时候我 qq 上通知你
    gouchaoer
        11
    gouchaoer  
    OP
       2017-03-25 09:40:53 +08:00 via Android
    @moyang 多谢,贵厂的技术让人印象深刻,比如 chrome 插件机制根本没提供 socket 权限,还是可以通过 hack 来搞定很多东西
    moyang
        12
    moyang  
       2017-03-25 16:22:45 +08:00 via Android
    @gouchaoer 我司跟做黑产的差不多,全都是 hack
    carrotuestc
        13
    carrotuestc  
       2017-04-20 09:37:02 +08:00
    膜拜超哥
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3140 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 13:45 · PVG 21:45 · LAX 05:45 · JFK 08:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.