V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
nozer
V2EX  ›  Python

45 行 Python 代码实现 TCP 二级代理

  •  1
     
  •   nozer · 2018-07-22 00:45:29 +08:00 · 6929 次点击
    这是一个创建于 2350 天前的主题,其中的信息可能已经有所发展或是发生改变。

    迫于种种原因,有时候我们不得不做一些流量转发的操作。

    比如写爬虫。

    写爬虫时,时常要与目标网站的“运维”、“程序员”斗智斗勇,而“代理”作为行走江湖必备的入门级杀手锏,自然是要逢场必上。

    有匪君子,如切如磋,如琢如磨。 有匪君子,如切如磋,如琢如磨。

    而这个“杀手锏”是不是那么好用却与代理的数量、质量息息相关。

    我时常苦恼于“维护代理”和“切换代理”的麻烦,我堂堂一代“爬虫大王”,冉冉升起的“东方新星”,万千少女的。。。呃,好像有点扯远了。总之,怎能沉溺于区区“代理切换”这种微不足道的小事中。

    那么,如果可以用一个二级代理来封装这些事情岂不美哉!

    所谓空想不如实干,不仅要实干,更要撸起袖子加油干!于是抓起 Python 就撸了一个流量转发程序。

    程序不长,去掉空行只有 45 行,但完整的实现了流量转发的功能,基于 ssclient 实现了二级代理,完整代码如下:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    import socket
    import select
    from concurrent.futures import ThreadPoolExecutor
    
    
    def process(conn, addr):
        proxy_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        proxy_conn.connect(('127.0.0.1', 1080))
        conn.setblocking(socket.MSG_DONTWAIT)
        proxy_conn.setblocking(socket.MSG_DONTWAIT)
        closed = False
        while not closed:
            rlist, _, _ = select.select([conn, proxy_conn], [], [])
            for r in rlist:
                w = proxy_conn if r is conn else conn
                try:
                    d = r.recv(1024)
                    if not d:
                        closed = True
                        break
                    w.sendall(d)
                except:
                    closed = True
                    break
    
        try:
            proxy_conn.shutdown(socket.SHUT_RDWR)
            proxy_conn.close()
        except:
            pass
    
        try:
            conn.shutdown(socket.SHUT_RDWR)
            conn.close()
        except:
            pass
    
    
    def start():
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind(('', 1991))
        s.listen(10)
        pool = ThreadPoolExecutor(128)
        print('Server listen on 0.0.0.0:1991')
        while True:
            conn, addr = s.accept()
            pool.submit(process, conn, addr)
    
    
    if __name__ == '__main__':
        start()
        
    

    流量转发既已实现,剩下的事情就很好解决了,无非是维护一个可用的“代理池”,在转发流量的过程中随机选取一个可用代理,之后进行流量转发即可。

    完整的功能将在我开发完成之后在博客中跟大家分享,并同步发布到我的微信公众号上。有兴趣的朋友可以关注我的公众号哟~

    关注我的公众号,看文章更方便哦。

    本帖同步更新在我的博客上,原文在此

    第 1 条附言  ·  2018-07-22 20:34:50 +08:00
    有必要补充一下:
    首先,热烈欢迎大家提出批评和改善的建议,如果能够切实的给出示例那就更好了。

    第二,这段代码是实现一个想法的代码片段,旨在简单、易懂、快速的实现一个点子,代码并不是为了展示高深的技巧或者鬼神莫测的奇淫巧技,它的出发点就是朴质的。

    第三,这段代码实现了流量转发,但项目的最终目标不是流量转发,而是以流量转发为起点,随机选取可用代理进行代理切换。

    第四,之所以摘取这一段发帖,一方面是为了分享,另一方面也是想看有没有人提出更好的方案或者指出其中可能存在的问题,毕竟没有人敢拍胸脯保证自己写的程序是完美无缺的。

    第四,学习、总结、分享是一个程序员可贵的品质,不但可以提升自己,更可以惠及他人。

    第五,内容是否有用,请不要以自己的主观意识进行评判。我敢保证,本帖对期望了解这方面知识的朋友是有益处的(收藏数可以作证),而且由于它简单,十分有利于理解消化。

    第五,本帖、以及本人不欢迎任何无脑黑、纯杠精、吹牛逼不上税的精神胜利者。因为你是在制造戾气、破坏论坛气氛、给他人带来困扰。

    另外,无论是在网络上、还是在现实中,做人都要有基本的素质,这不但关乎自己的切身利益,也是新时代下新公民的基本要求啊。同志们!!
    32 条回复    2018-08-13 23:21:46 +08:00
    AngelCriss
        1
    AngelCriss  
       2018-07-22 00:57:29 +08:00 via Android   ❤️ 1
    互联网的冗余信息又多了点而已
    429839446
        2
    429839446  
       2018-07-22 01:01:34 +08:00
    居然不用 coroutine
    AngelCriss
        3
    AngelCriss  
       2018-07-22 01:06:35 +08:00 via Android
    @429839446 很奇怪
    ericls
        4
    ericls  
       2018-07-22 04:39:26 +08:00 via iPhone
    @429839446 我也想说 搞一个 asyncio 的对比一下
    nbndco
        5
    nbndco  
       2018-07-22 08:14:05 +08:00 via iPhone
    这代码连逻辑都不对……
    lychnis
        6
    lychnis  
       2018-07-22 09:26:17 +08:00 via Android
    我不懂 python 的人乍一看这是同步的吧 这能用?
    naiba
        7
    naiba  
       2018-07-22 09:54:00 +08:00 via Android
    nozer
        8
    nozer  
    OP
       2018-07-22 10:15:14 +08:00
    @nbndco 还请不吝赐教。
    wwqgtxx
        9
    wwqgtxx  
       2018-07-22 10:27:23 +08:00   ❤️ 2
    从性能上更推荐这个
    https://github.com/wangyu-/tinyPortMapper
    而且就算用 Python 也应该用 Selector 或者是 asyncio 吧,这种用 ThreadPoolExecutor 的不觉得有点效率太低了么
    nozer
        10
    nozer  
    OP
       2018-07-22 10:29:10 +08:00
    @lychnis 兄弟你可能对同步异步有点误解,至于能不能用,当然是能的,至于好不好用当然是比不上键盘的。
    nozer
        11
    nozer  
    OP
       2018-07-22 10:33:33 +08:00
    @429839446 您这建议挺不错,您要不说,我还真不知道有这玩意儿。。。。当然这东西只是初步用来实现我的想法,一切从简单、易于理解的角度出发,在完善后续功能的过程中会考虑性能问题。
    nozer
        12
    nozer  
    OP
       2018-07-22 10:37:01 +08:00
    @wwqgtxx 用 Python 主要是考虑到后面要增加自己爬取、维护共享代理的功能,全部用 Python 实现会更加方便一些。
    nbndco
        13
    nbndco  
       2018-07-22 11:02:55 +08:00
    @nozer
    你虽然在标题里强调简单(行数少),但简单的主要原因只是因为你没写最重要的东西。当前的实现基本上就是同步的,虽然看起来代码是异步的,但是 IO 几乎没有复用,和同步唯一的区别就是一个线程处理两个方向而不是一个方向。并发只有 128 实在是少了一点。
    不过你就算自己做,不用 asyncio,完全 IO 复用,也不会长太多。
    misaka19000
        14
    misaka19000  
       2018-07-22 11:06:38 +08:00
    既然都用了 select 了,就不需要再用多线程了啊。。。
    Cbdy
        15
    Cbdy  
       2018-07-22 11:10:07 +08:00
    source.on('data', data => destination.write(data))
    destination.on('data', data => source.write(data))

    流量转发不是两行代码的事情吗?至于这么麻烦
    nozer
        16
    nozer  
    OP
       2018-07-22 11:19:41 +08:00
    @misaka19000 select 是用在单个请求的两端进行流量转发,线程是用来处理新的请求。
    nozer
        17
    nozer  
    OP
       2018-07-22 11:26:46 +08:00
    @misaka19000 我明白您的意思了。
    nozer
        18
    nozer  
    OP
       2018-07-22 11:34:34 +08:00
    @nbndco 兄弟回答的很中肯,一看就是仔细读过程序的,直接点出了要害。异步处理的却不够好,大家也提出了很多很有用的意见,不过兄弟能不能说下到底哪里连逻辑都不对,我好修改免得让别人看了误入歧途。。。
    nozer
        19
    nozer  
    OP
       2018-07-22 11:43:57 +08:00
    @Cbdy 大神,不需要两行,一行就够了。

    您看我帮您改写一下:s.on('data', data => d.write(data)) ;d.on('data', data => s.write(data)) ;。

    ? java 怎能少了分号
    aheadlead
        20
    aheadlead  
       2018-07-22 12:01:41 +08:00 via iPhone
    流量转发我都用 shell 写的… nc 足够解决很多问题了
    lance6716
        21
    lance6716  
       2018-07-22 12:06:09 +08:00 via Android   ❤️ 1
    @AngelCriss 经常有些半吊子水平的的人写博客,搞得我一搜东西中文博文基本没法看
    congeec
        22
    congeec  
       2018-07-22 12:06:44 +08:00 via iPhone
    楼主你这态度在这社区可不好混

    其实用 nc 或 socat 也就一行
    socat TCP-LISTEN:8080,fork,reuseaddr TCP:google.com:443
    nozer
        23
    nozer  
    OP
       2018-07-22 12:32:04 +08:00
    @aheadlead @congeec 咱们解决的不是同一个问题,我是要以此为基础随机选取代理,并不是仅仅做流量转发,流量转发只是前提,我在帖子里面也有描述(虽然比较啰嗦)。

    关于态度问题,好好讨论问题的,我向来认真对待。在我的回复里面,凡是指出问题的、提出建议的,只要中肯我都接受,并确认后期采纳,其它的我也会给出解释。

    其它的上来就奇奇怪怪的回答,我也只好回应一些奇奇怪怪的东西。

    比如这位大神 @aheadlead 上来就说一些与帖子本身无关的东西,我以为真的是 NB 哄哄的大神,结果点开帖子集一看,全是口水,半点干货没有,纯粹就是杠,这种人,我也只好他进行一番批判。 毕竟,今天是周末嘛。
    nozer
        24
    nozer  
    OP
       2018-07-22 12:33:21 +08:00
    。。。上一篇回复 at 错了人,aheadlead 抱歉抱歉。



    @aheadlead @congeec 咱们解决的不是同一个问题,我是要以此为基础随机选取代理,并不是仅仅做流量转发,流量转发只是前提,我在帖子里面也有描述(虽然比较啰嗦)。

    关于态度问题,好好讨论问题的,我向来认真对待。在我的回复里面,凡是指出问题的、提出建议的,只要中肯我都接受,并确认后期采纳,其它的我也会给出解释。

    其它的上来就奇奇怪怪的回答,我也只好回应一些奇奇怪怪的东西。

    比如这位大神 @lance6716 上来就说一些与帖子本身无关的东西,我以为真的是 NB 哄哄的大神,结果点开帖子集一看,全是口水,半点干货没有,纯粹就是杠,这种人,我也只好他进行一番批判。 毕竟,今天是周末嘛。
    AngelCriss
        25
    AngelCriss  
       2018-07-22 12:34:06 +08:00 via Android
    @nozer 其实你这帖子也没有干活。。
    nozer
        26
    nozer  
    OP
       2018-07-22 12:40:25 +08:00
    至于有没有干货,我的收藏提醒是很好的证明。

    @AngelCriss 好像楼有点歪了,大家还是对代码本身提出批判吧。
    AngelCriss
        27
    AngelCriss  
       2018-07-22 12:44:16 +08:00 via Android
    @nozer 我是看到标题进来的,一看很厉害的样子,然而看了代码,发现真是浪费时间,所以有了 1 楼的回复,说的比较隐晦而已。
    至于代码,没啥好说的,即使不会 Python 的人稍微看点文档也能写出来。
    你批评的那位只不过直白的说出来了而已。
    lihongjie0209
        28
    lihongjie0209  
       2018-07-22 13:26:30 +08:00
    @wwqgtxx #9 io 操作用 thread 没问题
    lance6716
        29
    lance6716  
       2018-07-22 18:09:37 +08:00 via Android
    @nozer 为什么你会觉得干货体现在论坛上,应该是课程作业、论文和项目啊…最近写了一个期末作业是支持作业调度的 shell,前置知识是 apue 这本书和一点点编译原理。然而我也不觉得有什么值得发出来的,毕竟都有 bash 了,我这种小打小闹的也是给互联网填充辣鸡
    Kilerd
        30
    Kilerd  
       2018-07-22 22:09:54 +08:00   ❤️ 2
    现在的中文 IT 圈就充斥着这样大量的低级而且可能充满大量问题的博文,甚至还会用几十年前的技术来实现某一个功能。

    更可笑的是,还存在着更大一群人乐于转载这些内容,对,不加任何分辨的那种。奇怪的是,他们在发布这些文章的时候,不会去 review 一下排版问题。

    最荒诞的是,大量的人根本没有能力去区分这一类的文章。(我见过有人真的认认真真地把那些因为到处复制转载而导致代码无法正常显示的文章阅读了,认真地敲里面的代码。
    nozer
        31
    nozer  
    OP
       2018-07-23 10:35:48 +08:00
    @Kilerd 阁下大才,所谓细微之处见真章,阁下既然指出“排版问题”至少是看过帖子内容的。我又细看了一下帖子,虽然排版实在算不上高明,但原本就是这样排的,只不过其中多出一句与本帖无关的话确系不应该。这是我的的失误,不过帖子没法编辑,也只能放任不管了。想必阁下必是做事细心之人,这点的确值得我辈学习,我会在今后注意这个问题。

    不过再看阁下其它语句,又实在令人遗憾。

    首先,第一句就显得很低级“现在的中文 IT 圈就充斥着这样大量的低级而且可能充满大量问题的博文,甚至还会用几十年前的技术来实现某一个功能。 ”

    阁下以上帝视角站在“中文 IT 圈”之外,先把自己摘出来,再不管三七二十一把对方说成整个圈子的“低级”,我辈“米粒之辉”自然不敢同阁下这种“日月之光”比肩,想必阁下也实战过许多项目,绝不是那种做仅仅做过几个练习 Demo 就目空一切之辈,必然是胸中沟壑万千,从来不会写出这种“低级”博文,否则亦不敢说出如此狂妄之言。

    阁下还说了“可能充满大量问题的博文”,就此暴露了阁下并非就事论事而是借题发挥。先不论博文到底是不是真的有问题,老是用这种“不确定性词汇”就足以让人觉得阁下办事不牢,因为阁下仅仅是凭借自己的臆想就直接得出结论,至于是不是真的“可能充满大量问题”谁关心呢,我只要发泄一下就好,这是不成熟的表现。

    阁下又说“用几十年前的技术来实现某一个功能”,不觉让人发笑。Python2 在 2000 年发布至今不过 18 年,让 python 走向兴盛的 2.7 版也不过是 2010 年发布,距今不过 8 年。用几十年来形容似乎有点夸张过头了吧?况且,代码中用到的 socket 和线程池不过是最基础的手段,不管是 C、java 都在大量使用,你可以说它历史悠久没有异步编程那么优雅,但你要说这玩意儿就不应该再出现在人世间,恕我不敢苟同。

    然后,阁下还有一句“最荒诞的是,大量的人根本没有能力去区分这一类的文章。(我见过有人真的认认真真地把那些因为到处复制转载而导致代码无法正常显示的文章阅读了,认真地敲里面的代码。”

    这话看似没有任何问题,有理有据,现实中也的确不缺乏这样的人。但放在此处也不过是暴露阁下发泄情绪的目的,因为阁下这话的前提就是站不足脚的。阁下首先就把本贴列为“胡乱转载,导致代码无法正常展示”的“这一类文章”,请问阁下,本帖的代码是否无法正常运行,是否有缺行少字?阁下这才是最荒诞的。



    最后,本帖大量无关回复已经带领本帖偏离初衷,失去了讨论的意义,恕我不再进行任何答复。有异议者可以 email 我:daoye.aim#outlook.com


    最最后,所谓“论坛”其核心在“论”,恳请诸位不要戾气滔天,诚挚讨论,有一说一有二说二,共建和谐。
    zeiyso
        32
    zeiyso  
       2018-08-13 23:21:46 +08:00
    socket.sendall( ) 和 non-blocking I/O 不能一起用的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3333 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 00:44 · PVG 08:44 · LAX 16:44 · JFK 19:44
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.