10

重新启动运行侦听 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()

运行me.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 完成的。

4

7 回答 7

3

SO_REUSEADDR在绑定之前,您必须在套接字上找到与设置等效的 Python 。确保套接字按照其他答案中的建议在退出时关闭既不必要也不充分,因为(a)套接字在进程退出时被操作系统关闭,并且(b)您仍然必须克服该TIME_WAIT状态下接受的连接,这仅SO_REUSEADDR可以做。

于 2012-12-10T08:42:24.083 回答
2

1.

You have a problem killing your python

air:~ dima$ ps aux | grep i-dont-exist.py | awk '{print $2}'
34198

Which means that your grep process gets caught up in and killed by your restart logic.

On linux you could use pidof instead.

Alternatively use start-stop-daemon and pid file.

2.

You already reuse address, so my guess is your python doesn't die fast enough.

For a quick test, add a sleep before you start python again.

If this helps, add a sleep-wait loop after kill command and only start new python when you are sure old python is not running anymore.

于 2012-12-19T14:38:47.600 回答
2

您的 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

插座(); 套索选择();绑定();听(); 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 进程无法绑定到相同的地址。

于 2012-12-19T18:56:58.567 回答
1

可能的解决方案#1:从旧版本中分叉并执行您的 python 脚本的新副本。它将继承侦听套接字。然后,如果需要,将其与父级分离并杀死(或退出)父级。请注意,即使子级(新版本)处理任何新的传入请求,父级(旧版本)也可以完成对任何现有请求的服务。

sendmsg()可能的解决方案#2:用and向旧运行脚本发出信号以将套接字移交给新脚本SCM_RIGHTS然后终止旧脚本。 此示例代码讨论了“文件描述符”,但也适用于套接字。请参阅:如何以最少的停机时间移交 TCP 侦听套接字?

可能的解决方案#3:如果bind()返回 EADDRINUSE,请稍等片刻,然后重试,直到成功。如果您需要快速重新启动脚本并且中间没有停机时间,那么这当然行不通:)

可能的解决方案#4:不要用 kill -9 杀死你的进程。用其他信号杀死它,例如SIGTERM. 当你得到它时,抓住SIGTERM并打电话。gobject.MainLoop.quit()

可能的解决方案#5:确保你的python脚本的父进程(例如shell)wait在它上面。如果脚本的父进程没有运行,或者脚本被守护进程,那么如果用 杀死SIGKILL,init 将成为它的父进程。initwait会定期调用,但可能需要一些时间,这可能就是您遇到的问题。如果您必须使用SIGKILL但您想要更快的清理速度,请致电您wait自己。

解决方案 4 和 5 在停止旧脚本和启动新脚本之间有一些非常短但非零的时间。解决方案 3 之间可能有相当长的时间,但非常简单。解决方案 1 和 2 是在没有停机时间的情况下执行此操作的方法:任何连接调用都将成功并获得旧的或新的运行脚本。

SO_REUSEADDRPS关于不同平台行为的更多细节: SO_REUSEADDR 在 Windows 上与 Unix 上的语义不同

然而,在 Windows 上,该选项实际上意味着完全不同的东西。这意味着该地址应该从目前恰好正在使用它的任何进程中窃取。

我不确定这是否是您遇到的问题,但请注意,如那里所述,不同版本的 Unix 上的行为也有所不同。

于 2012-12-18T12:23:27.977 回答
1

您可以在启动脚本中添加更多逻辑来进行预执行测试和清理。

#!/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 &

最终,您可能希望使用完整的 init 脚本并将这些进程守护进程。有关示例,请参阅http://www.thegeekstuff.com/2012/03/lsbinit-script/ ,尽管如果您的进程作为非特权用户运行,这将稍微改变实现,但总体概念是相同的。

于 2012-12-21T12:14:47.757 回答
1

这是我的猜测:kill 是异步的。它只是告诉内核向进程发送信号,它也不会等待信号被传递和处理。在重新启动该过程之前,您应该使用“等待”命令。

$ wait $PID
于 2012-12-16T19:02:20.423 回答
0

我尝试过的任何东西都不起作用。所以为了降低风险,我开始使用文件系统作为套接字示例:

# 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)
于 2013-01-19T19:54:58.793 回答