Skip to content

11. As funções de rede do Python

Abordaremos agora as funções de rede do Python que nos permitem programar em TCP / IP (Protocolo de Controle de Transferência / Protocolo de Internet).

  

11.1. Obter o nome ou o endereço IP de um computador na Internet


Programa (inet_01)


import sys, socket

#------------------------------------------------
def getIPandName(nomMachine):
    #nomMachine: nome do computador cujo endereço se deseja obter IP
    # nomMachine-->endereço IP
    try:
        ip=socket.gethostbyname(nomMachine)
        print "ip[%s]=%s" % (nomMachine,ip)
    except socket.error, erreur:
        print "ip[%s]=%s" % (nomMachine,erreur)
        return

    # endereço IP --> nomMachine
    try:
        name=socket.gethostbyaddr(ip)
        print "name[%s]=%s" % (ip,name[0])
    except socket.error, erreur:
        print "name[%s]=%s" % (ip,erreur)
        return


# ---------------------------------------- principal

# constantes
HOTES=["istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","localhost","","xx"]

# endereços IP das máquinas de HOTES
for i in range(len(HOTES)):
    getIPandName(HOTES[i])
# fim
sys.exit()

Notas:

  • linha 1: as funções de rede do Python estão encapsuladas no módulo socket.

Resultados

1
2
3
4
5
6
7
8
9
ip[istia.univ-angers.fr]=193.49.146.171
name[193.49.146.171]=istia.istia.univ-angers.fr
ip[www.univ-angers.fr]=193.49.144.40
name[193.49.144.40]=ametys-fo.univ-angers.fr
ip[www.ibm.com]=129.42.58.216
name[129.42.58.216]=[Errno 11004] host not found
ip[localhost]=127.0.0.1
name[127.0.0.1]=Gportpers3.ad.univ-angers.fr
ip[xx]=[Errno 11004] getaddrinfo failed

11.2. Um cliente web

Um script que permite obter o conteúdo do parágrafo “index” de um site.


Programa (inet_02)


import sys,socket

  #-----------------------------------------------------------------------
def getIndex(site):
    # lê o site URL e o armazena no arquivo site.html
  
    # inicialmente, sem erros
    erreur=""
    # criação do arquivo site.html
    try:
        html=open("%s.html" % (site),"w")
    except IOError, erreur:
        pass
    # erro?
    if erreur:
        return "Erreur (%s) lors de la création du fichier %s.html" % (erreur,site)

    # abertura de uma conexão na porta 80 do site
    try:
        connexion=socket.create_connection((site,80))
    except socket.error, erreur:
        pass
    # retorno em caso de erro
    if erreur:
        return "Echec de la connexion au site (%s,80) : %s" % (site,erreur)

    # a conexão representa um fluxo de comunicação bidirecional
    # entre o cliente (este programa) e o servidor web acessado
    # esse canal é utilizado para a troca de comandos e informações
    # o protocolo de comunicação é HTTP

    # o cliente envia o comando get para solicitar o URL /
    # sintaxe get URL HTTP/1.0
    # os cabeçalhos (headers) do protocolo HTTP devem terminar com uma linha em branco
    connexion.send("GET / HTTP/1.0\n\n")

    # o servidor agora responderá no canal de conexão. Ele enviará todos
    # seus dados e, em seguida, fechará o canal. O cliente lê tudo o que chega pelo canal de conexão
    # até o fechamento do canal
    ligne=connexion.recv(1000)
    while(ligne):
        html.write(ligne)
        ligne=connexion.recv(1000)

    # o cliente, por sua vez, encerra a conexão
    connexion.close()
    # fechamento do arquivo HTML
    html.close()
    # retorno
    return "Transfert reussi du paragraphe index du site %s" % (site)

# --------------------------------------------- main    

# obter o texto HTML de URL

# lista de sites
SITES=("istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","xx")

# leitura das páginas de índice dos sites da tabela SITES
for i in range(len(SITES)):
      # leitura da página de índice do site SITES[i]
      resultat=getIndex(SITES[i])
      # exibição do resultado
      print resultat

# fim
sys.exit()

Notas:

  • linha 57: a lista de URLs dos sites cuja página inicial se deseja obter. Essa lista está armazenada no arquivo de texto [nomsite.html];
  • linha 62: a função getIndex realiza o trabalho;
  • linha 4: a função getIndex;
  • linha 20: o método create_connection((site,port)) permite criar uma conexão com um serviço TCP / IP operando na porta port da máquina site;
  • linha 35: o método send permite enviar dados por meio de uma conexão TCP / IP. Nesse caso, o que é enviado é um texto. Esse texto segue o protocolo HTTP (Protocolo de Transferência HyperText);
  • linha 40: o método recv permite receber dados por meio de uma conexão TCP / IP. Aqui, a resposta do servidor web é lida em blocos de 1.000 caracteres e gravada no arquivo de texto [nomsite.html].

