[Python-es] ¿Como cierro correctamente un hilo-servidor de sockets?

Sergio Martín sergiomartinj en gmail.com
Vie Ago 26 02:17:22 CEST 2011


Tengo un script en el que, primero, ejecuto un servidor de sockets en
un hilo, y cada conexión que reciba, genera su propio hilo.
El problema viene cuando intento salirme del programa mediante una
excepción KeyboardInterrupt controlada, funciona bien si no ha habido
ninguna conexión al socket-servidor, pero si me salgo del programa una
vez que he recibido alguna conexión, y, a continuación ejecuto el
programa de nuevo, me sale un "socket.error: [Errno 48] Address
already in use", como si no hubiese cerrado el socket del servidor
correctamente, teniéndome que esperar un rato hasta que se libere el
puerto.
Tengo controladas dos situaciones una que desde el cliente telnet se
pase el comando "quit", con lo que cierro el socket del cliente, y
otra cuando se pierde la conexión con el cliente sin introducir el
comando "quit"
El error solo me lo lanza cuando he salido por medio del "quit".

Aviso que está escrito en python3, y se que hay mejores formas de
hacer esto en vez de usar hilos, como el módulo twisted (sin
compatibilidad python3) o el asyncore, pero solo tengo planeado
recibir un par de conexiones simultáneas por lo que no se generarán
muchos hilos.

Pongo una versión simplificada del programa, con solo lo básico para
ilustrar el problema:

#! /usr/bin/env python3

import threading
import socket
import sys
import time

class TelnetServer(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        self.socketserver = socket.socket()
        self.socketserver.bind(('', 9999))
        self.socketserver.listen(5)

    def run(self):
        print('Servidor en marcha')
        while True:
            socketclient, addr = self.socketserver.accept()
            client = TelnetClient(socketclient, addr)
            client.start()

    def close(self):
        print('Servidor detenido')

class TelnetClient(threading.Thread):

    def __init__(self, socketclient, addr):
        threading.Thread.__init__(self)
        self.socketclient = socketclient
        self.addr = addr

    def run(self):
        print('Conexión: %s:%s' % (self.addr[0], self.addr[1]))
        while True:
            try:
                command, args = self.prompt()
            except socket.error:
                self.close()
                break

            if command == None:
                pass
            elif command == 'quit':
                self.close()
                break
            else:
                self.send('Comando desconocido\n')

    def send(self, msg):
        self.socketclient.send(msg.encode('utf8'))

    def recv(self):
        return self.socketclient.recv(1024).decode('utf8')[:-2]

    def prompt(self):
        try:
            self.send('prompt> ')
            recv_list = self.recv().split()
            return recv_list[0].lower(), recv_list[1:]
        except IndexError:
            return None, []

    def close(self):
        self.socketclient.close()
        print('Desconexión: %s:%s' % (self.addr[0], self.addr[1]))

if __name__ == '__main__':
    try:
        telnetserver = TelnetServer()
        telnetserver.daemon = True
        telnetserver.start()

        while True:
            time.sleep(100)

    except KeyboardInterrupt:
        telnetserver.close()
        sys.exit()


Más información sobre la lista de distribución Python-es