Servidor Socket & Threading: como parar de forma correta?

614 views
Skip to first unread message

Angelo Sampaio

unread,
Feb 7, 2017, 7:17:04 AM2/7/17
to Python Brasil
Prezados

Estou tentando conectar duas aplicações em python que rodam em máquinas diferentes e estou lendo os exemplos e documentações sobre socket. Para o servidor cheguei até este ponto:

import socket, threading, time

class SockServer(threading.Thread):

    def __init__(self, host, port):
        threading.Thread.__init__(self)
        self.host = host
        self.port = port
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind((self.host, self.port))
        
    def run(self):
        self.sock.listen(5)
        client, address = self.sock.accept()
        size = 1024
        while True:
            try:
                data = client.recv(size)
                if data:
                    response = data
                    client.send(response)
                else:
                    raise error('Cliente desconectado')
            except:
                client.close()
                return False

# Chamada do servidor socket
if __name__ == "__main__":
    while True:

        try:
            if servidor.isAlive():
                pass
        except:
            servidor = SockServer('', 50000)
            servidor.start()


Neste trecho de código ele repete a informação recebida para o cliente e serve apenas para me certificar que tudo está funcionando corretamente. Ocorre que eu não consigo parar o servidor com ctrl+c  e sou obrigado a fechar o terminal toda a vez que preciso interromper a aplicação. Quais as estratégias mais corretas para interromper o funcionamento deste servidor?

Thiago

unread,
Feb 7, 2017, 8:37:24 AM2/7/17
to python...@googlegroups.com
Eu acho que a função recv está travando o seu servidor.

Não sei se essa forma seria a mais apropriada, mas você poderia colocar
uma variável no while do servidor, a qual interromperia o servidor
quando o signal do ctrl+c fosse encontrado.

Não sei para qual fim você quer esse servidor, mas eu não recomendaria
usar threads, pois quando eu o fiz (quase dessa mesma forma que você)
comecei a ter muitos problemas difíceis de resolver.

Quando eu troquei para um sistema mais simples com select e eliminei o
thread (uma programação quase que assíncrona), os problemas saíram e eu
consegui ter mais controle sobre meu código.

Talvez essa não seja a melhor solução, mas foi a que me deu mais
flexibilidade ao escrever meu servidor.

Se alguém tiver outra solução, também estou a disposição.

Angelo Sampaio

unread,
Feb 7, 2017, 2:53:47 PM2/7/17
to Python Brasil
Thiago

Vc tocou no ponto 'X' da questão, pois andei tentando colocar variáveis e capturar o ctrl+c e sempre fico preso nesse client.recv. Estou buscando exemplos e maiores detalhes sobre o uso do select e um novo leque de opções se abriu.

Valeu pela dica. 

Thiago

unread,
Feb 7, 2017, 4:02:09 PM2/7/17
to python...@googlegroups.com
Dei uma modificada no teu código de servidor, espero que não tenha
complicado ele demais.

Procurei não me distanciar muito do teu exemplo para facilitar o
entendimento, mas qualquer dúvida pode perguntar.


import select, socket, threading, time

class SockServer(threading.Thread):

def __init__(self, host, port):
threading.Thread.__init__(self)
self.running = True
self.host = host
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind((self.host, self.port))

def try_to_stop(self):
# Nota: nunca se sabe quando o servidor vai fechar
self.running = False

def _select(self, socket):
"""Retorna True se uma operacao no socket pode ser realizada
sem travar."""
reads, writes, errors = select.select([socket], [], [], 0.0001)
return socket in reads

def _mainloop(self, client, address):
size = 1024
while self.running:
try:
# Ve se chegou algo para ler no socket
if self._select(client): # Chegou
data = client.recv(size)
if data:
response = data
client.send(response)
else:
raise error('Cliente desconectado')
except KeyboardInterrupt: # Problema com o try / except
raise
except:
client.close()
return False

def run(self):
self.sock.listen(5)
# Espera uma conexao chegar no socket
while self.running and not self._select(self.sock):
time.sleep(0.1)
# Alguem conectou
if self._select(self.sock):
client, address = self.sock.accept()
self._mainloop(client, address)

# Chamada do servidor socket
if __name__ == "__main__":
pode_criar_servidor = True
while pode_criar_servidor:
try:
if servidor.isAlive():
time.sleep(0.1) # Evita sobrecarregar o computador
except KeyboardInterrupt:
pode_criar_servidor = False
servidor.try_to_stop()
except:
if pode_criar_servidor:
servidor = SockServer('', 50000)
servidor.start()


