Python:バインディングソケット:「アドレスはすでに使用されています」


83

TCP / IPネットワークのクライアントソケットについて質問があります。私が使っているとしましょう

try:

    comSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    comSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

except socket.error, msg:

    sys.stderr.write("[ERROR] %s\n" % msg[1])
    sys.exit(1)

try:
    comSocket.bind(('', 5555))

    comSocket.connect()

except socket.error, msg:

    sys.stderr.write("[ERROR] %s\n" % msg[1])

    sys.exit(2)

作成されたソケットはポート5555にバインドされます。問題は、接続を終了した後です。

comSocket.shutdown(1)
comSocket.close()

Wiresharkを使用すると、両側からFIN、ACK、およびACKでソケットが閉じているのがわかります。ポートを再び使用することはできません。次のエラーが発生します。

[ERROR] Address already in use

次回も同じポートを使用できるように、ポートをすぐにクリアするにはどうすればよいでしょうか。

comSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

setsockoptは問題を解決できないようですありがとうございます!


1
クライアントが特定のポートを必要とするのはなぜですか?
AJ。

2
これを本番サーバーに配置する必要があるため、そのサーバーでは、すべての発信接続がブロックされます。ソケットに特定のポートを指定して、ファイアウォールに接続の通過を許可するルールを設定できるようにする必要があります。
Tu Hoang 2011年

3
ネットワーク管理者は、アウトバウンドトラフィックが宛先ポートによって制御できることを理解する必要があります。
AJ。

7
これには十分な情報があります。問題の原因はTIME_WAITソケットの状態である可能性が99%あります。以下の回答には、次の解決策があります:)
lunixbochs 2011年

1
あなたのオペレーティングシステムは何ですか?通常、netstatを使用してソケットの状態を確認できます(ソケットを識別するためにポート番号を探します)
lunixbochs 2011年

回答:


124

SO_REUSEADDRソケットをバインドする前に、ソケットオプションを使用してみてください。

comSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

編集: あなたはまだこれに問題を抱えているようです。SO_REUSEADDR動作しない場合があります。ソケットをバインドして同じ宛先に(SO_REUSEADDR有効にして)再接続しようとしてTIME_WAITも、引き続き有効です。ただし、別のhost:portに接続することはできます。

いくつかの解決策が思い浮かびます。再度接続できるようになるまで、再試行を続けることができます。または、クライアントが(サーバーではなく)ソケットのクローズを開始した場合、それは魔法のように機能するはずです。


1
それでも再利用することはできません。同じポートを再利用できるようになるまで待たなければならない時間は1分30秒です:(
Tu Hoang

5
setsockopt以前に電話しましたbindか?最初のソケットはで作成されましたSO_REUSEADDRか、それとも失敗したソケットでしたか?これが機能するには、待機中のソケットがSO_REUSEADDR設定されている必要があります
lunixbochs 2011年

はい、comSocket.setsockopt(socket.SOL_SOCKET、socket.SO_REUSEADDR、1)を含めましたが、それでも機能しません。
Tu Hoang

1
tcp 0 0 98c5-9-134-71-1:freeciv mobile-166-132-02:2345 TIME_WAIT
Tu Hoang

1
@jcoffland私は彼が使用すべきではないことに同意しますbind()が、SO_REUSEADDRは、TCPサーバーソケットだけでなく、TCPクライアントソケットとUDPソケットを含むすべてのソケット用です。ここに誤った情報を投稿しないでください。
user2074 2119

26

これは私がテストした完全なコードであり、「アドレスはすでに使用されています」というエラーは絶対に発生しません。これをファイルに保存して、提供するHTMLファイルのベースディレクトリ内からファイルを実行できます。さらに、サーバーを起動する前にプログラムでディレクトリを変更できます

import socket
import SimpleHTTPServer
import SocketServer
# import os # uncomment if you want to change directories within the program

PORT = 8000

# Absolutely essential!  This ensures that socket resuse is setup BEFORE
# it is bound.  Will avoid the TIME_WAIT issue

class MyTCPServer(SocketServer.TCPServer):
    def server_bind(self):
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)

