Skip to content

Criando um servidor TCP concorrente em Python

No semestre passado da faculdade cursei a disciplina de Redes II. Nessa matéria uma das formas de avaliação é sempre composta de um trabalho prático juntamente com os devidos relatórios. No meu semestre o trabalho consituia em desenvolver um servidor TCP concorrente que atenda à N conexões, podendo ser desenvolvido em qualquer linguagem. Eu fiz o trabalho em dupla com o Thiago Mendes e nós optamos por fazer o nosso em Python. Depois da explicação toda, segue abaixo a nossa implementação:

Servidor

Esse arquivo implementa a classe Server, cuja finalidade é ouvir a porta definida em PORT e criar processos filhos (aka fork) para as requisições recebidas. Depois de criados os sub-processos, trabalha como uma espécie “chat” entre cliente e servidor transmitindo mensagens de um para o outro.

Obs.: Note que como esse programa era para fins acadêmicos, ele está cheio de comentários explicando o comportamento do mesmo.


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
from socket import *
from settings import *

class Server:
    __addr = (HOST, PORT) # endereço de funcionamento do servidor
    __sock = None # instancia do socket para o processo pai
    __conn = None # instancia do socket para os processos filhos
    __client = 0 # numero do cliente que está sendo atendido

    def __init__(self):
        try:
            print ' * Inicializando o servidor TCP'
            print ' * Instancinado o socket do protocolo TCP'
            self.__sock = socket(AF_INET, SOCK_STREAM) # instancia o socket
        except:
            print 'ERRO: Não foi possível instanciar o socket'
            sys.exit(1)

    def __del__(self):
        self.__sock.close() # fecha o socket utilizado pelo processo pai
        del(self.__sock)

    def __connect(self):
        try:
            print ' * Alocando porta local ' + str(self.__addr[1])
            self.__sock.bind(self.__addr) # reserva a porta local
            self.__sock.listen(5) # define o tamanho da fila de espera
        except:
            print 'ERRO: Não foi possível alocar a porta local', self.__addr[1]
            sys.exit(1)

    def run(self):
        self.__connect() # instancia o socket e aloca uma porta local
        while 1:
            try: # isso evita que seja exibida a mensagem de erro quando terminar o servidor com CTRL+C
                self.__conn, self.__addr = self.__sock.accept() # aceita a requisição do cliente
            except:
                sys.exit(1)
            self.__client += 1 # incrementa o valor para o novo cliente que está sendo atendido

            print ' * Requisição recebida de', self.__addr[0] + ':' + str(self.__addr[1])
            print ' * Criando processo filho para antender cliente', self.__client

            if os.fork(): # cria um processo filho para atender a nova requisição
                self.__conn.close() # fecha a conexão com o processo filho
            else:
                data = None
                while data != "quit": # executa enquanto o cliente estiver executando
                    data = self.__conn.recv(BUFSIZ) # recebe a mensagemd o cliente
                    self.__conn.send(data) # envia a mensagem de volta, confirmando o recebimento
                    print " * Recebida a mensagem '" + data + "' do cliente", self.__client
                self.__conn.close() # fecha a conexão do processo filho
                print ' * Encerrando conexão com o cliente', self.__client
                sys.exit(1)

if __name__ == "__main__":
    server = Server()
    server.run()

Cliente

Esse arquivo implementa a classe Client, cuja finalidade é estabelecer conexões com o servidor definido em HOST e PORT. Depois de estabelecido o canal de comunicação, o programa executa trabalha como uma espécie “chat” trocando mensagens com o servidor, o programa é encerrado quando a mensagem “quit” é enviada pelo cliente.

Obs.: Assim como o servidor, este programa está cheio de comentários explicando tudo o que acontece.


#!/usr/bin/env python
# -*- coding: utf-8 -*-
from socket import *
from settings import *

