Python: Binding Socket: "Dirección ya en uso"

81

Tengo una pregunta sobre el socket del cliente en la red TCP / IP. Digamos que uso

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)

El socket creado estará vinculado al puerto 5555. El problema es que después de finalizar la conexión

comSocket.shutdown(1)
comSocket.close()

Al usar wirehark, veo el zócalo cerrado con FIN, ACK y ACK desde ambos lados, no puedo usar el puerto nuevamente. Obtuve el siguiente error:

[ERROR] Address already in use

Me pregunto cómo puedo limpiar el puerto de inmediato para que la próxima vez pueda usar ese mismo puerto.

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

setsockopt no parece poder resolver el problema ¡Gracias!

Tu Hoang
fuente
1
¿Por qué un cliente necesita un puerto específico?
AJ.
2
Porque tengo que poner eso en un servidor de producción, y en ese servidor, todas las conexiones salientes están bloqueadas. Necesito especificar un puerto específico para el socket para que puedan configurar una regla en los firewalls que permita que la conexión pase.
Tu Hoang
3
Los administradores de su red deben comprender que el puerto de destino puede controlar el tráfico saliente .
AJ.
7
esto tiene suficiente información. hay un 99% de probabilidad de que el problema sea causado por el TIME_WAITestado del socket, para el cual la respuesta a continuación tiene una solución :)
lunixbochs
1
cual es tu sistema operativo? Por lo general, puede usar netstat para ver el estado de un socket (busque el número de puerto para identificar el socket)
lunixbochs

Respuestas:

122

Intente usar la SO_REUSEADDRopción de socket antes de atar el socket.

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

Editar: veo que todavía tienes problemas con esto. Hay un caso en el SO_REUSEADDRque no funcionará. Si intenta vincular un socket y volver a conectarse al mismo destino (con SO_REUSEADDRhabilitado), TIME_WAITseguirá en vigor. Sin embargo, le permitirá conectarse a un host diferente: puerto.

Me vienen a la mente un par de soluciones. Puede seguir intentándolo hasta que pueda volver a conectarse. O si el cliente inicia el cierre del socket (no el servidor), entonces debería funcionar mágicamente.

Bryan
fuente
1
Todavía no puedo reutilizarlo. El tiempo que tengo que esperar antes de poder reutilizar el mismo puerto es 1 minuto y 30 segundos :(
Tu Hoang
5
¿Llamaste setsockoptantes bind? ¿Se creó el primer socket SO_REUSEADDRo solo el fallado? el enchufe de espera debe estar SO_REUSEADDRconfigurado para que esto funcione
lunixbochs
Sí, incluí comSocket.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) pero aún no funciona.
Tu Hoang
1
tcp 0 0 98c5-9-134-71-1: freeciv mobile-166-132-02: 2345 TIME_WAIT
Tu Hoang
1
@jcoffland Estoy de acuerdo en que no debería usarlo bind(), pero SO_REUSEADDR es para todos los sockets, incluidos no solo los sockets de servidor TCP, sino también los sockets de cliente TCP y los sockets UDP. No publique información errónea aquí.
Marqués de Lorne
26

Aquí está el código completo que he probado y absolutamente NO me da un error de "dirección ya en uso". Puede guardar esto en un archivo y ejecutar el archivo desde el directorio base de los archivos HTML que desea servir. Además, puede cambiar de directorio mediante programación antes de iniciar el servidor

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
usuario2543899
fuente
Incluso después de httpd.shutdown (), todavía desea llamar a httpd.server_close () para liberar completamente todos los recursos.
Androbin
Además, considere usar el módulo atexit para cualquier caso inesperado.
Androbin
13

Según este enlace

En realidad, el indicador SO_REUSEADDR puede tener consecuencias mucho mayores: SO_REUSADDR le permite usar un puerto que está atascado en TIME_WAIT, pero aún no puede usar ese puerto para establecer una conexión con el último lugar al que se conectó. ¿Qué? Supongamos que elijo el puerto local 1010 y me conecto al puerto 300 de foobar.com, y luego cierro localmente, dejando ese puerto en TIME_WAIT. Puedo reutilizar el puerto local 1010 de inmediato para conectarme a cualquier lugar excepto al puerto 300 de foobar.com.

Sin embargo, puede evitar completamente el estado TIME_WAIT asegurándose de que el extremo remoto inicie el cierre (evento de cierre). Entonces, el servidor puede evitar problemas dejando que el cliente se cierre primero. El protocolo de la aplicación debe diseñarse para que el cliente sepa cuándo cerrar. El servidor puede cerrarse de forma segura en respuesta a un EOF del cliente, sin embargo, también deberá establecer un tiempo de espera cuando espera un EOF en caso de que el cliente haya abandonado la red de manera desagradable. En muchos casos, basta con esperar unos segundos antes de que se cierre el servidor.

También te aconsejo que aprendas más sobre redes y programación de redes. Ahora debería al menos cómo funciona el protocolo tcp. El protocolo es bastante trivial y pequeño y, por lo tanto, puede ahorrarle mucho tiempo en el futuro.