Resultados

Transfert reussi du paragraphe index du site istia.univ-angers.fr
Transfert reussi du paragraphe index du site www.univ-angers.fr
Transfert reussi du paragraphe index du site www.ibm.com
Echec de la connexion au site (xx,80) : [Errno 11001] getaddrinfo failed

O arquivo recebido para o site [www.ibm.com]:

HTTP/1.1 302 Found
Date: Wed, 08 Jun 2011 15:43:56 GMT
Server: IBM_HTTP_Server
Content-Type: text/html
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Pragma: no-cache
Cache-Control: no-cache, must-revalidate
Location: http://www.ibm.com/us/en/
Content-Length: 209
Kp-eeAlive: timeout=10, max=14
Connection: Keep-Alive

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://www.ibm.com/us/en/">here</a>.</p>
</body></html>
  • as linhas 1 a 11 são os cabeçalhos HTTP da resposta do servidor;
  • linha 1: o servidor solicita que o cliente seja redirecionado para o endereço URL indicado na linha 8;
  • linha 2: data e hora da resposta;
  • linha 3: identidade do servidor web;
  • linha 4: conteúdo enviado pelo servidor. Aqui, uma página HTML que começa na linha 13;
  • linha 12: a linha vazia que encerra os cabeçalhos HTTP;
  • linhas 13-19: a página HTML enviada pelo servidor web.

11.3. Um cliente SMTP

Entre os protocolos TCP / IP, o SMTP (Protocolo de Transferência SendMail) é o protocolo de comunicação do serviço de envio de mensagens.


Programa (inet_03)


# -*- coding=utf-8 -*-

import sys,socket

#-----------------------------------------------------------------------
def getInfos(fichier):
    # retorna as informações (SMTP, remetente, destinatário, mensagem) extraídas do arquivo de texto [fichier]
    # linha 1: smtp, remetente, destinatário
    # linhas a seguir: o texto da mensagem

    # abertura de [fichier]
    erreur=""
    try:
        infos=open(fichier,"r")
    except IOError, erreur:
        return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
  
    # leitura da primeira linha
    ligne=infos.readline()
    # remoção do caractere de fim de linha
    ligne=cutNewLineChar(ligne)
    # recuperação dos campos smtp, remetente, destinatário
    champs=ligne.split(",")
    # o número de campos está correto?
    if len(champs)!=3 :
        return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
    # “processamento” das informações recuperadas
    # removemos de cada um dos três campos os “espaços em branco” que precedem ou seguem a informação útil
    for i in range(3):
        champs[i]=champs[i].strip()
    # recuperação dos campos
    (smtpServer,expediteur,destinataire)=champs
    message=""
    # leitura do restante da mensagem
    ligne=infos.readline()
    while ligne!='':
        message+=ligne
        ligne=infos.readline()
    infos.close()
    # retorno
    return ("",smtpServer,expediteur,destinataire,message)

