附加到Python子进程的套接字未正确释放

2024-05-15 22:44:42 发布

您现在位置:Python中文网/ 问答频道 /正文

我在写library for testing of HTTP services。在我的测试中(我使用的是python3.4),我使用http服务器例如HTTP服务。它在使用同一个Python解释器创建的单独进程中运行,有点像这样:

subprocess.Popen([sys.executable, '-m', 'http.server', '9090'])

问题

在http服务器进程被终止,它的端口无法从父Python进程中使用(似乎仍在使用)。为什么?在

更多详情

创建一个将使用与上一个相同端口的新进程没有问题,但是我无法从父进程开始监听它。端口也没有显示为可供port_for。在

如果以相同的方式生成非Python进程(即Mountebank),则会正确释放端口。在

如果我不打电话http服务器在启动之后,端口仍然可用。在

那又怎样?Linux是否会将Python子进程获取的资源(socket)附加到父进程,因为它是从同一个可执行文件创建的?还是Python在做什么?在

编辑1

您的测试缺少一件事,这实际上是我的场景:从父进程获取套接字。我已经更新了您的测试(如下),它显示在服务器停止后,套接字仍然不可用。在

^{pr2}$

我检查了python2和python3。如果我使用os.杀死或波本(如预期)。关于我的代码,您可以在this line之前检查端口可用性。我必须警告,我的代码只适用于python3。在

编辑2

尝试用sou-REUSEADDR-like-Foon-said连接到套接字实际上是可行的。但它不能回答我最初的问题。在

我仍然不知道为什么Python子进程所使用的端口仍将用于父进程,除非使用REUSEADDR。我还检查了从不同于运行主进程(master-Python3,slave-Python2)的解释器生成简单的HTTP服务器,并使用不同的程序(一个简单的瓶子服务)。在

当我生成Mountebank(NodeJS)或NetCat子进程时,即使不使用REUSEADDR,它们的端口在清理后仍然可用。在

在读了this post之后,我有了一个想法,那可能与http服务器在0.0.0.0上运行,Mountebank只在本地主机上运行,但这没有影响。在

所以问题依然存在。Python中有bug吗?或者是不同的口译员之间有什么奇怪的联系?或者我选择了处理清理非常好的非Python进程,甚至在一些非Python程序中也可以观察到相同的行为吗?在


Tags: 端口代码程序服务器http编辑for进程
1条回答
网友
1楼 · 发布于 2024-05-15 22:44:42

所以Linux在您终止进程后,默认情况下会保持TCP套接字打开一段时间。请注意,有些过程可能比其他过程清理得更好。你有几个选择:

  1. 退出http服务器处理更干净
  2. 设置“允许重用”地址。我不知道如何使用python一行程序来实现这一点,但是如果您有以下类似的东西,您就不会看到您的问题:

    def run(server_class=http.HTTPServer, handler_class=http.BaseHTTPRequestHandler):
    
      server_address = ('', 8000)
      httpd = server_class(server_address, handler_class)
      httpd.allow_reuse_address = True #killed processes won't linger tcp sockets
      httpd.serve_forever()
    

    run()

编辑:我不能重现你的错误。我试着给你举个例子来说明。在

^{pr2}$

(这将通过,并且子进程是否调用[nb,说明我目前无法轻松访问python3,因此交换掉了http服务器对于python2等效的SimpleHTTPServer],等待一秒钟以确保准备就绪,然后使用请求发出http请求(因为部分问题往往只存在于存在TCP连接的情况下),然后我杀死它(第一次使用SIGINT,又名control-c,第二次使用SIGTERM(kill-15),第三次使用SIGKILL(kill-9))。我得到的输出是预期的http(在我运行的目录中以HTML的形式列出文件),然后是执行相当于键盘中断的错误消息,然后是另外两个http列表(执行kill-15或kill-9不会给它打印stacktrace的机会)。在

编辑3:

我会尝试这段代码,在那里你试图让主要部分抓住它(除非你还需要避免做低级的socket,在这种情况下,你可能只是运气不好)(关键的变化是A)我添加了一个调用,将socketopt设置为reuseaddr,B)我在循环结束时调用了s.close()

测试插座是否空闲

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('localhost', 9090))
    print('socket is FREE')
    s.close()
except Exception as ex:
    print('socket is NOT FREE')
    print (ex)

相关问题 更多 >