【问题标题】:Python - how to relaunch the application on the fly while the application having a TCP port in listening mode?Python - 如何在应用程序在侦听模式下具有 TCP 端口时即时重新启动应用程序?
【发布时间】: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 监控有关...
  • 你能把套接字的文件描述符传递给另一个新进程吗?

标签: python linux network-programming python-2.7


【解决方案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)
            

            【讨论】:

              猜你喜欢
              • 2019-01-21
              • 1970-01-01
              • 1970-01-01
              • 2023-02-01
              • 2012-10-31
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多