#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
    # envia a mensagem ao servidor SMTP smtpserver em nome do remetente
    # para o destinatário. Se verbose=True, rastreia as trocas entre cliente e servidor

    # recupera-se o nome do cliente
    try:
        client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
    except socket.error, erreur:
        return "Erreur IP / Nom du client : %s" % (erreur)
    
    # abertura de uma conexão na porta 25 do smtpServer
    try:
        connexion=socket.create_connection((smtpServer,25))
    except socket.error, erreur:
        return "Echec de la connexion au site (%s,25) : %s" % (smtpServer,erreur)

    # a conexão representa um fluxo de comunicação bidirecional
    # entre o cliente (este programa) e o servidor SMTP contatado
    # esse canal é utilizado para a troca de comandos e informações

    # após a conexão, o servidor envia uma mensagem de boas-vindas que é lida
    erreur=sendCommand(connexion,"",verbose,1)
    if(erreur) :
        connexion.close()
        return erreur
    # comando ehlo:
    erreur=sendCommand(connexion,"EHLO %s" % (client),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # comando mail from:
    erreur=sendCommand(connexion,"MAIL FROM: <%s>" % (expediteur),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # comando rcpt to:
    erreur=sendCommand(connexion,"RCPT TO: <%s>" % (destinataire),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # comando data
    erreur=sendCommand(connexion,"DATA",verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # preparação da mensagem a ser enviada
    # ela deve conter as linhas
    # De: remetente
    # Para: destinatário
    # linha em branco
    # Mensagem
    # .
    data="From: %s\r\nTo: %s\r\n%s\r\n.\r\n" % (expediteur,destinataire,message)
    # envio da mensagem
    erreur=sendCommand(connexion,data,verbose,0)
    if erreur :
        connexion.close()
        return erreur
    # comando de saída
    erreur=sendCommand(connexion,"QUIT",verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # fim
    connexion.close()
    return "Message envoye"

# --------------------------------------------------------------------------
def sendCommand(connexion,commande,verbose,withRCLF):
    # envia comando no canal de conexão
    # modo detalhado se verbose=1
    # se withRCLF=1, adiciona a sequência RCLF ao comando

    # dados
    RCLF="\r\n" if withRCLF else ""
    # envio do comando se o comando não estiver vazio
    if commande:
        connexion.send("%s%s" % (commande,RCLF))
        # possível resposta
        if verbose:
            affiche(commande,1)
        # leitura da resposta com menos de 1.000 caracteres
        reponse=connexion.recv(1000)
        # eco, se houver
        if verbose:
            affiche(reponse,2)
        # recuperação do código de erro
        codeErreur=reponse[0:3]
        # erro retornado pelo servidor?
        if int(codeErreur) >=500:
            return reponse[4:]
        # retorno sem erro
        return ""

# --------------------------------------------------------------------------
def affiche(echange,sens):
    # exibe a troca na tela?
    # se sens=1 exibe -->troca
    # se sens=2, exibe <-- troca sem os dois últimos caracteres RCLF
    if sens==1:
        print "--> [%s]" % (echange)
        return
    elif sens==2:
        l=len(echange)
        print "<-- [%s]" % echange[0:l-2]
        return

      
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
    # remove-se o marcador de fim de linha de [ligne], se houver
    l=len(ligne)
    while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
        l-=1
    return(ligne[0:l])

# main ----------------------------------------------------------------
  
# cliente SMTP (Protocolo de Transferência SendMail) que permite enviar uma mensagem
# as informações são extraídas de um arquivo INFOS contendo as seguintes linhas
# linha 1: smtp, remetente, destinatário
# linhas seguintes: o texto da mensagem

# remetente: e-mail do remetente
# destinatário: e-mail do destinatário
# SMTP: nome do servidor SMTP a ser utilizado


# protocolo de comunicação SMTP cliente-servidor
# -> o cliente se conecta à porta 25 do servidor SMTP
# <- o servidor envia uma mensagem de boas-vindas
# -> o cliente envia o comando EHLO: nome do seu computador
# <- o servidor responde com OK ou não
# -> o cliente envia o comando mail from: <remetente>
# <- o servidor responde com OK ou não
# -> o cliente envia o comando rcpt to: <destinatário>
# <- o servidor responde com OK ou não
# -> o cliente envia o comando data
# <- o servidor responde com OK ou não
# -> o cliente envia todas as linhas de sua mensagem e termina com uma linha contendo apenas o caractere .
# <- o servidor responde com OK ou não
# -> o cliente envia o comando quit
# <- o servidor responde com OK ou não

# as respostas do servidor têm o formato xxx texto, em que xxx é um número de 3 dígitos. Qualquer número xxx >=500
# indica um erro. A resposta pode conter várias linhas, todas começando com xxx-, exceto a última
# no formato xxx(espaço)

# as linhas de texto trocadas devem terminar com os caracteres RC(#13) e LF(#10)

# # os parâmetros para o envio do e-mail
MAIL="mail2.txt" 
# recuperam-se os parâmetros do e-mail
res=getInfos(MAIL)
# erro?
if res[0]:
      print "%s" % (erreur)
      sys.exit()
# envio de e-mail no modo detalhado
(smtpServer,expediteur,destinataire,message)=res[1:]
print "Envoi du message [%s,%s,%s]" % (smtpServer,expediteur,destinataire)
resultat=sendmail(smtpServer,expediteur,destinataire,message,True)
print "Resultat de l'envoi : %s" % (resultat)
# fim
sys.exit()

Observações:

  • em um computador Windows com antivírus, este pode impedir que o script Python se conecte à porta 25 de um servidor SMTP. Nesse caso, é necessário desativar o antivírus. Para o McAfee, por exemplo, pode-se proceder da seguinte forma:
  • no [1], ativa-se o console VirusScan
  • em [2], desativa-se o serviço [Protection lors de l'accès]
  • em [3], ele está parado

Resultados

O arquivo infos.txt:

smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
Subject: test

ligne1
ligne2

ligne3

Resultados na tela:

Envoi du message [smtp.univ-angers.fr,serge.tahe@univ-angers.fr,serge.tahe@univ-angers.fr]
--> [EHLO Gportpers3.ad.univ-angers.fr]
<-- [220 smtp.univ-angers.fr ESMTP Postfix
250-smtp.univ-angers.fr
250-PIPELINING
250-SIZE 20480000
250-VRFY
250-ETRN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN]
--> [MAIL FROM: <serge.tahe@univ-angers.fr>]
<-- [250 2.1.0 Ok]
--> [RCPT TO: <serge.tahe@univ-angers.fr>]
<-- [250 2.1.5 Ok]
--> [DATA]
<-- [354 End data with <CR><LF>.<CR><LF>]
--> [From: serge.tahe@univ-angers.fr
To: serge.tahe@univ-angers.fr
Subject: test

ligne1
ligne2

ligne3

.
]
<-- [250 2.0.0 Ok: queued as 63412114203]
--> [QUIT]
<-- [221 2.0.0 Bye]
Resultat de l'envoi : Message envoye

A mensagem exibida pelo cliente de e-mail Thunderbird:

  

11.4. Um segundo cliente SMTP

Este segundo script faz o mesmo que o anterior, mas utilizando os recursos do módulo [smtplib].


Programa (inet_04)


# -*- coding=utf-8 -*-

import sys,socket, smtplib

#-----------------------------------------------------------------------
def getInfos(fichier):
    # retorna as informações (SMTP, remetente, destinatário, mensagem) extraídas do arquivo de texto [fichier]
    # linha 1: smtp, remetente, destinatário
    # linhas seguintes: o texto da mensagem

    # abertura do arquivo [fichier]
    erreur=""
    try:
        infos=open(fichier,"r")
    except IOError, erreur:
        return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
  
    # leitura da primeira linha
    ligne=infos.readline()
    # remoção do caractere de fim de linha
    ligne=cutNewLineChar(ligne)
    # recuperação dos campos smtp, remetente, destinatário
    champs=ligne.split(",")
    # o número de campos está correto?
    if len(champs)!=3 :
        return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
    # “processamento” das informações recuperadas — remove-se os “espaços” que as precedem ou seguem
    for i in range(3):
        champs[i]=champs[i].strip()
    # recuperação dos campos
    (smtpServer,expediteur,destinataire)=champs
    # leitura da mensagem a ser enviada
    message=""
    ligne=infos.readline()
    while ligne!='':
        message+=ligne
        ligne=infos.readline()
    infos.close()
    # retorno
    return ("",smtpServer,expediteur,destinataire,message)

#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
    # envia a mensagem ao servidor SMTP smtpserver em nome do remetente
    # para o destinatário. Se verbose=True, rastreia as trocas entre cliente e servidor

    # utiliza-se a biblioteca smtplib 
    try:
        server = smtplib.SMTP(smtpServer)
        if verbose:
            server.set_debuglevel(1)
            server.sendmail(expediteur, destinataire, message)
            server.quit()
    except Exception, erreur:
        return "Erreur envoi du message : %s" % (erreur)
    # fim
    return "Message envoye"
      
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
    # remove-se o marcador de fim de linha de [ligne], caso exista
    l=len(ligne)
    while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
        l-=1
    return(ligne[0:l])

# main ----------------------------------------------------------------
  
# cliente SMTP (Protocolo de Transferência SendMail) que permite enviar uma mensagem
# as informações são extraídas de um arquivo INFOS contendo as seguintes linhas
# linha 1: smtp, remetente, destinatário
# linhas seguintes: o texto da mensagem

# remetente: e-mail do remetente
# destinatário: e-mail do destinatário
# SMTP: nome do servidor SMTP a ser utilizado


# protocolo de comunicação SMTP cliente-servidor
# -> o cliente se conecta à porta 25 do servidor SMTP
# <- o servidor envia uma mensagem de boas-vindas
# -> o cliente envia o comando EHLO: nome do seu computador
# <- o servidor responde com OK ou não
# -> o cliente envia o comando mail from: <remetente>
# <- o servidor responde com OK ou não
# -> o cliente envia o comando rcpt to: <destinatário>
# <- o servidor responde com OK ou não
# -> o cliente envia o comando data
# <- o servidor responde com OK ou não
# -> o cliente envia todas as linhas de sua mensagem e termina com uma linha contendo apenas o caractere .
# <- o servidor responde com OK ou não
# -> o cliente envia o comando quit
# <- o servidor responde com OK ou não

# as respostas do servidor têm o formato xxx texto, em que xxx é um número de 3 dígitos. Qualquer número xxx >=500
# indica um erro. A resposta pode conter várias linhas, todas começando com xxx-, exceto a última
# no formato xxx(espaço)

# as linhas de texto trocadas devem terminar com os caracteres RC(#13) e LF(#10)

# # os parâmetros para o envio do e-mail
MAIL="mail2.txt" 
# recuperam-se os parâmetros do e-mail
res=getInfos(MAIL)
# erro?
if res[0]:
      print "%s" % (erreur)
      sys.exit()
# envio do e-mail no modo detalhado
(smtpServer,expediteur,destinataire,message)=res[1:]
print "Envoi du message [%s,%s,%s]" % (smtpServer,expediteur,destinataire)
resultat=sendmail(smtpServer,expediteur,destinataire,message,True)
print "Resultat de l'envoi : %s" % (resultat)
# fim
sys.exit()

Notas:

  • este script é idêntico ao anterior, com exceção da função sendmail. Essa função agora utiliza as funcionalidades do módulo [smtplib] (linha 3).

Resultados

O arquivo mail2.txt:

smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
From: serge.tahe@univ-angers.fr
To: serge.tahe@univ-angers.fr
Subject: test

ligne1
ligne2

ligne3

Resultados na tela:

Envoi du message [smtp.univ-angers.fr,serge.tahe@univ-angers.fr,serge.tahe@univ-
angers.fr]
send: 'ehlo Gportpers3.ad.univ-angers.fr\r\n'
reply: '250-smtp.univ-angers.fr\r\n'
reply: '250-PIPELINING\r\n'
reply: '250-SIZE 20480000\r\n'
reply: '250-VRFY\r\n'
reply: '250-ETRN\r\n'
reply: '250-ENHANCEDSTATUSCODES\r\n'
reply: '250-8BITMIME\r\n'
reply: '250 DSN\r\n'
reply: retcode (250); Msg: smtp.univ-angers.fr
PIPELINING
SIZE 20480000
VRFY
ETRN
ENHANCEDSTATUSCODES
8BITMIME
DSN
send: 'mail FROM:<serge.tahe@univ-angers.fr> size=99\r\n'
reply: '250 2.1.0 Ok\r\n'
reply: retcode (250); Msg: 2.1.0 Ok
send: 'rcpt TO:<serge.tahe@univ-angers.fr>\r\n'
reply: '250 2.1.5 Ok\r\n'
reply: retcode (250); Msg: 2.1.5 Ok
send: 'data\r\n'
reply: '354 End data with <CR><LF>.<CR><LF>\r\n'
reply: retcode (354); Msg: End data with <CR><LF>.<CR><LF>
data: (354, 'End data with <CR><LF>.<CR><LF>')
send: 'From: serge.tahe@univ-angers.fr\r\nTo: serge.tahe@univ-angers.fr\r\nSubje
ct: test\r\n\r\nligne1\r\nligne2\r\n\r\nligne3\r\n.\r\n'
reply: '250 2.0.0 Ok: queued as D4977114358\r\n'
reply: retcode (250); Msg: 2.0.0 Ok: queued as D4977114358
data: (250, '2.0.0 Ok: queued as D4977114358')
send: 'quit\r\n'
reply: '221 2.0.0 Bye\r\n'
reply: retcode (221); Msg: 2.0.0 Bye
Resultat de l'envoi : Message envoye

11.5. Cliente/servidor de eco

Cria-se um serviço de eco. O servidor retorna em letras maiúsculas todas as linhas de texto que o cliente lhe envia. O serviço pode atender a vários clientes ao mesmo tempo graças ao uso de threads.


O programa do servidor (inet_08)


# -*- coding=utf-8 -*-

# servidor TCP genérico no Windows

# carregamento dos arquivos de cabeçalho
import re,sys,SocketServer,threading

# servidor TCP multithread
class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):

  def handle(self):
    # thread atual
    cur_thread = threading.currentThread()
    # dados do cliente
    self.data="on"
    # parada em cadeia vazia
    while self.data:
      # as linhas de texto do cliente são lidas com o método readline
      self.data =self.rfile.readline().strip()
      # monitoramento do console
      print "client %s : %s (%s)" % (self.client_address[0],self.data,cur_thread.getName())
      # envio da resposta ao cliente
      response = "%s: %s" % (cur_thread.getName(), self.data.upper())
      # self.wfile é o fluxo de gravação para o cliente
      self.wfile.write(response)                    

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

# ------------------------------------------------------ main        
# sintaxe de chamada: argv[0] porta
# o servidor é iniciado na porta indicada

# Dados
syntaxe="syntaxe : %s port" % (sys.argv[0])

#------------------------------------- verifica-se a chamada
# deve haver um único argumento
nbArguments=len(sys.argv)
if nbArguments!=2:
    print syntaxe
    sys.exit(1)

# a porta deve ser numérica
port=sys.argv[1]
modele=r"^\s*\d+\s*$"
if not re.match(modele,port):
    print "Le port %s n'est pas un nombre entier positif\n" % (port)
    sys.exit(2)

# inicialização do servidor — atende cada cliente em uma thread
host="localhost"
server = ThreadedTCPServer((host,int(port)), ThreadedTCPRequestHandler)

# o servidor é iniciado em um thread
# cada cliente será atendido em um thread adicional
server_thread = threading.Thread(target=server.serve_forever)
# inicia o servidor — loop infinito de espera por clientes
server_thread.start()
# acompanhamento
print "Serveur d'echo a l'ecoute sur le port % s" % (port)

Notas:

  • linha 39: sys.argv representa os parâmetros do script. Aqui, eles devem ter o formato: nom_du_script porta. Portanto, devem ser dois. sys.argv[0] passará a ser nom_du_script e sys.argv[1] passará a ser port ;
  • linha 53: o servidor de eco é uma instância da classe ThreadedTcpServer. O construtor dessa classe espera dois parâmetros:
    • parâmetro 1: uma tupla de dois elementos (host, porta) que define a máquina e a porta de escuta do servidor;
    • parâmetro 2: o nome da classe responsável por processar as solicitações de um cliente.
  • linha 57: é criado um thread (mas ainda não iniciado). Esse thread executa o método [serve_forever] do servidor TCP. Esse método é um loop de escuta de conexões de clientes. Assim que uma conexão de cliente for detectada, será criada uma instância da classe ThreadedTCPRequestHandler. Seu método handle é responsável pelo diálogo com o cliente;
  • linha 59: a thread do serviço de eco é iniciada. A partir desse momento, os clientes podem se conectar ao serviço;
  • linha 27: a classe do servidor de eco. Ela deriva de duas classes: SocketServer.ThreadingMixIn e SocketServer.TCPServer. Isso faz dele um servidor TCP multithread: o servidor é executado em um thread e cada cliente é atendido em um thread adicional;
  • linha 9: a classe que processa as solicitações dos clientes. Ela deriva da classe SocketServer.StreamRequestHandler. Assim, ela herda dois atributos:
    • rfile: que é o fluxo de leitura dos dados enviados pelo cliente — pode ser tratado como um arquivo de texto;
    • wfile: que é o fluxo de gravação que permite enviar dados ao cliente — pode ser tratado como um arquivo de texto.
  • linha 11: o método handle processa as solicitações dos clientes;
  • linha 13: o thread que executa esse método;
  • linha 17: loop de processamento das solicitações do cliente. O loop termina quando o cliente envia uma linha vazia;
  • linha 19: leitura da solicitação do cliente;
  • linha 21: self.client_address[0] representa o endereço IP do cliente. cur_thread.getName() é o nome da thread que executa o método handle;
  • linha 23: a resposta ao cliente tem dois componentes — o nome da thread que atende o cliente e o comando que ele enviou, em letras maiúsculas.

O programa do cliente (inet_06)


# -*- coding=utf-8 -*-
import re,sys,socket

# ------------------------------------------------------ main        
# cliente TCP genérico
# sintaxe da chamada: argv[0] host porta
# o cliente se conecta ao serviço de eco (host, porta)
# o servidor retorna as linhas digitadas pelo cliente

# sintaxe
syntaxe="syntaxe : %s hote port" % (sys.argv[0])

#------------------------------------- verifica-se a chamada
# deve haver dois argumentos
nbArguments=len(sys.argv)
if nbArguments!=3:
    print syntaxe
    sys.exit(1)

# recuperam-se os argumentos
hote=sys.argv[1]
# a porta deve ser numérica
port=sys.argv[2]
modele=r"^\s*\d+\s*$"
if not re.match(modele,port):
    print "Le port %s foit être un nombre entier positif" % (port)
    sys.exit(2)

try:
  # conexão do cliente ao servidor
  connexion=socket.create_connection((hote,int(port)))
except socket.error, erreur:
  print "Echec de la connexion au site (%s,%s) : %s" % (hote,port,erreur)
  sys.exit(3)
  
try:
    # ciclo de entrada
    ligne=raw_input("Commande (rien pour arreter): ").strip()
    while ligne!="":
        # envia-se a linha para o servidor
        connexion.send("%s\n" % (ligne))
        # aguarda-se a resposta
        reponse=connexion.recv(1000)
        print "<-- %s" % (reponse)
        ligne=raw_input("Commande (rien pour arreter): ")
except socket.error, erreur:
    print "Echec de la connexion au site (%s,%s) : %s" % (hote,port,erreur)
    sys.exit(3)
finally:
    # encerramento da conexão
    connexion.close()

Os resultados

O servidor é iniciado em uma primeira janela de comandos:

cmd>%python% inet_08.py 100
Serveur d'echo a l'ecoute sur le port 100

Um primeiro cliente é iniciado em uma segunda janela:

1
2
3
4
5
6
7
cmd>%python% inet_06.py localhost 100
Commande (rien pour arreter): cmde 1 du client 1
[cmde 1 du client 1]
<-- Thread-2: CMDE 1 DU CLIENT 1
Commande (rien pour arreter): cmde 2 du client 1
<-- Thread-2: CMDE 2 DU CLIENT 1
Commande (rien pour arreter):

O cliente recebe, de fato, como resposta ao comando que envia ao servidor, esse mesmo comando em letras maiúsculas. Um segundo cliente é iniciado em uma terceira janela:

1
2
3
4
5
6
cmd>%python% inet_06.py localhost 100
Commande (rien pour arreter): cmde 1 du client 2
<-- Thread-3: CMDE 1 DU CLIENT 2
Commande (rien pour arreter): cmde 2 du client 2
<-- Thread-3: CMDE 2 DU CLIENT 2
Commande (rien pour arreter):

O console do servidor fica então assim:

1
2
3
4
5
6
cmd>%python% inet_08.py 100
Serveur d'echo a l'ecoute sur le port 100
client 127.0.0.1 : cmde 1 du client 1 (Thread-2)
client 127.0.0.1 : cmde 2 du client 1 (Thread-2)
client 127.0.0.1 : cmde 1 du client 2 (Thread-3)
client 127.0.0.1 : cmde 2 du client 2 (Thread-3)

Os clientes são atendidos em threads diferentes. Para encerrar um cliente, basta digitar um comando vazio.

11.6. Servidor TCP genérico

Propomos escrever um script em Python que

  • seria um servidor TCP capaz de atender um cliente por vez,
  • aceite linhas de texto enviadas pelo cliente,
  • aceite linhas de texto provenientes do teclado e que sejam enviadas em resposta ao cliente.

Assim, é o usuário no teclado que atua como servidor:

  • ele vê em seu terminal as linhas de texto enviadas pelo cliente;
  • ele responde ao cliente digitando a resposta no teclado.

Dessa forma, ele pode se adaptar a qualquer tipo de cliente. É por isso que o chamaremos de “servidor TCP genérico”. Trata-se de uma ferramenta prática para explorar protocolos de comunicação TCP. No exemplo a seguir, o cliente TCP será um navegador da web, o que nos permitirá explorar o protocolo HTTP utilizado pelos clientes da web.


O programa (inet_10)


# -*- coding=utf-8 -*-

# servidor TCP genérico

# carregamento dos arquivos de cabeçalho
import re,sys,SocketServer,threading

# servidor TCP genérico
class MyTCPHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        # exibindo o cliente
        print "client %s" % (self.client_address[0])
        # criação de um thread para leitura dos comandos do cliente
        thread_lecture = threading.Thread(target=self.lecture)
        thread_lecture.start()
        # encerramento ao receber o comando 'bye'
        # leitura do comando digitado no teclado
        cmde=raw_input("--> ")
        while cmde!="bye":
            # envio do comando ao cliente. self.wfile é o fluxo de gravação para o cliente
            self.wfile.write("%s\n" % (cmde))
            # leitura do próximo comando
            cmde=raw_input("--> ")

    def lecture(self):
        # são exibidas todas as linhas enviadas pelo cliente até receber o comando “bye”
        ligne=""
        while ligne!="bye":
            # as linhas de texto do cliente são lidas com o método readline
            ligne = self.rfile.readline().strip()
            # rastreamento do console
            print "<--- %s : %s" % (self.client_address[0], ligne)
    
# ------------------------------------------------------ main        
# sintaxe de chamada: argv[0] porta
# o servidor é iniciado na porta indicada

# Dados
syntaxe="syntaxe : %s port" % (sys.argv[0])

#------------------------------------- verifica-se a chamada
# deve haver um único argumento
nbArguments=len(sys.argv)
if nbArguments!=2:
    print syntaxe
    sys.exit(1)

# a porta deve ser numérica
port=sys.argv[1]
modele=r"^\s*\d+\s*$"
if not re.match(modele,port):
    print "Le port %s n'est pas un nombre entier positif\n" % (port)
    sys.exit(2)

# inicialização do servidor
host="localhost"
server = SocketServer.TCPServer((host, int(port)), MyTCPHandler)
print "Service tcp generique lance sur le port %s. Arret par Ctrl-C" % (port)
server.serve_forever()

Notas:

  • linha 57: o servidor TCP será uma instância da classe SocketServer.TCPServer. O construtor dessa classe aceita dois parâmetros:
    • o primeiro parâmetro é uma tupla de dois elementos (host, porta), em que host é a máquina na qual o serviço opera (geralmente localhost) e port, a porta na qual o serviço aguarda (escuta) as solicitações dos clientes;
    • o segundo parâmetro especifica a classe de serviço de um cliente. Quando um cliente se conecta, é criada uma instância da classe de serviço e é seu método handle que deve gerenciar a conexão com o cliente.

O servidor TCP SocketServer.TCPServer não é multithread. Ele atende um cliente por vez;

  • linha 59: o método serve_forever do servidor TCP é executado. Trata-se de um loop infinito de espera por clientes;
  • linha 9: o servidor TCP é, neste caso, uma classe derivada da classe SocketServer.StreamRequestHandler. Isso permite tratar os fluxos de dados trocados com o cliente como arquivos de texto. Já nos deparamos com essa classe. Temos à disposição os métodos:
    • readline para ler uma linha de texto proveniente do cliente;
    • write para enviar linhas de texto de volta ao cliente.
  • linha 12: self.client_address[0] é o endereço IP do cliente;
  • linha 14: o servidor TCP vai se comunicar com o cliente por meio de duas threads
    • um thread para ler as linhas do cliente;
    • um thread para gravar linhas para o cliente.
  • linha 14: a thread de leitura das linhas do cliente é criada. Seu parâmetro target define o método executado pela thread. É aquele definido na linha 25;
  • linha 25: a thread de leitura é iniciada;
  • linhas 25-32: o método executado pelo thread de leitura;
  • linha 28: o thread de leitura lê todas as linhas de texto enviadas pelo cliente até receber a linha “bye”;
  • linha 18: estamos aqui na thread de gravação para o cliente. O princípio é enviar ao cliente todas as linhas de texto digitadas pelo usuário no teclado.

Os resultados

O servidor

dos>%python% inet_10.py 100
Service tcp generique lance sur le port 100. Arret par Ctrl-C

O navegador do cliente

 

A solicitação recebida pelo servidor

dos>%python% inet_10.py 100
Service tcp generique lance sur le port 100. Arret par Ctrl-C
client 127.0.0.1
<--- 127.0.0.1 : GET / HTTP/1.1
<--> --- 127.0.0.1 : Host: localhost:100
<--- 127.0.0.1 : User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10 GTB7.1 ( .NET CLR 3.5.30729)
<--- 127.0.0.1 : Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0,8
<--- 127.0.0.1 : Accept-Language: fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3
<--- 127.0.0.1 : Accept-Encoding: gzip,deflate
<--- 127.0.0.1 : Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
<--- 127.0.0.1 : Keep-Alive: 115
<--- 127.0.0.1 : Connection: keep-alive
<--- 127.0.0.1 : Cache-Control: max-age=0
<--- 127.0.0.1 :

A resposta do servidor digitada pelo usuário no teclado (sem o sinal -->)

1
2
3
4
5
6
7
HTTP/1.1 200 OK
--> Server: serveur tcp generique
--> Connection: close
--> Content-Type: text/html
-->
--> <html><body><h2>Serveur tcp generique</h2></body></html>
--> bye
  • linhas 1-5: resposta HTTP enviada ao cliente;
  • linha 6: a página HTML enviada ao cliente;
  • linha 7: fim do diálogo com o cliente. O atendimento ao cliente será encerrado e a conexão será fechada, o que interromperá abruptamente o thread de leitura das linhas de texto enviadas pelo cliente;
  • as linhas 1-5 da resposta HTTP enviada ao cliente têm o seguinte significado:
  • linha 1: o recurso solicitado pelo cliente foi encontrado;
  • linha 2: identificação do servidor;
  • linha 3: o servidor encerrará a conexão após o envio do recurso;
  • linha 4: tipo do recurso enviado pelo servidor: um documento HTML;
  • linha 5: uma linha vazia.

A página exibida pelo navegador [1]:

Se exibirmos o código-fonte recebido pelo navegador [2], percebemos que o código HTML recebido pelo navegador é, de fato, aquele que foi enviado a ele.