Python: Binding Socket: "Adresse wird bereits verwendet"

81

Ich habe eine Frage zum Client-Socket im TCP / IP-Netzwerk. Nehmen wir an, ich benutze

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)

Der erstellte Socket wird an Port 5555 gebunden. Das Problem besteht darin, dass nach dem Beenden der Verbindung

comSocket.shutdown(1)
comSocket.close()

Bei Verwendung von Wireshark sehe ich, dass die Buchse von beiden Seiten mit FIN, ACK und ACK geschlossen ist. Ich kann den Port nicht mehr verwenden. Ich erhalte folgende Fehlermeldung:

[ERROR] Address already in use

Ich frage mich, wie ich den Port sofort löschen kann, damit ich beim nächsten Mal immer noch denselben Port verwenden kann.

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

setsockopt scheint das Problem nicht lösen zu können. Danke!

Tu Hoang
quelle
1
Warum benötigt ein Client einen bestimmten Port?
AJ.
2
Da ich das auf einem Produktionsserver ablegen muss und auf diesem Server alle ausgehenden Verbindungen blockiert sind. Ich muss einen bestimmten Port für den Socket angeben, damit sie eine Regel für die Firewalls einrichten können, über die die Verbindung hergestellt werden kann.
Tu Hoang
3
Ihre Netzwerkadministratoren sollten verstehen , dass Outbound - Verkehr kann gesteuert werden durch Zielhafen.
AJ.
7
Dies hat genug Informationen. Es besteht eine 99% ige Wahrscheinlichkeit, dass das Problem durch den TIME_WAITSocket-Status verursacht wird , für den die folgende Antwort eine Lösung bietet :)
lunixbochs
1
Was ist dein Betriebssystem? Normalerweise können Sie netstat verwenden, um den Status eines
Sockets anzuzeigen

Antworten:

122

Versuchen Sie es mit der SO_REUSEADDRSocket-Option, bevor Sie den Socket binden.

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

Edit: Ich sehe, du hast immer noch Probleme damit. Es gibt einen Fall, in dem SO_REUSEADDRes nicht funktioniert. Wenn Sie versuchen, einen Socket zu binden und erneut eine Verbindung zum selben Ziel herzustellen (mit SO_REUSEADDRaktiviert), TIME_WAITbleibt dies weiterhin wirksam. Sie können jedoch eine Verbindung zu einem anderen Host herstellen: Port.

Ein paar Lösungen kommen in den Sinn. Sie können entweder so lange wiederholen, bis Sie wieder eine Verbindung herstellen können. Oder wenn der Client das Schließen des Sockets initiiert (nicht des Servers), sollte dies auf magische Weise funktionieren.

Bryan
quelle
1
Kann es immer noch nicht wiederverwenden. Die Zeit, die ich warten muss, bevor ich denselben Port wiederverwenden kann, beträgt 1 Minute 30 Sekunden :(
Tu Hoang
5
haben , rufen Sie setsockoptvor bind? Wurde der erste Socket mit SO_REUSEADDRoder nur der ausgefallene erstellt? Die wartende Steckdose muss SO_REUSEADDReingestellt sein, damit dies funktioniert
lunixbochs
Ja, ich habe comSocket.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) eingefügt, funktioniert aber immer noch nicht.
Tu Hoang
1
tcp 0 0 98c5-9-134-71-1: freeciv mobile-166-132-02: 2345 TIME_WAIT
Tu Hoang
1
@jcoffland Ich bin damit einverstanden, dass er nicht verwenden sollte bind(), aber SO_REUSEADDR gilt für alle Sockets, einschließlich nicht nur TCP-Server-Sockets, sondern auch TCP-Client-Sockets und UDP-Sockets. Veröffentlichen Sie hier keine Fehlinformationen.
Marquis von Lorne
26

Hier ist der vollständige Code, den ich getestet habe und der mir KEINEN Fehler "Adresse wird bereits verwendet" gibt. Sie können dies in einer Datei speichern und die Datei aus dem Basisverzeichnis der HTML-Dateien ausführen, die Sie bereitstellen möchten. Darüber hinaus können Sie Verzeichnisse vor dem Starten des Servers programmgesteuert ändern

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
user2543899
quelle
Auch nach httpd.shutdown () möchten Sie noch httpd.server_close () aufrufen, um alle Ressourcen vollständig freizugeben.
Androbin
Ziehen Sie auch in Betracht, das atexit-Modul für unerwartete Ereignisse zu verwenden.
Androbin
13

