Python 多进程与套接字未关闭

4 投票
2 回答
3192 浏览
提问于 2025-04-16 08:51

我在使用Python的多进程和套接字时遇到了一些奇怪的情况。下面的代码和我正在处理的代码类似(我简化了很多,尽量让它看起来简单)。

这段代码创建了三个进程:一个进程什么都不做,另一个进程启动第三个进程,这个进程会在一个套接字上监听。如果我结束了“监听”进程,套接字仍然保持打开状态(我可以通过netstat看到它)。

  • 如果我移除(或停止)“什么都不做”的进程,这样就没问题了。
  • 如果我把所有东西都换成线程(threading.Thread),这样也没问题,但
  • 如果我把“什么都不做”保持为进程,而把服务器和启动器换成线程,问题依然存在。

有没有人能告诉我为什么套接字仍然是打开的?在处理多进程和套接字时有没有什么问题?

我使用的是运行在Linux上的Python 2.6.6。

非常感谢,
阿尔瓦罗。

import time
from multiprocessing import Process, Event

import socket

class Server(Process):
    def __init__(self, port):
        super(Server, self).__init__()

        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.bind(("127.0.0.1",port))
        self.s.listen(1)
        self.s.settimeout(10)
        self.is_stop = Event()
        self.is_stop.clear()

    def run(self):
        while not self.is_stop.is_set():
            print "Server: running (pid %s)" % self.pid 
            time.sleep(1)
        print "Server: exiting"

    def stop(self):
        self.is_stop.set()
        self.s.close()

class Launcher(Process):
    def __init__(self):
        super(Launcher, self).__init__()
        self.srv = Server(9999)
        self.srv.start()

    def run(self):
        print "Launcher pid %s" % self.pid
        while True:
            time.sleep(1)

    def stop(self):
        print "Launcher: I'm stopping the server"
        self.srv.stop()
        self.srv.terminate()
        self.srv.join()
        print "Launcher: server stopped"

class DoNothing(Process):
    def __init__(self):                
        super(DoNothing, self).__init__()

    def run(self):
        while True:
            time.sleep(1)


l = Launcher()
l.start()

dn = DoNothing()
dn.start()

time.sleep(2)

print " Stop launcher "
l.stop()

while True:
    time.sleep(1)

编辑:

相关的 netstat -lnp 输出:

tcp        0      0 127.0.0.1:9999          0.0.0.0:*               LISTEN      7183/python

我注意到在netstat中显示的进程ID(pid)从父进程(当服务器进程运行时)变成了启动器的进程ID(当服务器停止时)。

2 个回答

1

我对 multiprocessing 这个包不是很精通,但看起来 Process 的构造函数是在父进程的环境下被调用的(也就是在进行 分叉之前)。如果真是这样,那就意味着在你的进程层级中,最上面的那个进程负责进行 bind 操作。子进程可能会继承这个套接字。

如果你把所有与 self.s 相关的内容(比如 bind 等)都放到 Server.run 里面,会发生什么呢?

3

要解决当前的问题(也就是套接字没有正常关闭),在 Server.stop 方法里加上 self.s.shutdown(socket.SHUT_RDWR) 这一行代码:

def stop(self):
    self.is_stop.set()
    self.s.shutdown(socket.SHUT_RDWR) 
    self.s.close()

撰写回答