基于python asyncio实现的简单反代

一直想用python实现一个简单的反代工具,最近终于着手做了一下,这篇文章主要是记录一下学习的东西。


参考资料

asycio实现TCP代理

asycio官方文档

解决StreamReader读取长数据一直阻塞

ngrok原理简单分析

设计思路

内网穿透

内网穿透的效果就是把只能在内网访问到的设备和服务暴露在公网上。例如内网的网站,俗称内网外入。

由于个人宽带不一定拥有公网IP,和有对称NAT的存在,所以在内网里可以访问公网的设备能向在公网的服务器请求TCP连接,但服务器却没有办法通过公网向在内网的PC请求连接。

于是可以先由内网设备与在公网的服务器建立一条TCP连接,然后其他设备访问公网服务器的某个端口,所有流量由服务器通过这条连接转发给内网设备。

TCP代理

首先是实现一个基础的TCP代理功能,因为无论是在服务器还是内网设备,都需要转发流量。

原理很简单,就是两个套接字,套接字A用来与服务连接,套接字B用来访问要代理的应用。然后不停地将A收到的发给B,将B收到的发给A。

最后我这里用了asyncio的套接字服务asyncio.start_server

监听套接字接收到新的请求以后会回调handle_proxy

async def join_pipe(reader, writer):
    try:
        while not reader.at_eof():
            writer.write(await reader.read(2048))
    finally:
        writer.close()

async def handle_proxy(local_reader, local_writer):
    try:
        remote_reader, remote_writer = await asyncio.open_connection(
            'jwzx.cqupt.edu.cn', 80
        )
        task_list = [
            asyncio.create_task(join_pipe(remote_reader, local_writer)),
            asyncio.create_task(join_pipe(local_reader, remote_writer)),
        ]
        print(f'New TCP connection link { local_writer.get_extra_info("peername") !r} to { remote_writer.get_extra_info("peername") !r}')
        done, pending = await asyncio.wait(task_list)
    finally:
        local_writer.close()

这个转发部分就是我的反代程序的核心部分了。

server与client

其实Client和Server就是两个几乎一模一样的代理服务器。但又右一点不一样。

考虑到例如http请求,浏览器请求一个页面的时候可能会发多次TCP连接和断开的请求,一旦server和client断开以后,server就没有办法主动连接client了,所以我参考ngrok的设计,也设计了一条Control tunnel的连接一直保持,用来管理用于proxy的通道。

当proxy断开以后,如果接到新的连接请求,就通过Control告诉client,让它与server建立一条新的proxy tunnel,并开始转发。

也就是说Server在监听到外部服务的时候会通知Client发一条新的连接Proxy,将Proxy连接的套接字B与外部的套接字A连在一起进行转发。

Client在监听到Control发来的新请求以后会按照信息向新的端口发连接,成功建立Proxy,得到套接字A以后,再开始与内网服务器建立链接,得到套接字B,然后链接这两个套接字开始转发。

然后就按照外部客户的要求,该发送就发送,该断开就断开。这样虽然每次会断开Server与Client的连接,但代码逻辑更简单了,写起来更方便。

代码实现

Demo

Read the rest

C#实现俄罗斯方块AI

 

QQ20170304232315

这个脑洞起源于一道题目。大意是有固定的格子,制作一个AI来让游戏不死。

 

这题的格子很少,于是可以分类讨论,只要8种情况(好像是?)

先后我去找了一些资料。
发现实现AI的大概思路都是利用启发函数来得到每种状态每个位置的最优解,只考虑当前的一个方块。
对于俄罗斯方块游戏的设计,我用了wiki上面的规定和游戏规则:
1.10个格子宽+22个格子的高,通常情况下21-22个格子被挡住。
2.有 “I”, “O”, “J”, “L”, “S”, “Z”, “T”.七种方块。
3.允许获得下一个方块的形状(因为我设计的AI不许要考虑下一个方块,所以我没有在界面中实现)
4.更多具体参见:http://tetris.wikia.com/wiki/Tetris_Guideline

 

游戏部分具体的实现不细说,方法很多。
AI部分:
我试过多种启发,后来找到了一种Pierre Dellacherie’s Algorithm,这里我主要说。
这种算法有如下评价
1.Landing Height):本次下落的方块中点地板的距离
2.Row eliminiated):本次下落后此方块贡献(参与完整行组成的个数)*完整行的行数
3.Row transitions):在同一行,方块 从无到有 或 从有到无 算一次(边界算有方块)
4.Column transition):在同一列,方块 从无到有 或 从有到无 算一次(边界算有方块)
5.Hole num):空洞的数量。空洞无论有多大,只算一个。一个图中可能有多个空洞
6.Well sum):井就是两边都有方块的空列。(空洞也可以是井,一列中可能有多个井)。此值为所有的井以1为公差首项为1的等差数列的总和
例:共三个井,2,3,1 wellsum=(1+2)+(1+2+3)+1;

它们的权值分别为

1
-4.500158825082766
2
3.4181268101392694
3
-3.2178882868487753
4
-9.348695305445199
5
-7.899265427351652
6
-3.3855972247263626

分数为所有评价和权值的乘积的和
经过多次实验发现这个启发非常可靠。在目前的测试看来100W行后还在稳定运行。

 

AItetris源码

参考资料:

http://imake.ninja/el-tetris-an-improvement-on-pierre-dellacheries-algorithm/

Tetris AI – The (Near) Perfect Bot

 

 … Read the rest

c#实现XM文件播放/转码

这个脑洞也是很久以前就有的了。今天总算把坑填了。

XM文件就是各种注册机啊修改器啊里面的音乐,出奇的魔性啊,dididi~biubiubiu~很好听嘛………

pg

工具可以实现XM到MP3的格式转换,顺便加了播放功能。

基于bassmod的类库实现,这个类库好像很厉害,暴风之类的都使用的这个库。… Read the rest