Nach diesem Link

Tatsächlich kann das SO_REUSEADDR-Flag zu weitaus größeren Konsequenzen führen: Mit SO_REUSADDR können Sie einen Port verwenden, der in TIME_WAIT steckt, aber Sie können diesen Port immer noch nicht verwenden, um eine Verbindung zu dem zuletzt verbundenen Ort herzustellen. Was? Angenommen, ich wähle den lokalen Port 1010 aus, stelle eine Verbindung zum foobar.com-Port 300 her und schließe dann lokal, wobei dieser Port in TIME_WAIT verbleibt. Ich kann den lokalen Port 1010 sofort wiederverwenden, um eine Verbindung zu einem anderen Ort als dem foobar.com-Port 300 herzustellen.

Sie können den Status TIME_WAIT jedoch vollständig vermeiden, indem Sie sicherstellen, dass das Remote-Ende das Schließen initiiert (Ereignis close). So kann der Server Probleme vermeiden, indem er den Client zuerst schließen lässt. Das Anwendungsprotokoll muss so konzipiert sein, dass der Client weiß, wann er schließen muss. Der Server kann als Antwort auf eine EOF vom Client sicher geschlossen werden. Er muss jedoch auch eine Zeitüberschreitung festlegen, wenn er eine EOF erwartet, falls der Client das Netzwerk unangemessen verlassen hat. In vielen Fällen reicht es aus, nur ein paar Sekunden zu warten, bevor der Server geschlossen wird.

Ich rate Ihnen auch, mehr über Networking und Netzwerkprogrammierung zu erfahren. Sie sollten jetzt zumindest wissen, wie das TCP-Protokoll funktioniert. Das Protokoll ist recht trivial und klein und kann Ihnen daher in Zukunft viel Zeit sparen.

Mit dem netstatBefehl können Sie leicht sehen, welche Programme ((Programmname, PID) Tupel) an welche Ports gebunden sind und wie der aktuelle Socket-Status ist: TIME_WAIT, CLOSING, FIN_WAIT und so weiter.

Eine wirklich gute Erklärung der Linux-Netzwerkkonfigurationen finden Sie unter /server/212093/how-to-reduce-number-of-sockets-in-time-wait .

Rustem K.
quelle
Außerdem sollten Sie mit Ihrem Code vorsichtig sein. Wenn sich Ihr Code noch in der Entwicklung befindet und einige Ausnahmen aufgetreten sind, wird die Verbindung möglicherweise nicht ordnungsgemäß geschlossen, insbesondere von der Serverseite.
Rustem K
8
Sie sollten fair sein und die Sätze zitieren, die Sie aus Toms Network Guide kopiert und eingefügt haben . Bitte korrigieren Sie Ihre Antwort.
Hello World
8

Sie müssen die allow_reuse_address vor dem Binden festlegen. Führen Sie anstelle des SimpleHTTPServers dieses Snippet aus:

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

Dies verhindert, dass der Server bindet, bevor wir die Flags setzen können.

Vukasin Toroman
quelle
Es scheint viel einfacher zu sein, TCPServer zu unterordnen und das Attribut zu überschreiben, siehe meine Antwort zum Beispiel
Andrei
3

Wie Felipe Cruze erwähnt hat, müssen Sie vor dem Binden SO_REUSEADDR festlegen. Ich habe eine Lösung auf einer anderen Site gefunden - eine Lösung auf einer anderen Site, die unten wiedergegeben ist

Das Problem ist, dass die Socket-Option SO_REUSEADDR festgelegt werden muss, bevor die Adresse an den Socket gebunden wird. Dies kann durch Unterklassen von ThreadingTCPServer und Überschreiben der Methode server_bind wie folgt erfolgen:

SocketServer, Socket importieren

Klasse MyThreadingTCPServer (SocketServer.ThreadingTCPServer):
    def server_bind (self):
        self.socket.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind (self.server_address)

