Python: Binding Socket: «Адрес уже используется» - PullRequest
64 голосов
/ 17 июня 2011

У меня вопрос по поводу клиентского сокета в сети 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 не делаетКажется, в состоянии решить проблему Спасибо!

Ответы [ 12 ]

103 голосов
/ 17 июня 2011

Попробуйте использовать параметр сокета SO_REUSEADDR перед привязкой сокета.

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

Редактировать: Я вижу, у вас все еще есть проблемы с этим.Есть случай, когда SO_REUSEADDR не будет работать.Если вы попытаетесь связать сокет и повторно подключиться к тому же месту назначения (с включенным SO_REUSEADDR), то TIME_WAIT все равно будет действовать.Это, однако, позволит вам подключиться к другому хосту: порт.

На ум приходит пара решений.Вы можете продолжить повторную попытку, пока не сможете снова установить соединение.Или, если клиент инициирует закрытие сокета (не сервера), он должен волшебным образом работать.

24 голосов
/ 17 сентября 2013

Вот полный код, который я протестировал и абсолютно НЕ дает мне ошибку «адрес уже используется».Вы можете сохранить это в файле и запустить файл из базового каталога 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
12 голосов
/ 08 октября 2013

На самом деле флаг SO_REUSEADDR может привести к гораздо более серьезным последствиям: SO_REUSADDR позволяет вам использовать порт, который застрял в TIME_WAIT, но вы все равно не можете использовать этот порт для установления соединения с последним местом, к которому он подключен. Какие? Предположим, я выбрал локальный порт 1010 и подключился к порту 300 foobar.com, а затем закрылся локально, оставив этот порт в TIME_WAIT. Я могу сразу же использовать локальный порт 1010 для подключения к любому месту, кроме порта 300 foobar.com.

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

Я также советую вам узнать больше о сети и сетевом программировании. Теперь вы должны хотя бы понять, как работает протокол TCP. Протокол довольно прост и мал, поэтому в будущем может сэкономить много времени.

С помощью команды netstat вы можете легко увидеть, какие программы ((имя_программы, pid) кортеж) привязаны к каким портам и каково текущее состояние сокета: TIME_WAIT, CLOSING, FIN_WAIT и т. Д.

Действительно хорошее объяснение сетевых конфигураций Linux можно найти https://serverfault.com/questions/212093/how-to-reduce-number-of-sockets-in-time-wait.

6 голосов
/ 12 февраля 2016

Если вы столкнулись с проблемой, используя 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()
6 голосов
/ 04 ноября 2014

Вам необходимо установить 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()

Это предотвращает привязку сервера до того, как мы получим возможность установить флаги.

3 голосов
/ 28 сентября 2012

Как упоминал Фелипе Круз, вы должны установить 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)

2 голосов
/ 05 июня 2013

Для меня лучшим решением было следующее. Поскольку инициатива закрытия соединения была сделана сервером, 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-адреса:

Если указан, source_address должен быть 2-кортежем (хост, порт), чтобы сокет связывался как его адрес источника перед подключением. Если хост или порт имеют ‘’ или 0 соответственно, будет использоваться поведение ОС по умолчанию.

2 голосов
/ 17 июня 2011

socket.socket() должен работать до socket.bind() и использовать REUSEADDR, как сказано

1 голос
/ 16 мая 2018

Я нашел другую причину этого исключения. При запуске приложения из 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 запускает новый run как отдельный процесс, который находит сокет, использованный предыдущим run.

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

НЕ помогло.

Помог процесс убийства 13210. Запуск скрипта python из командной строки, например

python3 <app-name>.py

всегда работал хорошо, когда SO_REUSEADDR был установлен в true. В новой среде Thonny IDE или Idle3 IDE такой проблемы не было.

1 голос
/ 03 апреля 2014

другое решение, конечно же, в среде разработки - это процесс уничтожения, например

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