Python: Binding Socket: „Adres już używany”


83

Mam pytanie dotyczące gniazda klienta w sieci TCP / IP. Powiedzmy, że używam

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)

Utworzone gniazdo będzie powiązane z portem 5555. Problem w tym, że po zakończeniu połączenia

comSocket.shutdown(1)
comSocket.close()

Używając wireshark, widzę, że gniazdo jest zamknięte FIN, ACK i ACK z obu stron, nie mogę ponownie użyć portu. Otrzymuję następujący błąd:

[ERROR] Address already in use

Zastanawiam się, jak mogę od razu wyczyścić port, aby następnym razem nadal korzystać z tego samego portu.

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

Program setsockopt nie jest w stanie rozwiązać problemu. Dziękujemy!


1
Dlaczego klient potrzebuje określonego portu?
AJ.

2
Ponieważ muszę to umieścić na serwerze produkcyjnym, a na tym serwerze wszystkie połączenia wychodzące są blokowane. Muszę określić konkretny port do gniazda, aby mogli ustawić regułę w zaporach ogniowych, która zezwala na połączenie.
Tu Hoang

3
Administratorzy sieci powinni wiedzieć, że ruch wychodzący może być kontrolowany przez port docelowy .
AJ.

7
to ma wystarczającą ilość informacji. istnieje 99% szansa, że ​​problem jest spowodowany TIME_WAITstanem gniazda, na który odpowiedź poniżej ma rozwiązanie :)
lunixbochs

1
jaki jest twój system operacyjny zwykle możesz użyć netstat, aby zobaczyć stan gniazda (poszukaj numeru portu, aby zidentyfikować gniazdo)
lunixbochs

Odpowiedzi:


124

Spróbuj użyć SO_REUSEADDRopcji gniazda przed powiązaniem gniazda.

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

Edycja: widzę, że nadal masz z tym problemy. Jest przypadek, w którym SO_REUSEADDRnie zadziała. Jeśli spróbujesz powiązać gniazdo i ponownie połączyć się z tym samym miejscem docelowym (z SO_REUSEADDRwłączoną opcją), to TIME_WAITnadal będzie obowiązywać. Pozwoli to jednak na połączenie z innym hostem: portem.

Przychodzi mi na myśl kilka rozwiązań. Możesz kontynuować ponawianie, aż ponownie uzyskasz połączenie. Lub jeśli klient zainicjuje zamknięcie gniazda (nie serwera), powinno to magicznie działać.


1
Nadal nie można go użyć ponownie. Czas, przez jaki muszę czekać, zanim będę mógł ponownie użyć tego samego portu, to 1 minuta 30 sekund :(
Tu Hoang,

5
dzwoniłeś setsockoptwcześniej bind? czy pierwsze gniazdo zostało utworzone za pomocą SO_REUSEADDR, czy tylko nieudane? gniazdo oczekujące musi być SO_REUSEADDRustawione, aby to działało
lunixbochs

Tak, dołączyłem comSocket.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1), ale nadal nie działa.
Tu Hoang

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

1
@jcoffland Zgadzam się, że nie powinien używać bind(), ale SO_REUSEADDR jest dla wszystkich gniazd, w tym nie tylko gniazd serwera TCP, ale także gniazd klienta TCP i gniazd UDP. Nie publikuj tutaj dezinformacji.
user207421

26

Oto pełny kod, który przetestowałem i absolutnie NIE daje mi błędu „adres już używany”. Możesz zapisać to w pliku i uruchomić plik z katalogu podstawowego plików HTML, które chcesz udostępniać. Ponadto można było programowo zmieniać katalogi przed uruchomieniem serwera

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

Nawet po wykonaniu httpd.shutdown () nadal chcesz wywołać httpd.server_close (), aby w pełni zwolnić wszystkie zasoby.
Androbin,

Rozważ również użycie modułu atexit w przypadku nieoczekiwanego wystąpienia.
Androbin,


13

Zgodnie z tym linkiem

W rzeczywistości flaga SO_REUSEADDR może prowadzić do znacznie poważniejszych konsekwencji: SO_REUSADDR pozwala ci użyć portu, który utknął w TIME_WAIT, ale nadal nie możesz użyć tego portu do nawiązania połączenia z ostatnim miejscem, do którego był podłączony. Co? Załóżmy, że wybieram lokalny port 1010 i łączę się z portem 300 foobar.com, a następnie zamykam lokalnie, pozostawiając ten port w TIME_WAIT. Mogę od razu ponownie wykorzystać lokalny port 1010, aby połączyć się z dowolnym miejscem poza portem 300 foobar.com.

Można jednak całkowicie uniknąć stanu TIME_WAIT, upewniając się, że zdalny koniec inicjuje zamknięcie (zdarzenie zamknięcia). Serwer może więc uniknąć problemów, pozwalając klientowi zamknąć się jako pierwszy. Protokół aplikacji musi być zaprojektowany tak, aby klient wiedział, kiedy zamknąć. Serwer może bezpiecznie zamknąć się w odpowiedzi na EOF od klienta, jednak będzie musiał również ustawić limit czasu, gdy oczekuje EOF w przypadku, gdy klient opuścił sieć niezręcznie. W wielu przypadkach wystarczy odczekać kilka sekund przed zamknięciem serwera.