Angelo Sampaio

unread,
Feb 7, 2017, 4:25:32 PM2/7/17
to Python Brasil
Thiago

Cara, vc não complicou, vc explicou! Eu não havia encontrado nenhuma referencia ao 'select' pois sempre procurava por artigos ou dicas buscando 'socket' e 'threading' . e depois da sua dica tudo ficou mais fácil. Além do seu ótimo exemplo, adiciono os seguintes links que podem ajudar outros com as mesmas dúvidas:



Para dinamizar o meu aprendizado em python estou portando o código de uma solução de simulação de sistemas elétricos de potência que fiz em 2009. Nesse momento estou fazendo os diversos componentes comunicarem-se via socket.

Valeu mesmo por sua orientação!
 

Angelo Sampaio

unread,
Feb 28, 2017, 6:47:57 PM2/28/17
to Python Brasil
Bem, depois do post do Thiago consegui evoluir bastante na parte socket das aplicações que estou desenvolvendo porém uma dificuldade a mais surgiu: quando a comunicação era interrompida entre as máquinas (travamento da aplicação ou interrupção da rede) não era possível restabelecer sem reiniciar o socket em todas as máquinas. Então tentei fazer clientes e servidores com uma espécie de 'self healing'. A conexão caiu? Tudo bem - para tudo, nada mais é gerado até a comunicação voltar e então conecta de novo e a vida segue. 
Eu envio um keep alive para saber se a comunicação está viável e se não estiver vai ocorrer a exceção 'broken pipe' e eu faço uma lógica com isso. Tem jeito de saber se a comunicação entre cliente e servidor caiu sem ter de enviar pacotes de keep alive?

Sugestões são bem vindas pois sou bem novato em Python.

O código segue abaixo:

#!/usr/bin/env python
# coding: utf-8

import os, sys, time, datetime, logging, threading, socket, select

class SockServer(threading.Thread):
    """ Classe para prover servidor de conexão socket usando threads
        Sintaxe: servidor = SockServer(host,port)
    """

    def __init__(self, host, port):
        # Método para construir a classe
        threading.Thread.__init__(self)
        self.running = True
        self.host = host
        self.port = port

        # Configurando socket para TCP
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind((self.host, self.port))

    def stop(self):
        # Método para interromper a thread do servidor.
        self.running = False

    def _select(self, socket):
        # Método para detectar se ocorreu recepção de dados no socket
        # sem interromper o loop no comando 'recv'.
        # Retorna 'True' se existirem dados no buffer.
        reads, writes, errors = select.select([socket], [], [], 0.0001)
        return socket in reads

    def run(self):
        # Método obrigatório para a thread aceitar ativação através de 'start()'.
        # Ativa a recepção de conexão dos clientes.
        self.sock.listen(5)

        # Rotina que trava a execução da recepção de dados até o momento da
        # detecção da conexão de um cliente.
        while self.running and not self._select(self.sock):
            time.sleep(0.01)

        # Estabelece a conexão com o cliente e executa a rotina para processamento de
        # processamento da informações recebidas do cliente.
        if self._select(self.sock):
            client, address = self.sock.accept()
            self.exec_cmd(client, address)

    def exec_cmd(self, client, address):
        # Método que  executa o comando recebido do cliente
        size = 1024
        while self.running:
            try:
                # Rotina para tratar a chegada de dados no socket
                if self._select(client):
                    cmd = client.recv(size).split()
                    if cmd:
                      
                        # Ação do servidor
                        print cmd

            except:
                self.stop()
                client.close()
                return False