Maurice Flanagan
quelle
3

Ich habe einen anderen Grund für diese Ausnahme gefunden. Beim Ausführen der Anwendung von Spyder IDE (in meinem Fall Spyder3 auf Raspbian) und Beenden des Programms durch ^ C oder eine Ausnahme war der Socket noch aktiv:

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

Beim erneuten Ausführen des Programms wurde die "bereits verwendete Adresse" gefunden. Die IDE scheint den neuen 'Lauf' als separaten Prozess zu starten, der den vom vorherigen 'Lauf' verwendeten Socket findet.

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

hat nicht geholfen.

Tötungsprozess 13210 half. Starten des Python-Skripts über die Befehlszeile wie

python3 <app-name>.py

funktionierte immer gut, wenn SO_REUSEADDR auf true gesetzt war. Die neue Thonny IDE oder Idle3 IDE hatte dieses Problem nicht.

Peets
quelle
1

socket.socket()sollte vorher laufen socket.bind()und REUSEADDRwie gesagt verwenden

Felipe Cruz
quelle
er änderte den Inhalt der Frage: stackoverflow.com/revisions/6380057/4 - überprüfen Sie die Revisionen vor dem nächsten Mal :)
Felipe Cruz
1

Für mich war die bessere Lösung die folgende. Da die Initiative zum Schließen der Verbindung vom Server durchgeführt wurde, setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)hatte dies keine Auswirkung und TIME_WAIT vermied eine neue Verbindung auf demselben Port mit dem Fehler:

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

Ich habe die Lösung schließlich verwendet, um das Betriebssystem den Port selbst auswählen zu lassen. Dann wird ein anderer Port verwendet, wenn sich der Präzedenzfall noch in TIME_WAIT befindet.

Ich ersetzte:

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

mit:

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

Wie in der Python-Socket-Dokumentation einer TCP-Adresse angegeben:

Wenn angegeben, muss source_address ein 2-Tupel (Host, Port) sein, an das der Socket vor dem Herstellen der Verbindung als Quelladresse gebunden werden soll. Wenn Host oder Port '' oder 0 sind, wird das Standardverhalten des Betriebssystems verwendet.

user2455528
quelle
1
Sie können die Bindung genauso gut weglassen, aber das OP gibt an, dass er Port 5555 verwenden muss, und diese Antwort tut dies nicht.
Marquis von Lorne
1

Eine andere Lösung, natürlich in der Entwicklungsumgebung, ist beispielsweise das Beenden von Prozessen

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
test30
quelle
Das Beenden von Prozessen hat keinerlei Auswirkungen auf TIME_WAIT.
Marquis von Lorne
0

Ich weiß, dass Sie bereits eine Antwort akzeptiert haben, aber ich glaube, das Problem hat mit dem Aufruf von bind () auf einem Client-Socket zu tun. Dies mag in Ordnung sein, aber bind () und shutdown () scheinen nicht gut zusammen zu spielen. Außerdem wird SO_REUSEADDR im Allgemeinen mit Listen-Sockets verwendet. dh auf der Serverseite.

Sie sollten übergeben und IP / Port zu verbinden (). So was:

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

Rufen Sie bind () nicht auf, setzen Sie SO_REUSEADDR nicht.

jcoffland
quelle
2
bind()und shutdown()haben genau nichts miteinander zu tun, und der Vorschlag, dass "sie nicht gut zusammen zu spielen scheinen", ist unbegründet. Sie haben den Teil verpasst, in dem er den lokalen Hafen 5555 nutzen muss.
Marquis of Lorne
0

Ich denke, der beste Weg ist, den Prozess an diesem Port zu beenden, indem Sie das Terminal eingeben fuser -k [PORT NUMBER]/tcp, z fuser -k 5001/tcp.

Kiblawi_Rabee
quelle
1
Es sei denn, der Prozess ist bereits beendet und der Port wurde nur geöffnet, um vom Betriebssystem bereinigt zu werden.
Chris Merck
1
Das Beenden von Prozessen hat keinerlei Auswirkungen auf TIME_WAIT.
Marquis von Lorne