Handler = SimpleHTTPServer.SimpleHTTPRequestHandler

httpd = MyTCPServer(("", PORT), Handler)

# os.chdir("/My/Webpages/Live/here.html")

httpd.serve_forever()

# httpd.shutdown() # If you want to programmatically shut off the server

httpd.shutdown()の後でも、httpd.server_close()を呼び出して、すべてのリソースを完全に解放する必要があります。
アンドロビン2018年

また、予期しない存在がある場合は、atexitモジュールの使用を検討してください。
アンドロビン2018年


13

このリンクによると

実際、SO_REUSEADDRフラグは、はるかに大きな結果をもたらす可能性があります。SO_REUSADDRを使用すると、TIME_WAITでスタックしているポートを使用できますが、そのポートを使用して、最後に接続した場所への接続を確立することはできません。何?ローカルポート1010を選択し、foobar.comポート300に接続してからローカルで閉じ、そのポートをTIME_WAITのままにするとします。ローカルポート1010をすぐに再利用して、foobar.comポート300以外の場所に接続できます。

ただし、リモートエンドがクロージャ(クローズイベント)を開始するようにすることで、TIME_WAIT状態を完全に回避できます。したがって、サーバーはクライアントを最初に閉じることで問題を回避できます。アプリケーションプロトコルは、クライアントがいつ閉じるかを認識できるように設計する必要があります。サーバーは、クライアントからのEOFに応答して安全に閉じることができますが、クライアントがネットワークを正常に離れた場合に備えて、EOFを予期しているときにタイムアウトを設定する必要もあります。多くの場合、サーバーが閉じる前に数秒待つだけで十分です。

また、ネットワーキングとネットワークプログラミングについてさらに学ぶことをお勧めします。これで、少なくともtcpプロトコルがどのように機能するかを確認する必要があります。プロトコルは非常に簡単で小さいため、将来的には多くの時間を節約できる可能性があります。

ではnetstatコマンド簡単にプログラム((program_nameの、PID)タプル)を見ることができますどのポートとどのようなソケットの現在の状態であるにバインドされています。TIME_WAIT、CLOSING、FIN_WAITなどが。

Linuxネットワーク構成の本当に良い説明は/server/212093/how-to-reduce-number-of-sockets-in-time-waitで見つけることができます


また、コードには注意が必要です。コードがまだ開発中であり、いくつかの例外が発生した場合は、特にサーバー側から接続が適切に閉じられない可能性があります。
Rustem K 2014

8
公平を期して、トムのネットワークガイドからコピーして貼り付けた文章を引用する必要があります。答えを訂正してください。
HelloWorld

8

バインドする前に、allow_reuse_addressを設定する必要があります。SimpleHTTPServerの代わりに、次のスニペットを実行します。

Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
httpd = SocketServer.TCPServer(("", PORT), Handler, bind_and_activate=False)
httpd.allow_reuse_address = True
httpd.server_bind()
httpd.server_activate()
httpd.serve_forever()

これにより、フラグを設定する前にサーバーがバインドされなくなります。


TCPServerをサブクラス化し、属性をオーバーライドする方がはるかに簡単なようです。たとえば、私の答えを参照しください
Andrei

3

Felipe Cruzeが述べたように、バインドする前にSO_REUSEADDRを設定する必要があります。別のサイトで解決策を見つけました-他のサイトの解決策、以下に再現

問題は、アドレスがソケットにバインドされる前に、SO_REUSEADDRソケットオプションを設定する必要があることです。これは、次のようにThreadingTCPServerをサブクラス化し、server_bindメソッドをオーバーライドすることで実行できます。

SocketServer、socketをインポートします

