簡體   English   中英

Python - 如何在應用程序具有偵聽模式的TCP端口時如何重新啟動應用程序?

[英]Python - how to relaunch the application on the fly while the application having a TCP port in listening mode?

重新啟動應用程序運行偵聽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沒有重新啟動,因為殺戮是通過BASH完成的。

在綁定之前,您必須找到在套接字上設置SO_REUSEADDR的Python等效項。 確保套接字在退出時按照其他答案中的建議關閉既不必要也不充分,因為(a)當進程退出時,操作系統將關閉套接字,並且(b)您仍然必須克服TIME_WAIT狀態下的已接受連接,只有SO_REUSEADDR才能做到。

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。

您的Python程序是否有可能產生其他進程? 例如通過fork,subprocess或os.system?

您的偵聽文件描述符可能由生成的進程繼承:

os.system(“sleep 1000”)#without socket:

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

插座(); setsockopt的(); 綁定(); 聽(); os.system(“sleep 1000”)#with socket:

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進程無法綁定到同一個地址。

這是我的猜測:kill是異步的。 它只是告訴內核向進程發送信號,它也不會等待信號被傳遞和處理。 在重新啟動進程之前,您應該使用'wait'命令。

$ wait $PID

您可以向啟動腳本添加更多邏輯,以執行預執行測試和清理。

#!/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/作為示例,但如果您的進程作為非特權用戶運行,將略微更改實現,但總體概念是相同的。

可能的解決方案#1:從舊的python腳本中解析並執行新的python腳本。 它將繼承偵聽套接字。 然后,如果需要,將其與父項分離並終止(或退出)父項。 請注意,即使子(新版本)處理任何新的傳入請求,父(舊版本)也可以完成對任何現有請求的服務。

可能的解決方案#2:使用sendmsg()SCM_RIGHTS信號通知舊的運行腳本將套接字移交給新腳本, 然后 SCM_RIGHTS舊腳本。 此示例代碼討論“文件描述符”,但也適用於套接字。 請參閱: 如何以最短的停機時間切換TCP偵聽套接字?

可能的解決方案#3:如果bind()返回EADDRINUSE,請等待一會兒,然后重試直到成功。 如果你需要快速重啟腳本並且沒有停機時間,這當然是行不通的:)

可能的解決方案#4:不要使用kill -9殺死你的進程。 用其他信號代替它,例如SIGTERM 當你得到它時,捕獲SIGTERM並調用gobject.MainLoop.quit()

可能的解決方案#5:確保python腳本的父進程(例如shell) wait它。 如果腳本的父進程沒有運行,或者腳本被守護,那么如果使用SIGKILL終止,init將成為其父進程。 init調用會定期wait但可能需要一些時間,這可能就是您遇到的問題。 如果你必須使用SIGKILL但你想要更快的清理,只需自己打電話wait

解決方案4和5在停止舊腳本和啟動新腳本之間有一些非常短但非零的時間。 解決方案3可能介於兩者之間,但非常簡單。 解決方案1和2是實現此目的的方法,幾乎沒有停機時間 :任何連接調用都將成功並獲得舊的或新的運行腳本。

PS有關SO_REUSEADDR在不同平台上的行為的更多詳細信息: SO_REUSEADDR在Windows上的語義與Unix上的語義不同

但是,在Windows上,該選項實際上意味着完全不同的東西。 這意味着該地址應該從當前正在使用它的任何進程中被盜。

我不確定這是否是你遇到的,但請注意,如上所述,不同版本的Unix上的行為也有所不同。

我嘗試過什么都行不通。 所以為了降低風險我開始使用文件系統作為套接字示例:

# 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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM