Python - 如何在应用程序在侦听模式下具有 TCP 端口时即时重新启动应用程序?
Posted
技术标签:
【中文标题】Python - 如何在应用程序在侦听模式下具有 TCP 端口时即时重新启动应用程序?【英文标题】:Python - how to relaunch the application on the fly while the application having a TCP port in listening mode? 【发布时间】:2012-11-27 13:56:04 【问题描述】:重新启动运行侦听 TCP 端口的应用程序的最佳方法是什么? 问题是:如果我在重新启动时快速启动应用程序,它会失败,因为正在侦听的套接字已在使用中。
这种情况下如何安全重启?
socket.error: [Errno 98] Address already in use
代码:
#!/usr/bin/python
import sys,os
import pygtk, gtk, gobject
import socket, datetime, threading
import ConfigParser
import urllib2
import subprocess
def server(host, port):
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
sock.listen(1)
print "Listening... "
gobject.io_add_watch(sock, gobject.IO_IN, listener)
def listener(sock, *args):
conn, addr = sock.accept()
print "Connected"
gobject.io_add_watch(conn, gobject.IO_IN, handler)
return True
def handler(conn, *args):
line = conn.recv(4096)
if not len(line):
print "Connection closed."
return False
else:
print line
if line.startswith("unittest"):
subprocess.call("/var/tmp/runme.sh", shell=True)
else:
print "not ok"
return True
server('localhost', 8080)
gobject.MainLoop().run()
runme.sh
#!/bin/bash
ps aux | grep py.py | awk 'print $2' | xargs kill -9;
export DISPLAY=:0.0 && lsof -i tcp:58888 | grep LISTEN | awk 'print $2' | xargs kill -9;
export DISPLAY=:0.0 && java -cp Something.jar System.V &
export DISPLAY=:0.0 && /var/tmp/py.py &
编辑: 请注意,我将 Java 和 Python 作为一个具有两层的应用程序一起使用。所以 runme.sh 是我同时启动两个应用程序的启动脚本。在 Java 中,我按下 Python 重新启动按钮。但是 Python 不会重新启动,因为 kill 是通过 BASH 完成的。
【问题讨论】:
那么你知道为什么你的代码没有设置SO_REUSEADDR
吗?
@MatthewAdams:还没有。还是失败了。
我现在已经查看了大量关于同一问题的其他问题,似乎 EJP 关于SO_REUSEADDR
是完全正确的。我仍然不明白为什么您的代码无法立即重新连接,因为看起来您正在设置 SO_REUSEADDR
...
我认为它必须与gobject
的 io 监控有关...
你能把套接字的文件描述符传递给另一个新进程吗?
【参考方案1】:
在绑定之前,您必须找到在套接字上设置 SO_REUSEADDR
的 Python 等效项。确保套接字按照其他答案中的建议在退出时关闭既不必要也不充分,因为(a)当进程退出时,套接字会被操作系统关闭,并且(b)您仍然必须克服TIME_WAIT
状态下接受的连接,只有SO_REUSEADDR
可以做到。
【讨论】:
你肯定想关闭套接字吧?这在过去一直为我解决了这个问题,这似乎是一种很好的做法...... @MatthewAdams 当进程退出时,套接字被操作系统关闭。一定要在你的正常代码中关闭它,但没有必要去你的答案中概述的英雄长度。 (+1) 啊。虽然公平地说,“英雄长度”只是三行代码加上使用不同的kill
标志......
虽然看着the docs(一直滚动到底部),但似乎@YumYumYum 正在用这条线设置SO_REUSEADDR
:sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
【参考方案2】:
这是我的猜测:kill 是异步的。它只是告诉内核向进程发送信号,它也不会等待信号被传递和处理。在重新启动进程之前,您应该使用“等待”命令。
$ wait $PID
【讨论】:
【参考方案3】:可能的解决方案 #1:从旧版本中分叉并执行新的 Python 脚本副本。它将继承侦听套接字。然后,如果需要,将其与父级分离并杀死(或退出)父级。请注意,即使子(新版本)处理任何新的传入请求,父(旧版本)也可以完成对任何现有请求的服务。
可能的解决方案#2:使用sendmsg()
和SCM_RIGHTS
向旧运行脚本发出信号,将套接字移交给新脚本,然后杀死旧脚本。 This sample code 谈论“文件描述符”,但也适用于套接字。见:How to hand-over a TCP listening socket with minimal downtime?
可能的解决方案#3:如果bind()
返回 EADDRINUSE,请稍等片刻,然后重试直到成功。如果您需要快速重新启动脚本并且中间没有停机时间,那么这当然行不通:)
可能的解决方案 #4:不要使用 kill -9 终止您的进程。用其他信号杀死它,例如SIGTERM
。抓住SIGTERM
并在收到时致电gobject.MainLoop.quit()
。
可能的解决方案#5:确保你的python脚本的父进程(例如shell)wait
s 就可以了。如果脚本的父进程没有运行,或者脚本被守护,那么如果用SIGKILL
杀死,init 将成为它的父进程。 init 会定期调用wait
,但这可能需要一些时间,这可能就是您遇到的问题。如果您必须使用SIGKILL
,但您想要更快的清理速度,请自己致电wait
。
解决方案 4 和 5 在停止旧脚本和启动新脚本之间有一些非常短但非零的时间。解决方案 3 之间可能有相当长的时间,但非常简单。解决方案 1 和 2 是真正没有停机时间的方法:任何连接调用都会成功并获得旧的或新的运行脚本。
附:更多关于SO_REUSEADDR
在不同平台上的行为细节:SO_REUSEADDR doesn't have the same semantics on Windows as on Unix
然而,在 Windows 上,该选项实际上意味着相当多的东西 不同的。这意味着该地址应该从任何 目前恰好正在使用它的进程。
我不确定这是否是您遇到的问题,但请注意,如上所述,不同版本的 Unix 上的行为也有所不同。
【讨论】:
【参考方案4】:1.
你在杀死你的 python 时遇到了问题
air:~ dima$ ps aux | grep i-dont-exist.py | awk 'print $2'
34198
这意味着您的 grep
进程被您的重启逻辑所困并杀死。
在 linux 上你可以使用 pidof 代替。
或者使用 start-stop-daemon 和 pid 文件。
2.
你已经重用了地址,所以我猜你的 python 死得不够快。
为了快速测试,在再次启动 python 之前添加一个睡眠。
如果这有帮助,请在 kill 命令后添加一个 sleep-wait 循环,并且只有在您确定旧的 python 不再运行时才启动新的 python。
【讨论】:
【参考方案5】:您的 Python 程序是否有可能产生其他进程?例如通过 fork、subprocess 还是 os.system?
你的监听文件描述符有可能被衍生进程继承:
os.system("sleep 1000") # 没有套接字:
ls -l /proc/`pidof sleep`/fd
total 0
lrwx------ 1 user user 64 2012-12-19 19:52 0 -> /dev/pts/0
lrwx------ 1 user user 64 2012-12-19 19:52 1 -> /dev/pts/0
l-wx------ 1 user user 64 2012-12-19 19:52 13 -> /dev/null
lrwx------ 1 user user 64 2012-12-19 19:52 2 -> /dev/pts/0
socket();套索选择();绑定();听(); os.system("sleep 1000") # 带套接字:
ls -l /proc/`pidof sleep`/fd
total 0
lrwx------ 1 user user 64 2012-12-19 19:49 0 -> /dev/pts/0
lrwx------ 1 user user 64 2012-12-19 19:49 1 -> /dev/pts/0
l-wx------ 1 user user 64 2012-12-19 19:49 13 -> /dev/null
lrwx------ 1 user user 64 2012-12-19 19:49 2 -> /dev/pts/0
lrwx------ 1 user user 64 2012-12-19 19:49 5 -> socket:[238967]
lrwx------ 1 user user 64 2012-12-19 19:49 6 -> socket:[238969]
也许你的 Python 脚本死了,但它的子脚本没有死,后者继续引用监听套接字,因此新的 Python 进程无法绑定到相同的地址。
【讨论】:
【参考方案6】:您可以在启动脚本中添加更多逻辑来进行执行前测试和清理。
#!/bin/bash
export DISPLAY=:0.0
# If py.py is found running
if pgrep py.py; then
for n in $(seq 1 9); do
# kill py.py starting at kill -1 and increase to kill -9
if ! pgrep py.py; then
# if no running py.py is found break out of this loop
break
fi
pkill -$n py.py
sleep .5
done
fi
# Verify nothing has tcp/58888 open in a listening state
if lsof -t -i tcp:58888 -stcp:listen; then
echo process with pid $(lsof -t -i tcp:58888 -stcp:listen) still listening on port 58888, exiting
exit
fi
java -cp Something.jar System.V &
/var/tmp/py.py &
最终,您可能希望使用完整的初始化脚本并将这些进程进行守护进程。有关示例,请参阅 http://www.thegeekstuff.com/2012/03/lsbinit-script/,尽管如果您的进程作为非授权用户运行,这将稍微改变实现,但总体概念是相同的。
【讨论】:
【参考方案7】:我尝试过的任何方法都不起作用。所以为了降低风险,我开始使用文件系统作为套接字示例:
# Echo server program
import socket,os
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
os.remove("/tmp/socketname")
except OSError:
pass
s.bind("/tmp/socketname")
s.listen(1)
conn, addr = s.accept()
while 1:
data = conn.recv(1024)
if not data: break
conn.send(data)
conn.close()
# Echo client program
import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect("/tmp/socketname")
s.send('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)
【讨论】:
以上是关于Python - 如何在应用程序在侦听模式下具有 TCP 端口时即时重新启动应用程序?的主要内容,如果未能解决你的问题,请参考以下文章
如何在具有 Animator 的 GameObject 之外添加 AnimationEvent 接收器/侦听器?