class Client:
    __addr = (HOST, PORT) # endereço de funcionamento do servidor
    __sock = None # instancia do socket para o processo pai

    def __init__(self):
        try:
            print ' * Inicializando o cliente TCP'
            print ' * Instancinado o socket do protocolo TCP'
            self.__sock = socket(AF_INET, SOCK_STREAM) # instancia o socket
        except:
            print 'ERRO: Não foi possível instanciar o socket'
            exit(1)

    def __def__(self):
        self.__sock.close() # fecha o socket utilizado pelo processo
        del(self.__sock)

    def __connect(self):
        try:
            print ' * Conectando com o servidor em', self.__addr[0] + ':' + str(self.__addr[1])
            self.__sock.connect(self.__addr) # cria a conexao
        except:
            print 'ERRO: Não foi possível conectar com o servidor'
            exit(1)

    def run(self):
        self.__connect() # instancia o socket e conecta com o servidor
        data = None
        while data != "quit": # executa até quando o usuário desejar sair
            print ' >>>',
            data = str(raw_input()) # recebe a mensagem do cliente
            self.__sock.send(data) # envia a mensagem para o servidor
            data = self.__sock.recv(BUFSIZ) # recebe confirmação do envio
        print ' * Encerrando conexão com o servidor'

if __name__ == "__main__":
    client = Client()
    client.run()

Configuração

Define as configurações da aplicação. Cliente e servidor irão rodar em em localhost e o servidor atenderá requisições na porta 5002.


HOST = 'localhost'
PORT = 5002
BUFSIZ = 1024

Log do sistema operacional no servidor

Log do sistema operacional com o servidor em funcionamento. As primeiras versões do trabalho deixavam alguns processos filhos abertos mesmo depois de finalizado o servidor, o que foi corrigido e explicitado abaixo.


# antes de iniciar todos os processos
afurlan@isengard:~/redes2$ ps ux | grep python | grep server
afurlan@isengard:

# iniciado o servidor
afurlan@isengard:~/redes2$ ps ux | grep python | grep server
afurlan   9657  0.5  0.6   6048  3360 pts/0    S+   12:02   0:00 /usr/bin/python ./server
afurlan@isengard:

# iniciado o cliente 1
afurlan@isengard:~/redes2$ ps ux | grep python | grep server
afurlan   9657  0.2  0.6   6048  3384 pts/0    S+   12:02   0:00 /usr/bin/python ./server
afurlan   9663  0.0  0.3   6048  1548 pts/0    S+   12:02   0:00 /usr/bin/python ./server
afurlan@isengard:

# iniciado o cliente 2
afurlan@isengard:~/redes2$ ps ux | grep python | grep server
afurlan   9657  0.1  0.6   6048  3384 pts/0    S+   12:02   0:00 /usr/bin/python ./server
afurlan   9663  0.0  0.3   6048  1548 pts/0    S+   12:02   0:00 /usr/bin/python ./server
afurlan   9671  0.0  0.3   6048  1548 pts/0    S+   12:02   0:00 /usr/bin/python ./server
afurlan@isengard:

# encerrado o cliente 1
afurlan@isengard:~/redes2$ ps ux | grep python | grep server
afurlan   9657  0.1  0.6   6048  3384 pts/0    S+   12:02   0:00 /usr/bin/python ./server
afurlan   9671  0.0  0.3   6048  1548 pts/0    S+   12:02   0:00 /usr/bin/python ./server
afurlan@isengard:

# encerrado o cliente 2
afurlan@isengard:~/redes2$ ps ux | grep python | grep servr
afurlan   9657  0.0  0.6   6048  3384 pts/0    S+   12:02   0:00 /usr/bin/python ./server
afurlan@isengard:

# encerrado o servidor
afurlan@isengard:~/redes2$ ps ux | grep python | grep server
afurlan@isengard:~/redes2$

O Danilo Cesar cursou essa matéria comigo e também desenvolveu uma versão desse servidor, portanto encham o saco para ele postar a sua porque que ficou bem bacana :)

Clique aqui para baixar um tarball contendo todos os arquivos acima (com excessão do log).

One Trackback/Pingback

  1. [...] prestes a me formar, e lembrando-me de um convite do Arthur Furlan, decidi que estava na hora de liberar alguns trabalinhos legais que escrevi na [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*