Con el netstatcomando puede ver fácilmente qué programas ((program_name, pid) tuple) están vinculados a qué puertos y cuál es el estado actual del socket: TIME_WAIT, CLOSING, FIN_WAIT, etc.

Puede encontrar una muy buena explicación de las configuraciones de red de Linux /server/212093/how-to-reduce-number-of-sockets-in-time-wait .

Rustem K
fuente
Además, debes tener cuidado con tu código. Si su código aún está en desarrollo y se produjeron algunas excepciones, es posible que la conexión no se cierre correctamente, especialmente desde el lado del servidor.
Rustem K
8
Debe ser justo y citar las oraciones que copia y pega de Tom's Network Guide. Por favor corrija su respuesta.
HelloWorld
8

Debe establecer allow_reuse_address antes de vincular. En lugar de SimpleHTTPServer, ejecute este fragmento:

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

Esto evita que el servidor se vincule antes de que tengamos la oportunidad de configurar las banderas.

Vukasin Toroman
fuente
Parece mucho más fácil subclasificar TCPServer y anular el atributo, vea mi respuesta, por ejemplo
Andrei
3

Como mencionó Felipe Cruze, debe configurar SO_REUSEADDR antes de vincular. Encontré una solución en otro sitio - solución en otro sitio, que se reproduce a continuación

El problema es que la opción de socket SO_REUSEADDR debe establecerse antes de que la dirección esté vinculada al socket. Esto se puede hacer subclasificando ThreadingTCPServer y anulando el método server_bind de la siguiente manera:

import SocketServer, socket

clase 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
fuente
3

Encontré otra razón para esta excepción. Al ejecutar la aplicación desde Spyder IDE (en mi caso fue Spyder3 en Raspbian) y el programa terminó con ^ C o una excepción, el socket seguía activo:

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

Al ejecutar el programa nuevamente, se encontró la "Dirección ya en uso"; el IDE parece iniciar la nueva 'ejecución' como un proceso separado que encuentra el socket utilizado por la 'ejecución' anterior.

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

no ayudó.

El proceso de matanza 13210 ayudó. Iniciando el script de Python desde la línea de comandos como

python3 <app-name>.py

siempre funcionó bien cuando SO_REUSEADDR se estableció en verdadero. El nuevo Thonny IDE o Idle3 IDE no tenía este problema.

peets
fuente
1

socket.socket()debe ejecutarse antes socket.bind()y usar REUSEADDRcomo se dijo

Felipe Cruz
fuente
cambió el contenido de la pregunta: stackoverflow.com/revisions/6380057/4 - verifique las revisiones antes de la próxima vez :)
Felipe Cruz
1

Para mí, la mejor solución fue la siguiente. Dado que la iniciativa de cerrar la conexión fue realizada por el servidor, el setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)no tuvo efecto y el TIME_WAIT estaba evitando una nueva conexión en el mismo puerto con error:

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

Finalmente usé la solución para permitir que el sistema operativo elija el puerto en sí, luego se usa otro puerto si el precedente todavía está en TIME_WAIT.

Yo reemplacé:

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

con:

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

Como se indicó en la documentación del socket de python de una dirección tcp:

Si se proporciona, source_address debe ser una tupla de 2 (host, puerto) para que el socket se una como su dirección de origen antes de conectarse. Si el host o el puerto son '' o 0 respectivamente, se utilizará el comportamiento predeterminado del sistema operativo.

usuario2455528
fuente
1
También podría omitir el enlace, pero el OP indica que debe usar el puerto 5555, y esta respuesta no lo hace.
Marqués de Lorne
1

otra solución, en el entorno de desarrollo, por supuesto, es matar el proceso usándolo, por ejemplo

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
fuente
Los procesos de eliminación no afectan a TIME_WAIT de ninguna manera.
Marqués de Lorne
0

Sé que ya ha aceptado una respuesta, pero creo que el problema tiene que ver con llamar a bind () en un socket de cliente. Esto podría estar bien, pero bind () y shutdown () no parecen funcionar bien juntos. Además, SO_REUSEADDR se usa generalmente con conectores de escucha. es decir, en el lado del servidor.

Debería pasar una ip / puerto para conectar (). Me gusta esto:

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

No llame a bind (), no configure SO_REUSEADDR.

jcoffland
fuente
2
bind()y no shutdown()tienen exactamente nada que ver el uno con el otro, y la sugerencia de que "no parecen jugar bien juntos" no tiene fundamento. Te has perdido la parte donde necesita usar el puerto local 5555.
Marqués de Lorne
0

Creo que la mejor manera es simplemente matar el proceso en ese puerto, escribiendo en la terminal fuser -k [PORT NUMBER]/tcp, por ejemplo fuser -k 5001/tcp.

Kiblawi_Rabee
fuente
1
A menos que el proceso ya haya terminado y haya dejado el puerto abierto para que lo limpie el sistema operativo.
Chris Merck
1
Los procesos de eliminación no afectan a TIME_WAIT de ninguna manera.
Marqués de Lorne