提问人:doggo 提问时间:4/27/2020 最后编辑:doggo 更新时间:3/29/2021 访问量:1365
如何将接受的套接字从父进程传递到其子进程?
How can I pass an accepted socket from a parent process to its child process?
问:
注意:根据下面的答案,我认为我没有正确地传达这个问题。我目前正在用代码重写它,以便更清楚。
我正在编写一个 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)
除此之外,套接字将由 using 和 friends 生成的进程继承。例如,如果我们像这样重写我们的父代码: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()
评论
Python 代码片段
sock = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
套接字重用地址是这里要注意的关键点
然后,您可以使用
sock.file_no将其传递给客户端。
客户端可以使用
s_client = socket.socket(<filehandle from parent>, socket.AF_INET,
socket.SOCK_STREAM))
评论