我在 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
|