【发布时间】:2019-05-03 23:04:24
【问题描述】:
我想要做什么
我正在尝试模拟以下简单 socat(1) 命令的行为:
socat tcp-listen:SOME_PORT,fork,reuseaddr exec:'SOME_PROGRAM'
上述命令创建了一个分叉 TCP 服务器,它为每个连接分叉并执行 SOME_PROGRAM,将所述命令的 stdin 和 stdout 重定向到 TCP 套接字。
这是我想要实现的目标:
- 使用
asyncio创建一个简单的 TCP 服务器来处理多个并发连接。 - 每当收到连接时,将
SOME_PROGRAM作为子进程启动。 - 将从套接字接收到的任何数据传递到
SOME_PROGRAM的标准输入。 - 将从
SOME_PROGRAM的标准输出接收到的任何数据传递到套接字。 -
SOME_PROGRAM退出时,将告别消息连同退出代码一起写入套接字并关闭连接。
我想在纯 Python 中执行此操作,而不使用使用 asyncio 模块的外部库。
到目前为止我所拥有的
这是我目前写的代码:
import asyncio
class ServerProtocol(asyncio.Protocol):
def connection_made(self, transport):
self.client_addr = transport.get_extra_info('peername')
self.transport = transport
self.child_process = None
print('Connection with {} enstablished'.format(self.client_addr))
asyncio.ensure_future(self._create_subprocess())
def connection_lost(self, exception):
print('Connection with {} closed.'.format(self.client_addr))
if self.child_process.returncode is not None:
self.child_process.terminate()
def data_received(self, data):
print('Data received: {!r}'.format(data))
# Make sure the process has been spawned
# Does this even make sense? Looks so awkward to me...
while self.child_process is None:
continue
# Write any received data to child_process' stdin
self.child_process.stdin.write(data)
async def _create_subprocess(self):
self.child_process = await asyncio.create_subprocess_exec(
*TARGET_PROGRAM,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE
)
# Start reading child stdout
asyncio.ensure_future(self._pipe_child_stdout())
# Ideally I would register some callback here so that when
# child_process exits I can write to the socket a goodbye
# message and close the connection, but I don't know how
# I could do that...
async def _pipe_child_stdout(self):
# This does not seem to work, this function returns b'', that is an
# empty buffer, AFTER the process exits...
data = await self.child_process.stdout.read(100) # Arbitrary buffer size
print('Child process data: {!r}'.format(data))
if data:
# Send to socket
self.transport.write(data)
# Reschedule to read more data
asyncio.ensure_future(self._pipe_child_stdout())
SERVER_PORT = 6666
TARGET_PROGRAM = ['./test']
if __name__ == '__main__':
loop = asyncio.get_event_loop()
coro = loop.create_server(ServerProtocol, '0.0.0.0', SERVER_PORT)
server = loop.run_until_complete(coro)
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
还有我试图作为子进程运行的./test 程序:
#!/usr/bin/env python3
import sys
if sys.stdin.read(2) == 'a\n':
sys.stdout.write('Good!\n')
else:
sys.exit(1)
if sys.stdin.read(2) == 'b\n':
sys.stdout.write('Wonderful!\n')
else:
sys.exit(1)
sys.exit(0)
不幸的是,上面的代码并没有真正起作用,我有点不知道接下来要尝试什么。
按预期工作:
- 子进程已正确生成,并且似乎也正确接收了来自套接字的输入,因为我可以从
htop看到它,而且我也可以看到,只要我发送b\n它就会终止。李>
什么不符合预期:
基本上其他的...
- 子进程的输出永远不会发送到套接字,实际上根本就不会读取。调用
await self.child_process.stdout.read(100)似乎永远不会终止:相反,它只会在子进程死亡之后终止,结果只是b''(一个空的bytes对象)。 - 我无法理解子进程何时终止:如上所述,我想在发生这种情况时向套接字发送“再见”消息以及
self.child_process.returncode,但我不知道如何以有意义的方式做到这一点。
我尝试了什么:
- 我尝试使用
asyncio.loop.subprocess_exec()而不是asyncio.create_subprocess_exec()创建子进程。这解决了知道进程何时终止的问题,因为我可以实例化asyncio.SubprocessProtocol的子类并使用它的process_exited()方法,但是根本没有帮助我,因为如果我这样做了这样一来,我就不想再与流程'stdin或stdout交谈了!也就是说,我没有要与之交互的Process对象... - 我尝试过使用
asyncio.loop.connect_write_pipe()和loop.connect_read_pipe(),但没有成功。
问题
那么,有人可以帮我弄清楚我做错了什么吗?必须有一种方法可以使这项工作顺利进行。当我第一次开始时,我正在寻找一种方法来轻松地使用一些管道重定向,但我不知道这是否可能在这一点上。是吗?看起来应该是。
我可以在 15 分钟内使用 fork()、exec() 和 dup2() 用 C 语言编写这个程序,所以我一定缺少一些东西!任何帮助表示赞赏。
【问题讨论】:
-
你有没有看到这个问题:stackoverflow.com/questions/48506460/…?
-
尝试在调试模式下使用 ayncio 运行,可能会有用
-
@Sanyash 我现在有,但我真的不明白这有什么帮助。我知道如何使用
asyncio编写 TCP 服务器。我不知道如何让客户与子流程对话。 -
您可能想尝试类似于这个简单的tcp proxy 的更高级别的方法,将客户端处理程序中的
asyncio.open_connection替换为asyncio.create_subprocess_exec
标签: python python-3.x python-asyncio