クラスMyThreadingTCPServer(SocketServer.ThreadingTCPServer):
    def server_bind(self):
        self.socket.setsockopt(socket.SOL_SOCKET、socket.SO_REUSEADDR、1)
        self.socket.bind(self.server_address)


3

この例外の別の理由を見つけました。Spyder IDE(私の場合はRaspbian上のSpyder3)からアプリケーションを実行し、プログラムが^ Cまたは例外で終了したとき、ソケットはまだアクティブでした。

sudo netstat -ap | grep 31416
tcp  0  0 0.0.0.0:31416  0.0.0.0:*    LISTEN      13210/python3

プログラムを再度実行すると、「アドレスはすでに使用されています」が見つかりました。IDEは、前の「実行」で使用されたソケットを見つける別のプロセスとして、新しい「実行」を開始するようです。

socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

助けにはならなかった。

殺害プロセス13210が役に立ちました。次のようなコマンドラインからPythonスクリプトを開始する

python3 <app-name>.py

SO_REUSEADDRがtrueに設定されている場合、常に正常に機能しました。新しいThonnyIDEまたはIdle3IDEにはこの問題はありませんでした。



1

私にとってより良い解決策は次のとおりでした。接続を閉じるイニシアチブはサーバーによって行われたため、setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)効果はなく、TIME_WAITは同じポートでの新しい接続をエラーで回避していました。

[Errno 10048]: Address already in use. Only one usage of each socket address (protocol/IP address/port) is normally permitted 

私はついにソリューションを使用してOSにポート自体を選択させました。その後、前例がまだTIME_WAITにある場合は、別のポートが使用されます。

交換しました:

self._socket.bind((guest, port))

と:

self._socket.bind((guest, 0))

tcpアドレスのPythonソケットドキュメントに示されているように:

指定する場合、接続する前にソケットを送信元アドレスとしてバインドするには、source_addressを2タプル(ホスト、ポート)にする必要があります。ホストまたはポートがそれぞれ ''または0の場合、OSのデフォルトの動作が使用されます。


1
バインドを省略してもかまいませんが、OPはポート5555を使用する必要があると述べており、この回答はそうではありません。
user2074 2119

1

もちろん、開発環境での別の解決策は、それを使用してプロセスを強制終了することです。

def serve():
    server = HTTPServer(('', PORT_NUMBER), BaseHTTPRequestHandler)
    print 'Started httpserver on port ' , PORT_NUMBER
    server.serve_forever()
try:
    serve()
except Exception, e:
    print "probably port is used. killing processes using given port %d, %s"%(PORT_NUMBER,e)
    os.system("xterm -e 'sudo fuser -kuv %d/tcp'" % PORT_NUMBER)
    serve()
    raise e

プロセスを強制終了しても、TIME_WAITにはまったく影響しません。
user2074 2119

0

あなたがすでに答えを受け入れていることは知っていますが、問題はクライアントソケットでbind()を呼び出すことに関係していると思います。これは問題ないかもしれませんが、bind()とshutdown()は一緒にうまく機能していないようです。また、SO_REUSEADDRは通常、リッスンソケットで使用されます。つまり、サーバー側です。

connect()にIP / portを渡す必要があります。このような:

comSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
comSocket.connect(('', 5555))

bind()を呼び出さないでください。SO_REUSEADDRを設定しないでください。


2
bind()そしてshutdown()、お互いにまったく関係がなく、「彼らは一緒にうまく機能していないようだ」という提案は根拠がありません。あなたは彼がローカルポート5555を使用する必要がある一部見逃している
user207421

0

最良の方法は、ターミナルfuser -k [PORT NUMBER]/tcpに入力して、そのポートのプロセスを強制終了することだと思いますfuser -k 5001/tcp


1
プロセスがすでに強制終了されており、OSによってクリーンアップされるためにポートが開いたままになっている場合を除きます。
クリスメルク2017年

1
プロセスを強制終了しても、TIME_WAITにはまったく影響しません。
user2074 2119
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.