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_REUSEADDRsock.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)waits 就可以了。如果脚本的父进程没有运行,或者脚本被守护,那么如果用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 接收器/侦听器?

如何将代码发送到 3ds Max 脚本侦听器

c#socket编程关于阻塞侦听的问题

如何通过 Python 脚本侦听和报告服务器 (SSH) 连接?

如何让两个多播套接字侦听具有相同端口的两个多播通道