Radzę również dowiedzieć się więcej o sieci i programowaniu sieciowym. Powinieneś teraz przynajmniej wiedzieć, jak działa protokół tcp. Protokół jest dość trywialny i niewielki, dzięki czemu może zaoszczędzić wiele czasu w przyszłości.

Za pomocą netstatpolecenia możesz łatwo zobaczyć, które programy ((nazwa_programu, pid) krotka) są powiązane z którymi portami i jaki jest aktualny stan gniazda: CZAS_WAIT, ZAMKNIĘCIE, FIN_WAIT i tak dalej.

Naprawdę dobre wyjaśnienie konfiguracji sieci linuxa można znaleźć /server/212093/how-to-reduce-number-of-sockets-in-time-wait .


Powinieneś także uważać na swój kod. Jeśli Twój kod jest nadal w fazie rozwoju i wystąpiły pewne wyjątki, połączenie może nie zostać poprawnie zamknięte, zwłaszcza po stronie serwera.
Rustem K

8
Powinieneś być uczciwy i cytować zdania, które skopiowałeś i wkleiłeś z Tom's Network Guide. Popraw swoją odpowiedź.
HelloWorld

8

Musisz ustawić allow_reuse_address przed powiązaniem. Zamiast SimpleHTTPServer uruchom ten fragment:

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

Zapobiega to wiązaniu się serwera, zanim mieliśmy szansę ustawić flagi.


Wydaje się, że znacznie łatwiej jest podklasa TCPServer i nadpisanie atrybutu, zobacz na przykład moją odpowiedź
Andrei

3

Jak wspomniał Felipe Cruze, przed związaniem należy ustawić SO_REUSEADDR. Znalazłem rozwiązanie na innej stronie - rozwiązanie na innej stronie, przedstawione poniżej

Problem polega na tym, że opcja gniazda SO_REUSEADDR musi być ustawiona przed powiązaniem adresu z gniazdem. Można to zrobić, tworząc podklasę ThreadingTCPServer i zastępując metodę server_bind w następujący sposób:

import SocketServer, gniazdo

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


3

Znalazłem inny powód tego wyjątku. Podczas uruchamiania aplikacji ze Spyder IDE (w moim przypadku był to Spyder3 na Raspbian) i programu zakończonego przez ^ C lub wyjątek, gniazdo było nadal aktywne:

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

Uruchomienie programu ponownie znalazło "Adres już używany"; IDE wydaje się uruchamiać nowe „uruchomienie” jako oddzielny proces, który znajduje gniazdo używane przez poprzednie „uruchomienie”.

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

nie pomogło.

Pomógł proces zabijania 13210. Uruchamianie skryptu w Pythonie z wiersza poleceń, na przykład

python3 <app-name>.py

zawsze działało dobrze, gdy SO_REUSEADDR było ustawione na true. Nowy Thonny IDE lub Idle3 IDE nie miał tego problemu.



1

Dla mnie lepszym rozwiązaniem było to. Ponieważ inicjatywa zamknięcia połączenia została podjęta przez serwer, setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)nie przyniosło to żadnego skutku, a TIME_WAIT unikał nowego połączenia na tym samym porcie z błędem:

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

Ostatecznie skorzystałem z rozwiązania, aby pozwolić systemowi operacyjnemu wybrać sam port, a następnie używany jest inny port, jeśli precedens jest nadal w TIME_WAIT.

Wymieniłem:

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

z:

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

Jak wskazano w dokumentacji gniazda Pythona adresu tcp:

Jeśli podano, adres_źródłowy musi być krotką (host, port), aby gniazdo zostało powiązane jako adres źródłowy przed połączeniem. Jeśli host lub port mają odpowiednio wartość „” lub 0, zostanie użyte domyślne zachowanie systemu operacyjnego.


1
Równie dobrze można pominąć wiązanie, ale OP stwierdza, że ​​musi używać portu 5555, a ta odpowiedź nie.
user207421

1

Innym rozwiązaniem, oczywiście w środowisku programistycznym, jest na przykład zabijanie procesu przy jego użyciu

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

Zabijanie procesów nie wpływa w żaden sposób na TIME_WAIT.
user207421

0

Wiem, że już zaakceptowałeś odpowiedź, ale wydaje mi się, że problem jest związany z wywołaniem funkcji bind () na gnieździe klienta. To może być w porządku, ale bind () i shutdown () nie wydają się dobrze ze sobą współpracować. Ponadto, SO_REUSEADDR jest zwykle używany z gniazdami nasłuchującymi. tj. po stronie serwera.

Powinieneś przejść i ip / port, aby się połączyć (). Lubię to:

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

Nie wywołuj bind (), nie ustawiaj SO_REUSEADDR.


2
bind()i shutdown()nie mają ze sobą nic wspólnego, a sugestia, że ​​„nie wydają się dobrze ze sobą grać” jest bezpodstawna. Przegapiłeś część, w której musi użyć lokalnego portu 5555.
user207421

0

Myślę, że najlepszym sposobem jest po prostu zabicie procesu na tym porcie, wpisując w terminalu fuser -k [PORT NUMBER]/tcpnp fuser -k 5001/tcp.


1
Chyba że proces został już zabity i po prostu pozostawił otwarty port w celu wyczyszczenia przez system operacyjny.
Chris Merck

1
Zabijanie procesów nie wpływa w żaden sposób na TIME_WAIT.
user207421
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.