Python: Binding Socket: “Адреса вже використовується”


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)

setockopt, здається, не може вирішити проблему Дякую!


1
Навіщо клієнту потрібний конкретний порт?
AJ.

2
Тому що я повинен помістити це на робочий сервер, і на цьому сервері всі вихідні з'єднання заблоковані. Мені потрібно вказати конкретний порт для сокета, щоб вони могли встановити правило на брандмауерах, яке дозволяє з'єднанню проходити.
Tu Hoang

3
Ваші адміністратори мережі повинні розуміти, що вихідний трафік може контролюватися портом призначення .
AJ.

7
цього достатньо інформації. є 99% ймовірності, що проблема викликана станом TIME_WAITсокета, для якого у відповіді нижче є рішення :)
lunixbochs

1
яка ваша операційна система? зазвичай ви можете використовувати netstat, щоб побачити стан сокета (шукайте номер порту, щоб визначити сокет)
lunixbochs

Відповіді:


124

Спробуйте скористатися SO_REUSEADDRопцією socket перед тим, як прив’язати сокет.

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

Редагувати: Я бачу, що у вас все ще виникають проблеми з цим. Є випадок, коли SO_REUSEADDRне вийде. Якщо ви спробуєте прив'язати сокет і знову підключитися до того самого пункту призначення (з SO_REUSEADDRувімкненим), TIME_WAITце все одно буде діяти. Однак це дозволить вам підключитися до іншого хосту: порту.

Пару рішень спадає на думку. Ви можете продовжити повторну спробу, поки не зможете знову встановити з'єднання. Або якщо клієнт ініціює закриття сокета (а не сервера), то це магічно має спрацювати.


1
Досі не може використовувати його повторно. Час, який мені доводиться чекати, перш ніж мати змогу повторно використовувати той самий порт, становить 1 хвилину 30 секунд :(
Tu Hoang,

5
ти дзвонив setsockopt раніше bind? був створений перший сокет SO_REUSEADDR, або просто невдалий? очікуючий сокет повинен був SO_REUSEADDRналаштувати це для роботи
lunixbochs

Так, я включив comSocket.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1), але все ще не працює.
Ту Хоанг,

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. Не розміщуйте тут дезінформацію.
user207421

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 (), щоб повністю звільнити всі ресурси.
Андробін

Крім того, розгляньте можливість використання модуля atexit для будь-якого несподіваного існування.
Андробін


13

За цим посиланням

Насправді прапор SO_REUSEADDR може призвести до набагато більших наслідків: SO_REUSADDR дозволяє використовувати порт, який застряг у TIME_WAIT, але ви все одно не можете використовувати цей порт для встановлення з'єднання з останнім місцем, до якого він підключився. Що? Припустимо, я вибираю локальний порт 1010 і підключаюся до порту foobar.com 300, а потім закриваю локально, залишаючи цей порт у TIME_WAIT. Я можу повторно використати локальний порт 1010 відразу для підключення до будь-якого місця, крім порту foobar.com 300.

Однак ви можете повністю уникнути стану TIME_WAIT, переконавшись, що віддалений кінець ініціює закриття (закриття події). Таким чином, сервер може уникнути проблем, дозволивши клієнту закритись першим. Протокол програми повинен бути розроблений таким чином, щоб клієнт знав, коли закривати. Сервер може безпечно закритись у відповідь на EOF від клієнта, однак йому також потрібно буде встановити час очікування, коли він очікує EOF на випадок, якщо клієнт невдало залишив мережу. У багатьох випадках буде достатньо просто почекати кілька секунд до закриття сервера.

Я також раджу вам дізнатись більше про мережу та мережеве програмування. Тепер слід принаймні як працює протокол tcp. Протокол досить тривіальний і невеликий, а отже, може заощадити вам багато часу в майбутньому.

С netstat команди ви можете легко побачити, які програми ((ім'я програми, pid) кортеж) прив'язані до яких портів і який поточний стан сокета: TIME_WAIT, CLOSING, FIN_WAIT тощо.

Дійсно гарне пояснення конфігурацій мережі Linux можна знайти /server/212093/how-to-reduce-number-of-sockets-in-time-wait .


Крім того, ви повинні бути обережними з кодом. Якщо ваш код все ще розробляється та трапляються деякі винятки, підключення може бути неправильно закрито, особливо з боку сервера.
Рустем К

8
Ви повинні бути справедливими і цитувати речення, які ви скопіювали та вставили з Томового путівника по мережі. Будь ласка, виправте свою відповідь.
HelloWorld

8

Вам потрібно встановити адресу_дозволеного_користування перед прив'язуванням. Замість 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 і замінити атрибут, див. Мою відповідь, наприклад
Андрій

3

Як згадував Феліпе Круз, перед прив'язуванням потрібно встановити SO_REUSEADDR. Я знайшов рішення на іншому сайті - рішення на іншому сайті, відтворене нижче

Проблема полягає в тому, що опцію сокета SO_REUSEADDR потрібно встановити, перш ніж адреса буде прив’язана до сокета. Це можна зробити, підкласуючи ThreadingTCPServer і замінюючи метод server_bind наступним чином:

імпортувати SocketServer, сокет

клас 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 (у моєму випадку це був Spyder3 на Raspbian) і програми, завершеної ^ 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. У новій IDE Thonny або Idle3 IDE цієї проблеми не було.



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 

Нарешті я використав рішення, щоб дозволити ОС вибрати порт сам, тоді використовується інший порт, якщо прецедент все ще знаходиться у TIME_WAIT.

Я замінив:

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

з:

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

Як було зазначено в документації на сокет python адреси tcp:

Якщо надається, джерело_адреси має бути двокомплексним (хост, порт), до якого сокет повинен прив'язуватися як його вихідна адреса перед підключенням. Якщо хост або порт - '' або 0, відповідно буде використана поведінка ОС за замовчуванням.


1
Ви можете так само пропустити прив'язку, але OP стверджує, що він повинен використовувати порт 5555, і ця відповідь не робить.
user207421

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.
user207421

0

Я знаю, що ви вже прийняли відповідь, але я вважаю, що проблема пов’язана із викликом bind () на клієнтському сокеті. Це може бути нормально, але bind () і shutdown (), здається, не грають добре разом. Крім того, SO_REUSEADDR зазвичай використовується з сокетами прослуховування. тобто на стороні сервера.

Вам слід пройти і 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
Хіба що процес вже не вбитий, і він просто не залишив порт відкритим для очищення ОС.
Кріс Мерк,

1
Процеси вбивства жодним чином не впливають на TIME_WAIT.
user207421
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.