class SockClient(threading.Thread):
    """ Classe para prover cliente de conexão socket usando threads
        Sintaxe: cliente = SockClient(host,port)
    """
    def __init__(self, host, port):
        # Método para construir a classe
        threading.Thread.__init__(self)
        self.running = True
        self.host = host
        self.port = port

        # Configurando socket para TCP
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.dest = (self.host, self.port)

    def stop(self):
        # Método para interromper a thread do cliente.
        self.running = False
        self.sock.close()

    def _send(self, cmd):
        # Método para envio de comandos para o servidor.
        self.cmd = cmd
        try:
            self.sock.send(self.cmd)
        except:
            return False

    def run(self):
        # Método obrigatório para a thread aceitar ativação através de 'start()'.
        # Ativa o cliente socket.
        try:
            self.sock.connect(self.dest)

            # Loop infinito para manter a execução do cliente
            while self.running:

                # Enviando mensagem para verificar se a conexão está integra
                self.sock.send('KeepAlive ' + timestamp('hmsms')
                time.sleep(1)
        except:
            self.stop()
            return False

def timestamp(tipo):
    """ Funcao para gerar timestamp de uso geral """

    if tipo == 'hmsms':
        return str('{0:%H%M%S%f}'.format(datetime.datetime.now()))[0:9]
    elif tipo == 'ms':
        return str('{:%f}'.format(datetime.datetime.now()))[0:3]

# Chamada para camada de comunicação socket
if __name__ == "__main__":

    os.system('clear')

    # Criando variáveis de ambiente e verificando diretórios.
    # Detectando em qual diretório a aplicação está sendo executada
    dir_atual = os.getcwd()

    # Verificando se existe o diretório de 'log'.
    # Se não existir - Criar.
    log_dir = dir_atual + '/log'
    if not os.path.exists(log_dir):
        os.mkdir(log_dir)

    # Criando o arquivo 'simul.log'.
    nome_arq_log = log_dir + '/' + 'sock_' + timestamp('hmsms') + '.log'
    with open(nome_arq_log, 'w') as arq_log:
        arq_log.write('\n')

    # Variável com o nome da aplicação
    nome_prog = sys.argv[0].split('./')[1]

    # Ajustando recursos de logging
    logger = logging.getLogger(nome_prog)
    handler = logging.FileHandler(nome_arq_log)
    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    # Setando para debug
    logger.setLevel(logging.DEBUG)

    # CONFIGURAÇÃO DO SERVIDOR
    # Endereço IP da máquina LOCAL
    ip_local = ''
    port_local = 50000

    # CONFIGURAÇÃO DO CLIENTE
    # Endereço IP da máquna REMOTA
    ip_remoto = '192.168.1.100'
    port_remoto = 40000

    # Chamada da função de conexão
    try:
        while True:
            running = True
            status_conexao_ini = ''
            try:
                while running:
        
                    # Rotina de ativação do servidor local 'tutor'
                    # --------------------------------------------
                    try:
                        if not servidor.isAlive() and running:
                            servidor.stop()
                            servidor = SockServer(ip_local, port_local)
                            servidor.start()
                    except:
                        if running:
                            servidor = SockServer(ip_local, port_local)
                            servidor.start()
        
                    # Rotina de ativação do cliente 'sage'
                    # ------------------------------------
                    try:
                        # Verificando o status inicial do cliente.
                        stat_cliente_ini = cliente.isAlive()
        
                        # Caso a thread do cliente exista mas esteja inativa -> ativar.
                        # Na primeira execução ocorrerá um erro que será tratado por 'except'.
                        if not cliente.isAlive() and running:
                            cliente.stop()
                            cliente = SockClient(ip_remoto, port_remoto)
                            cliente.start()
        
                        # Verificando o status atual do cliente.
                        stat_cliente_atu = cliente.isAlive()
        
                        # Determinando se o cliente esta conectado.
                        if stat_cliente_ini == True and stat_cliente_atu == True:
                            status_conexao_atu = "Conectado"
                        else:
                            status_conexao_atu = "Desconectado"
        
                        # Informando o status do cliente somente quando há transição de estados
                        if status_conexao_ini != status_conexao_atu:
                            status_conexao_ini = status_conexao_atu
        
                            # Formatando mensagens de log
                            if status_conexao_ini == "Desconectado":
                                logger.critical('- Sem conexão com o servidor remoto.')
                            elif status_conexao_ini == "Conectado":
                                logger.info(' - Conexão estabelecida com o servidor remoto.')
                    except:
                        if running:
                            cliente = SockClient(ip_remoto, port_remoto)
                            cliente.start()
                    time.sleep(4)
            except:
                running = False
                servidor.stop()
                cliente.stop()            
    except KeyboardInterrupt:
        logger.info(' - Comunicação socket desativada.')


Thiago

unread,
Feb 28, 2017, 8:04:49 PM2/28/17
to python...@googlegroups.com
Oi Angelo.

Se não me falhe a memória, quando você aplicar a função recv(bytes) no
socket, e você receber uma string vazia como resposta, então quer dizer
que a conexão caiu.

Você provavelmente vai ter que testar porque estou dizendo isso com base
na memória, mas eu tenho quase certeza de que é isso.
Reply all
Reply to author
Forward
0 new messages