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

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を2つのレイヤーを持つ1つのアプリケーションとして一緒に使用していることに注意してください。つまり、runme.shは、両方のアプリを同時に起動するためのスタートアップスクリプトです。JavaからPythonの再起動ボタンを押します。ただし、強制終了はBASHを介して行われるため、Pythonは再起動しません。

4

7 に答える 7

3

SO_REUSEADDRバインドする前に、ソケットの設定に相当する Python を見つける必要があります。TIME_WAIT他の回答で推奨されているように、終了時にソケットが閉じられていることを確認することは、(a) プロセスの終了時に OS によってソケットが閉じられ、(b)状態で受け入れられた接続を克服する必要があるため、必要でも十分でもありませんSO_REUSEADDRできる。

于 2012-12-10T08:42:24.083 に答える
2

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 を開始します。

于 2012-12-19T14:38:47.600 に答える
2

Python プログラムが他のプロセスを生成する可能性はありますか? たとえば、フォーク、サブプロセス、または 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

ソケット(); setsockopt(); 練る(); 聞く(); 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 スクリプトの新しいコピーを古いものからフォークして実行します。リスニング ソケットを継承します。次に、必要に応じて、親から切り離し、親を強制終了 (または終了) します。親 (古いバージョン) は、子 (新しいバージョン) が新しい着信要求を処理している場合でも、既存の要求の処理を終了できることに注意してください。

考えられる解決策 #2: 古い実行中のスクリプトにシグナルを送り、ソケットを新しいスクリプトに渡すようにandsendmsg()を使用してからSCM_RIGHTS、古いスクリプトを強制終了します。 このサンプル コードは「ファイル記述子」について説明していますが、ソケットでも問題なく動作します。参照:最小限のダウンタイムで TCP リッスン ソケットをハンドオーバーする方法は?

考えられる解決策 #3: bind()EADDRINUSE が返された場合は、しばらく待ってから、成功するまで再試行します。スクリプトをすばやく再起動する必要があり、その間にダウンタイムがない場合、もちろんこれは機能しません:)

考えられる解決策 #4: kill -9 でプロセスを強制終了しないでください。代わりに、たとえば などの他のシグナルでそれを殺しますSIGTERM。あなたがそれを取得したら、キャッチSIGTERMして呼び出しgobject.MainLoop.quit()ます。

考えられる解決策 #5: Python スクリプトの親プロセス (シェルなど)がそのwait上にあることを確認します。スクリプトの親プロセスが実行されていない場合、またはスクリプトがデーモン化されている場合、 で強制終了するとSIGKILL、init がその親になります。init はwait定期的に呼び出しますが、少し時間がかかる場合があります。これはおそらくあなたが遭遇しているものです。使用する必要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' コマンドを使用する必要があります。

$ 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 に答える