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で問題を解決できないようですありがとうございます!
ソケットをバインドする前に、SO_REUSEADDR
ソケットオプションを使用してみてください。
comSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
編集:まだ問題が発生しているようです。 SO_REUSEADDR
が機能しない場合があります。ソケットをバインドして同じ宛先に再接続しようとすると(SO_REUSEADDR
が有効になっている場合)、TIME_WAIT
は引き続き有効です。ただし、異なるHost:portに接続できます。
いくつかの解決策が思い浮かびます。再び接続できるようになるまで、再試行を続けることができます。または、クライアントが(サーバーではなく)ソケットのクローズを開始した場合、魔法のように動作するはずです。
ここに、私がテストした完全なコードを示しますが、「すでに使用されているアドレス」エラーは絶対に表示しません。これをファイルに保存し、提供する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
実際、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 にあります。
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()
バインドする前に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()
これにより、フラグを設定する機会が得られる前にサーバーがバインドされなくなります。
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)
socket.socket()
はsocket.bind()
の前に実行し、前述のREUSEADDR
を使用する必要があります
私にとってより良い解決策は次のとおりでした。接続を閉じるというイニシアチブはサーバーによって行われたため、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のデフォルトの動作が使用されます。
もちろん、開発環境での別の解決策は、それを使用してプロセスを強制終了することです。
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
私はあなたがすでに答えを受け入れていることを知っていますが、問題はクライアントソケットでbind()を呼び出すことと関係があると思います。これは問題ないかもしれませんが、bind()とshutdown()は一緒にうまく動作しないようです。また、SO_REUSEADDRは一般に待機ソケットで使用されます。つまり、サーバー側で。
Connect()にip/portを渡す必要があります。このような:
comSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
comSocket.connect(('', 5555))
Bind()を呼び出さないでください。SO_REUSEADDRを設定しないでください。
この例外の別の理由を見つけました。 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にはこの問題はありませんでした。
最善の方法は、ターミナルfuser -k [PORT NUMBER]/tcp
を入力して、そのポートのプロセスを強制終了することです。 fuser -k 5001/tcp
。