我在 stackoverflow 提问了,暂时还没有人回复。网址在此。 https://stackoverflow.com/questions/56786505/when-i-created-a-socket5-proxy-server-using-asyncio-my-code-was-unexpectedly-bl
# -*- coding: utf-8 -*- import asyncio from struct import unpack, pack async def handle_echo(reader, writer): data = await reader.read(1024 * 64) addr = writer.get_extra_info('peername') print(f"connect from {addr!r}") if len(data) < 3: writer.close() return result = unpack('!BBB', data[:3]) writer.write(b'\x05\x00') await writer.drain() data = await reader.read(1024 * 64) result = unpack('!4B', data[:4]) if result[0] == 5 and result[1] == 1 and result[3] == 3: host_len = unpack('!B', data[4:5])[0] host = data[5:host_len + 5].decode() port = unpack('!H', data[host_len + 5:])[0] print(f'len {host_len},host {host},port {port}') try: reader_remote, writer_remote = await asyncio.open_connection(host, port) writer.write(pack('!5B', 5, 0, 0, 3, host_len) + host.encode() + pack('!H', port)) await writer.drain() print(f'connect success !{host}') except (TimeoutError, ConnectionRefusedError) as _: print(f'connect failed !{host}') writer.write(pack('!5B', 5, 3, 0, 3, host_len) + host.encode() + pack('!H', port)) await writer.drain() writer.close() return while True: client_data = await reader.read(1024 * 64) print(f'{host} client->local {len(client_data)}') if not client_data: writer_remote.close() writer.close() print(f'{host} disconnect') return writer_remote.write(client_data) await writer_remote.drain() print(f'{host} local->remote !{len(client_data)}') remote_data = await reader_remote.read(1024 * 64) print(f'{host} remote->local! {len(remote_data)}') writer.write(remote_data) await writer.drain() print(f'{host} local->client! {len(remote_data)}') async def main(): server = await asyncio.start_server( handle_echo, '0.0.0.0', 3333) addr = server.sockets[0].getsockname() print(f'Serving on {addr}') async with server: await server.serve_forever() asyncio.run(main())
如果一个网页只有一个 html 的话就没问题,例如:example.com 。但是像 baidu.com 这种的要加载好多 js 和 css 的就不行了,打印几行日志就卡住了,应该是卡在这里,client_data = await reader.read(1024 * 64),不知道为什么。系统 win10,python3.7.3。
非常感谢大家的回复,第一次写asyncio的异步socket通信没什么经验,我尝试下全双工的写法
1 qizheshang 2019-06-27 17:32:53 +08:00 asyncio.run(main()),你这个 run 从哪里来的?跑不通 # -*- coding: utf-8 -*- python3 这个完全可以去掉了 |
![]() | 2 wwqgtxx 2019-06-27 17:41:03 +08:00 via iPhone @qizheshang py3.7 新加的函数 |
![]() | 3 lcdtyph 2019-06-27 18:43:49 +08:00 我觉得问题出在 handle_echo 的那个 while True 里, 如果是远程先发送数据的话就死在 await reader.read 上了 |
![]() | 4 janxin 2019-06-27 18:45:09 +08:00 @qizheshang 3.7 asyncio 加了很多新函数 |
![]() | 5 exch4nge 2019-06-27 18:53:33 +08:00 如果资源大于 64k ( 1024*64 )的时候,一次性读不完所有内容,所以你仅仅把一部分请求内容转发,一端会继续等待,并不会发送数据 |
![]() | 6 XiaoxiaoPu 2019-06-27 18:59:08 +08:00 代理要全双工的,你的代码只做了一个方向的数据中转。从网上找了个例子,你可以参考下: github .com /msoedov/toxic_proxy/blob/master/toxic_proxy/app.py#L41 |
7 2pang 2019-06-27 19:07:21 +08:00 从 A 读发给 B 然后从 B 读发给 A 你觉得这很容易死锁么 假如应该从 B 读发给 A 的时候 你一直等待在从 A 读 |
![]() | 8 exch4nge 2019-06-27 19:13:03 +08:00 @exch4nge #5 补充一下,你这个 while True 里面逻辑是严格有顺序的,就是先从 Client 读数据,然后发送到 Server,然后从 Server 等待回应,然后再把回应发到 Client。这种模式在 HTTP 1.1 却是没什么问题,是请求-响应模式。不过,资源大于 64k 的时候,你紧紧把部分资源回应发到 Client,Client 还会等着你发剩余的部分,不会向你这个 Proxy 发送数据,所以你会在那个语句卡住。 1、 要么读写的时候读到数据干为止再转发, 2、要么同时分别处理 C -> S 和 S -> C 两个方向的请求,比如用两个线程(或协程)一个负责从 Client 读数据转发到 Server,另一个负责从 Server 读数据转发到 Client |
![]() | 9 exch4nge 2019-06-27 19:16:46 +08:00 @exch4nge #8 却是 -> 确实, 紧紧 -> 仅仅; 上面说的两种中,还是 2 好一些,难免遇到 server 卡住,发一部分不给你发剩余的,Proxy 又没有分析 http 响应包不知道包边界在哪。 |
![]() | 10 1462326016 OP @qizheshang 这个是 Python3.7 新添加的函数,跑不通的话可以用 ```loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather(main()))``` 这个 |
![]() | 11 1462326016 OP @lcdtyph 但是远程我在上面只是进行了 tcp 连接,没有其他操作,应该不会是远程先发数据 |
![]() | 12 1462326016 OP @exch4nge 数据大小的话可以看 stackoverflow 里边贴的日志,我打印了数据大小,没有超出。。我怀疑是不是没有全部发送出去,像之前的 sock.send 和 sock.sendall 的区别。 |
![]() | 13 1462326016 OP @exch4nge 好的好的, 非常感谢, 我尝试一下 |
![]() | 14 1462326016 OP @2pang 了解了,感谢提醒,我尝试下全双工的写法。 |
![]() | 15 1462326016 OP @XiaoxiaoPu 我参考一下,尝试下全双工的写法, 感谢 |
![]() | 16 1462326016 OP 感谢大家的帮助,改成全双工已经没有问题了。 ``` # -*- coding: utf-8 -*- import asyncio from struct import unpack, pack async def handle_echo(reader, writer): data = await reader.read(1024 * 64) addr = writer.get_extra_info('peername') print(f"connect from {addr!r}") if len(data) < 3: print('too short...') writer.close() return result = unpack('!BBB', data[:3]) writer.write(b'\x05\x00') await writer.drain() data = await reader.read(1024 * 64) result = unpack('!4B', data[:4]) if result[0] == 5 and result[1] == 1 and result[3] == 3: host_len = unpack('!B', data[4:5])[0] host = data[5:host_len + 5].decode() port = unpack('!H', data[host_len + 5:])[0] print(f'len {host_len},host {host},port {port}') try: reader_remote, writer_remote = await asyncio.open_connection(host, port) writer.write(pack('!5B', 5, 0, 0, 3, host_len) + host.encode() + pack('!H', port)) await writer.drain() print(f'connect success !{host}') except (TimeoutError, ConnectionRefusedError) as _: print(f'connect failed !{host}') writer.write(pack('!5B', 5, 3, 0, 3, host_len) + host.encode() + pack('!H', port)) await writer.drain() writer.close() return up_stream = _pipe(reader, writer_remote, host) down_stream = _pipe(reader_remote, writer, host) await asyncio.gather(up_stream, down_stream) if result[0] == 5 and result[1] == 1 and result[3] == 1: ip = '.'.join([str(a) for a in unpack('!BBBB', data[4:8])]) port = unpack('H', data[8:10])[0] print(f'ip {ip},port {port}') try: reader_remote, writer_remote = await asyncio.open_connection(ip, port) writer.write(pack('!8B', 5, 0, 0, 1, *unpack('!BBBB', data[4:8])) + pack('!H', port)) await writer.drain() print(f'connect success !{ip}') except (TimeoutError, ConnectionRefusedError) as _: print(f'connect failed !{ip},{repr(_)}') writer.write(pack('!8B', 5, 3, 0, 1, *unpack('!BBBB', data[4:8])) + pack('!H', port)) await writer.drain() writer.close() return up_stream = _pipe(reader, writer_remote, ip) down_stream = _pipe(reader_remote, writer, ip) await asyncio.gather(up_stream, down_stream) async def _pipe(reader, writer, host): while reader.at_eof: try: data = await reader.read(1024 * 64) if not data: writer.close() break except (ConnectionAbortedError, ConnectionResetError) as _: writer.close() print(f'{host} 异常退出 {repr(_)}') break try: writer.write(data) await writer.drain() except (ConnectionAbortedError, ConnectionResetError) as _: writer.close() print(f'{host} 异常退出 {repr(_)}') break print(f'{host} 退出') async def main(): server = await asyncio.start_server( handle_echo, '0.0.0.0', 3333) addr = server.sockets[0].getsockname() print(f'Serving on {addr}') async with server: await server.serve_forever() loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather(main())) ``` 代码在此 |
![]() | 17 1462326016 OP 啊,格式乱了,不知道怎么格式化成 markdown。。。无语。 |
18 qizheshang 2019-07-09 19:05:17 +08:00 @janxin 好吧 我一直用的是 3.6 |