web-dev-qa-db-ja.com

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

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で問題を解決できないようですありがとうございます!

65
Tu Hoang

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

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

編集:まだ問題が発生しているようです。 SO_REUSEADDRが機能しない場合があります。ソケットをバインドして同じ宛先に再接続しようとすると(SO_REUSEADDRが有効になっている場合)、TIME_WAITは引き続き有効です。ただし、異なるHost:portに接続できます。

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

104
Bryan

ここに、私がテストした完全なコードを示しますが、「すでに使用されているアドレス」エラーは絶対に表示しません。これをファイルに保存し、提供する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
23
user2543899

実際、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)Tuple)がどのポートにバインドされており、ソケットの現在の状態(TIME_WAIT、CLOSING、FIN_WAITなど)が簡単にわかります。

Linuxネットワーク構成の本当に良い説明は https://serverfault.com/questions/212093/how-to-reduce-number-of-sockets-in-time-wait にあります。

12
Rustem K

TCPServerまたはSimpleHTTPServerを使用して問題に直面した場合、 SocketServer.TCPServer.allow_reuse_address (python 2.7.x)または socketserver.TCPServer.allow_reuse_address (python 3.x)属性をオーバーライドします

class MyServer(SocketServer.TCPServer):
    allow_reuse_address = True

server = MyServer((Host, PORT), MyHandler)
server.serve_forever()
8
Andrei

バインドする前に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()

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

6
Vukasin Toroman

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

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

 import SocketServer、socket 
 
 class MyThreadingTCPServer(SocketServer.ThreadingTCPServer):
 def server_bind(self):
 self.socket.setsockopt(socket .SOL_SOCKET、socket.SO_REUSEADDR、1)
 self.socket.bind(self.server_address)
 
3

socket.socket()socket.bind()の前に実行し、前述のREUSEADDRを使用する必要があります

2
Felipe Cruz

私にとってより良い解決策は次のとおりでした。接続を閉じるというイニシアチブはサーバーによって行われたため、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 socket documentation で示されているように:

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

2
user2455528

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

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
1
test30

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

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

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

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

1
jcoffland

この例外の別の理由を見つけました。 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に設定されている場合は常に正常に機能しました。新しいThonny IDEまたはIdle3 IDEにはこの問題はありませんでした。

1
peets

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

0
Kiblawi_Rabee