python - 如何将接受的套接字从父进程传递到其子进程?
问题描述
注意: 根据下面的答案,我认为我没有正确传达这个问题。我目前正在用代码重写它以更清晰。
我正在编写一个 python 服务器,它接受多个客户端的连接并存储它们。
如果我打印用于与连接的客户端之一对话的正确连接的套接字,我会得到如下输出:
<socket.socket fd=4, family=AddressFamily.AF_INET, type=2049, proto=0, laddr=('3.3.3.3', 1234), raddr=('4.4.4.4', 63402)>
出于隐私考虑,我将服务器的 IP 替换为 3.3.3.3,将客户端的 IP 替换为 4.4.4.4。我真正希望能起作用的是将信息保存到以下格式的文件中:
4 2049
然后当子进程启动时,它将使用以下方法将此信息传递给套接字构造函数:
recovered_client = socket(AF_INET, 2049, 0, 4)
但这不起作用。当我应用此过程并打印恢复的客户端时,我看到以下内容:
<socket.socket fd=4, family=AddressFamily.AF_INET, type=2049, proto=0>
似乎无法通过将文件描述符传递给构造函数来恢复原始连接中的字段laddr和raddr 。
我尝试通过将 laddr 和 raddr 中的主机和端口也添加到文件中来手动修复此问题,然后使用以下命令进行连接:
recovered_client.connect(('4.4.4.4', 63402))
但这会产生错误:
OSError: [Errno 88] Socket operation on non-socket
作为一个实验,我在父进程中保持连接打开,然后让子进程接受一个新的连接并打印它,我得到的是:
<socket.socket fd=4, family=AddressFamily.AF_INET, type=2049, proto=0, laddr=('3.3.3.3', 1234), raddr=('75.159.78.189', 49709)>
换句话说,已经建立了一个新的连接,其fd的值相同,但客户端端口不同。原始连接从未关闭,而是无限期挂起,因为正如预期的那样,父进程在调用子进程时冻结了。
所以这意味着我有两个不同的活动连接(尽管一个被冻结),它们的套接字具有相同的文件描述符。这是否意味着分配给套接字的字段fd的值与创建它的进程相关?
如果是这样,我的方法显然是没有希望的。如何将在父进程中创建的客户端连接传递给其子进程?
解决方案
如果是这样,我的方法显然是没有希望的。如何将在父进程中创建的客户端连接传递给其子进程?
子代从其父代继承所有打开的文件描述符。没有必要“通过”任何东西。考虑以下代码:
#!/usr/bin/python
import os
import socket
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('localhost', 2049))
s.listen(5)
def child_process(fd, addr):
while True:
data = fd.recv(10)
if len(data) == 0:
break
print('read:', data)
print('client {} has disconnected'.format(addr))
def main():
while True:
c_fd, c_addr = s.accept()
print('new connection from', c_addr)
pid = os.fork()
if pid > 0:
# This is the parent process
c_fd.close()
else:
# This is the child process
child_process(c_fd, c_addr)
return
try:
main()
finally:
s.close()
每个新连接都由一个子进程处理。在父级中打开的文件描述符(例如accept
调用返回的客户端套接字)在客户端中已经可用。我们只需要确保关闭父级中的客户端套接字,因为它已经被子级继承了。
如果您使用该subprocess
模块生成子进程,则情况大致相同,因为只是subprocess
在后台调用。这就是为什么我说“子进程”和“子进程”是同义词的原因。fork()
exec()
不过,有一个问题。其中两个,实际上:
默认情况下,
subprocess
将在生成子进程之前关闭所有打开的文件描述符。幸运的是,有一个close_fds
关键字参数可以禁用该行为。不幸的是,即使我们禁用 中的
close_fds
行为,返回的subprocess
文件描述符也会设置标志,这意味着当进程调用.accept
CLOSE_ON_EXEC
exec
但不用担心,我们可以通过CLOSE_ON_EXEC
像这样清除标志来解决这个问题:
c_fd, c_addr = s.accept()
flags = fcntl.fcntl(c_fd, fcntl.F_GETFD, 0)
fcntl.fcntl(c_fd, fcntl.F_SETFD, flags & ~fcntl.FD_CLOEXEC)
之后,套接字将由使用subprocess.call
和朋友生成的进程继承。例如,如果我们像这样重写我们的父代码:
#!/usr/bin/python
import fcntl
import socket
import subprocess
s = socket.socket(socket.AF_INET,
socket.SOCK_STREAM|socket.SOCK_CLOEXEC)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('localhost', 2049))
s.listen(5)
def main():
while True:
c_fd, c_addr = s.accept()
flags = fcntl.fcntl(c_fd, fcntl.F_GETFD, 0)
fcntl.fcntl(c_fd, fcntl.F_SETFD, flags & ~fcntl.FD_CLOEXEC)
print('new connection from', c_addr)
# Here we call the child command, passing the
# integer file descriptor as the first argument.
subprocess.check_call(['python', 'socketchild.py',
'{}'.format(c_fd.fileno()), c_addr[0]],
close_fds=False)
c_fd.close()
try:
main()
finally:
s.close()
然后我们可以编写子代码,使用该socket.fromfd
方法将该整数文件描述符转换回套接字:
#!/usr/bin/python
import socket
import sys
def child_process(fd, addr):
while True:
data = fd.recv(10)
if len(data) == 0:
break
print('read:', data)
print('client {} has disconnected'.format(addr))
def main():
fdno = int(sys.argv[1])
print('got fd:', fdno)
addr = sys.argv[2]
fd = socket.fromfd(fdno, socket.AF_INET, socket.SOCK_STREAM)
child_process(fd, addr)
if __name__ == '__main__':
main()
推荐阅读
- pandas - python数据框中日期转换中的断言错误
- html - html 按钮未嵌入到父 div 中
- php - 为什么我不能绑定 Elasticsearch 和 Laravel Scout?
- python - 如果已创建 dask.distributed 客户端,xarray.open_mfdataset() 将不起作用
- python - 西皮 | 统计:迭代变量选择(根据 AdjR2 添加或删除特征):回归
- ruby-on-rails - Ruby / Rails:wkhtmltopdf仅在等待PDF写入完成时才挂起
- visual-studio-code - 如何瞬间执行代码片段 - vscode
- salesforce - 通过 Workato 使用 SOAP Web 服务时,未填充 NetSuite Invoice 上的 createdFrom 字段
- python - ElementNotInteractableException:元素在 Selenium 中不可交互
- amazon-s3 - 如何将gz文件从s3下载到api网关