Skip to content

11. Python-Netzwerkfunktionen

Wir werden nun die Netzwerkfunktionen von Python besprechen, die es uns ermöglichen, TCP/IP-Programmierung (Transmission Control Protocol/Internet Protocol) durchzuführen.

  

11.1. Den Namen oder die IP-Adresse eines Rechners im Internet ermitteln


Programm (inet_01)

import sys, socket

#------------------------------------------------
def getIPandName(nomMachine):
    # nomMachine: name of the machine whose address is required IP: name of the machine whose address is required IP: name of the machine whose address is required
    #    nomMachine-->adresse IP
    try:
        ip=socket.gethostbyname(nomMachine)
        print "ip[%s]=%s" % (nomMachine,ip)
    except socket.error, erreur:
        print "ip[%s]=%s" % (nomMachine,erreur)
        return

    #    address 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


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

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

#    IP addresses of HOTES machines
for i in range(len(HOTES)):
    getIPandName(HOTES[i])
#    end
sys.exit()

Anmerkungen:

  • Zeile 1: Die Netzwerkfunktionen von Python sind im Socket-Modul zusammengefasst.

Ergebnisse

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. Ein Web-Client

Ein Skript zum Abrufen des Inhalts der Indexseite einer Website.


Programm (inet_02)

import sys,socket

  #-----------------------------------------------------------------------
def getIndex(site):
    #  reads the URL site/ and stores it in the site.html file

    #  initially no error
    erreur=""
    #  creation of the site.html file
    try:
        html=open("%s.html" % (site),"w")
    except IOError, erreur:
        pass
    #    mistake?
    if erreur:
        return "Erreur (%s) lors de la création du fichier %s.html" % (erreur,site)

    #    open a connection on site port 80
    try:
        connexion=socket.create_connection((site,80))
    except socket.error, erreur:
        pass
    #  return if error
    if erreur:
        return "Echec de la connexion au site (%s,80) : %s" % (site,erreur)

    #    connection represents a bidirectional communication flow
    #  between the client (this program) and the contacted web server
    #  this channel is used for the exchange of orders and information
    #  the dialog protocol is HTTP

    #  the customer sends the get command to request URL /
    #    syntax get URL HTTP/1.0
    #  protocol HTTP headers must end with an empty line
    connexion.send("GET / HTTP/1.0\n\n")

    #  the server will now respond on the connection channel. It will send all
    #  then close the channel. The customer reads everything that comes in from the connection
    #  until the channel closes
    ligne=connexion.recv(1000)
    while(ligne):
        html.write(ligne)
        ligne=connexion.recv(1000)

    #  the customer in turn closes the connection
    connexion.close()
    #    close html file
    html.close()
    #    return
    return "Transfert reussi du paragraphe index du site %s" % (site)

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

#  get the text HTML from URL

#    list of websites
SITES=("istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","xx")

#  reading the index pages of the sites in the SITES table
for i in range(len(SITES)):
      #    read site index page SITES[i]
      resultat=getIndex(SITES[i])
      #    result display
      print resultat

#    end
sys.exit()

Anmerkungen:

  • Zeile 57: Die Liste der URLs für die Websites, deren Startseiten wir benötigen. Diese ist in der Textdatei [sitename.html] gespeichert;
  • Zeile 62: Die Funktion getIndex erledigt die Arbeit;
  • Zeile 4: die Funktion getIndex;
  • Zeile 20: Die Methode create_connection((site,port)) ermöglicht es Ihnen, eine Verbindung zu einem TCP/IP-Dienst herzustellen, der auf dem Port port auf dem Server der Website läuft;
  • Zeile 35: Die Methode send ermöglicht das Senden von Daten über eine TCP/IP-Verbindung. Hier wird Text gesendet. Dieser Text folgt dem HTTP-Protokoll (HyperText Transfer Protocol);
  • Zeile 40: Die Methode recv wird verwendet, um Daten über eine TCP/IP-Verbindung zu empfangen. Hier wird die Antwort des Webservers in Blöcken von 1.000 Zeichen eingelesen und in der Textdatei [sitename.html] gespeichert.

Ergebnisse

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

Die für die Website [www.ibm.com] empfangene Datei:

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>
  • Die Zeilen 1–11 sind die HTTP-Header der Serverantwort;
  • Zeile 1: Der Server weist den Client an, zur in Zeile 8 angegebenen URL umzuleiten;
  • Zeile 2: Datum und Uhrzeit der Antwort;
  • Zeile 3: Identität des Webservers;
  • Zeile 4: vom Server gesendeter Inhalt. Hier eine HTML-Seite, die in Zeile 13 beginnt;
  • Zeile 12: die Leerzeile, die die HTTP-Header abschließt;
  • Zeilen 13–19: die vom Webserver gesendete HTML-Seite.

11.3. Ein SMTP-Client

Unter den TCP/IP-Protokollen ist SMTP (Simple Mail Transfer Protocol) das Kommunikationsprotokoll für den Nachrichtenübermittlungsdienst.


Programm (inet_03)

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

import sys,socket

#-----------------------------------------------------------------------
def getInfos(fichier):
    #  returns the information (smtp,sender,recipient,message) taken from the text file [file]
    #  line 1: smtp, sender, recipient
    #  next lines: message text

    #    open [file]
    erreur=""
    try:
        infos=open(fichier,"r")
    except IOError, erreur:
        return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))

    #  read the 1st line
    ligne=infos.readline()
    #  delete end-of-line mark
    ligne=cutNewLineChar(ligne)
    #  retrieving smtp, sender and recipient fields
    champs=ligne.split(",")
    #  do we have the right number of fields?
    if len(champs)!=3 :
        return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
    #  "processing" the recovered information
    #  we remove from each of the 3 fields the "blanks" that precede or follow the useful information
    for i in range(3):
        champs[i]=champs[i].strip()
    #  field recovery
    (smtpServer,expediteur,destinataire)=champs
    message=""
    #    read rest of message
    ligne=infos.readline()
    while ligne!='':
        message+=ligne
        ligne=infos.readline()
    infos.close()
    #    return
    return ("",smtpServer,expediteur,destinataire,message)

#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
    #  sends message to smtp server smtpserver from sender
    #    as recipient. If verbose=True, tracks client-server exchanges

    #  retrieve the customer's name
    try:
        client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
    except socket.error, erreur:
        return "Erreur IP / Nom du client : %s" % (erreur)

    #    open a connection on port 25 of smtpServer
    try:
        connexion=socket.create_connection((smtpServer,25))
    except socket.error, erreur:
        return "Echec de la connexion au site (%s,25) : %s" % (smtpServer,erreur)

    #    connection represents a bidirectional communication flow
    #  between the client (this program) and the smtp server contacted
    #  this channel is used for the exchange of orders and information

    #  after connection, the server sends a welcome message which is read as follows
    erreur=sendCommand(connexion,"",verbose,1)
    if(erreur) :
        connexion.close()
        return erreur
    #    cmde ehlo:
    erreur=sendCommand(connexion,"EHLO %s" % (client),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # cmde mail from:
    erreur=sendCommand(connexion,"MAIL FROM: <%s>" % (expediteur),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    #    cmde rcpt to:
    erreur=sendCommand(connexion,"RCPT TO: <%s>" % (destinataire),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    #    cmde data
    erreur=sendCommand(connexion,"DATA",verbose,1)
    if erreur :
        connexion.close()
        return erreur
    #    prepare message to send
    #  it must contain the lines
    # From: expéditeur
    #    To: recipient
    #  empty line
    #    Message
    # .
    data="From: %s\r\nTo: %s\r\n%s\r\n.\r\n" % (expediteur,destinataire,message)
    #    send message
    erreur=sendCommand(connexion,data,verbose,0)
    if erreur :
        connexion.close()
        return erreur
    #    cmde quit
    erreur=sendCommand(connexion,"QUIT",verbose,1)
    if erreur :
        connexion.close()
        return erreur
    #    end
    connexion.close()
    return "Message envoye"

# --------------------------------------------------------------------------
def sendCommand(connexion,commande,verbose,withRCLF):
    #  sends command to connection channel
    #    verbose mode if verbose=1
    #  if withRCLF=1, adds sequence RCLF to command

    #    data
    RCLF="\r\n" if withRCLF else ""
    #  send cmde if order not empty
    if commande:
        connexion.send("%s%s" % (commande,RCLF))
        #    possible echo
        if verbose:
            affiche(commande,1)
        #    read response of less than 1000 characters
        reponse=connexion.recv(1000)
        #    possible echo
        if verbose:
            affiche(reponse,2)
        #  error code recovery
        codeErreur=reponse[0:3]
        #  error returned by the server?
        if int(codeErreur) >=500:
            return reponse[4:]
        #  error-free return
        return ""

# --------------------------------------------------------------------------
def affiche(echange,sens):
    #    displays exchange ? screen
    #    if sens=1 displays -->change
    #    if sens=2 displays <-- exchange without last 2 characters RCLF
    if sens==1:
        print "--> [%s]" % (echange)
        return
    elif sens==2:
        l=len(echange)
        print "<-- [%s]" % echange[0:l-2]
        return


# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
    #  delete the [line] end-of-line mark if it exists
    l=len(ligne)
    while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
        l-=1
    return(ligne[0:l])

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

#  client SMTP (SendMail Transfer Protocol) for sending a message
#  information is taken from a INFOS file containing the following lines
#  line 1: smtp, sender, recipient
#  next lines: message text

#    sender:email sender
#    recipient: email recipient
#  smtp: name of smtp server to use


#    communication protocol SMTP client-server
#    -> client connects to smtp server port 25
#    <- server sends him a welcome message
#  -> customer sends command EHLO: machine name
#  <- server responds OK or not
#  -> customer sends mail from: <exp?diteur> command
#  <- server responds OK or not
#  -> client sends the rcpt to command: <recipient>
#  <- server responds OK or not
#    -> customer sends data order
#  <- server responds OK or not
#  -> client sends all the lines of its message and ends with a line containing the single character .
#  <- server responds OK or not
#    -> customer sends quit order
#  <- server responds OK or not

#  server responses have the form xxx text where xxx is a 3-digit number. Any number xxx >=500
#  indicates an error. The answer may consist of several lines all beginning with xxx- except the last one
#  of the form xxx(space)

#  exchanged text lines must end with RC(#13) and LF(#10) characters

#    # Mailing parameters
MAIL="mail2.txt" 
#    retrieve mail parameters
res=getInfos(MAIL)
#    mistake?
if res[0]:
      print "%s" % (erreur)
      sys.exit()
#  sending mail in verbose mode
(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)
#    end
sys.exit()

Hinweise:

  • Auf einem Windows-Rechner mit Antivirensoftware kann das Antivirenprogramm verhindern, dass das Python-Skript eine Verbindung zu Port 25 eines SMTP-Servers herstellt. Sie müssen daher das Antivirenprogramm deaktivieren. Bei McAfee können Sie beispielsweise Folgendes tun:
  • Öffnen Sie in [1] die VirusScan-Konsole
  • Beenden Sie in [2] den Dienst [On-Access Protection]
  • in [3] ist er angehalten

Ergebnisse

Die Datei „infos.txt“:

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

ligne1
ligne2

ligne3

Anzeige der Ergebnisse:

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

Die Nachricht, wie sie im E-Mail-Client Thunderbird angezeigt wird:

  

11.4. Ein zweiter SMTP-Client

Dieses zweite Skript macht dasselbe wie das vorherige, nutzt jedoch die Funktionen des [smtplib]-Moduls.


Programm (inet_04)

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

import sys,socket, smtplib

#-----------------------------------------------------------------------
def getInfos(fichier):
    #  returns the information (smtp,sender,recipient,message) taken from the text file [file]
    #  line 1: smtp, sender, recipient
    #  next lines: message text

    #    open [file]
    erreur=""
    try:
        infos=open(fichier,"r")
    except IOError, erreur:
        return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))

    #  read the 1st line
    ligne=infos.readline()
    #  delete end-of-line mark
    ligne=cutNewLineChar(ligne)
    #  retrieve smtp, sender, recipient fields
    champs=ligne.split(",")
    #  do we have the right number of fields?
    if len(champs)!=3 :
        return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
    #  "processing" the information recovered - removing the "blanks" that precede or follow them
    for i in range(3):
        champs[i]=champs[i].strip()
    #  field recovery
    (smtpServer,expediteur,destinataire)=champs
    #    read message to send
    message=""
    ligne=infos.readline()
    while ligne!='':
        message+=ligne
        ligne=infos.readline()
    infos.close()
    #    return
    return ("",smtpServer,expediteur,destinataire,message)

#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
    #  sends message to smtp server smtpserver from sender
    #    as recipient. If verbose=True, tracks client-server exchanges

    #  we use the smtplib library 
    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)
    #    end
    return "Message envoye"

# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
    #  delete the [line] end-of-line mark if it exists
    l=len(ligne)
    while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
        l-=1
    return(ligne[0:l])

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

#  client SMTP (SendMail Transfer Protocol) for sending a message
#  information is taken from a INFOS file containing the following lines
#  line 1: smtp, sender, recipient
#  next lines: message text

#    sender:email sender
#    recipient: email recipient
#  smtp: name of smtp server to use


#    communication protocol SMTP client-server
#    -> client connects to smtp server port 25
#    <- server sends him a welcome message
#  -> customer sends command EHLO: machine name
#  <- server responds OK or not
#  -> customer sends mail order from: <sender>
#  <- server responds OK or not
#  -> client sends the rcpt to command: <recipient>
#  <- server responds OK or not
#    -> customer sends data order
#  <- server responds OK or not
#  -> client sends all the lines of its message and ends with a line containing the single character .
#  <- server responds OK or not
#    -> customer sends quit order
#  <- server responds OK or not

#  server responses have the form xxx text where xxx is a 3-digit number. Any number xxx >=500
#  indicates an error. The answer may consist of several lines all beginning with xxx- except the last one
#  of the form xxx(space)

#  exchanged text lines must end with RC(#13) and LF(#10) characters

#    # Mailing parameters
MAIL="mail2.txt" 
#    retrieve mail parameters
res=getInfos(MAIL)
#    mistake?
if res[0]:
      print "%s" % (erreur)
      sys.exit()
#  sending mail in verbose mode
(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)
#    end
sys.exit()

Anmerkungen:

  • Dieses Skript ist bis auf die sendmail-Funktion identisch mit dem vorherigen. Diese Funktion nutzt nun die Funktionen des Moduls [smtplib] (Zeile 3).

Ergebnisse

Die Datei 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

Anzeige der Ergebnisse:

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. Echo Client/Server

Wir erstellen einen Echo-Dienst. Der Server gibt alle vom Client gesendeten Textzeilen in Großbuchstaben zurück. Der Dienst kann mithilfe von Threads mehrere Clients gleichzeitig bedienen.


Das Serverprogramm (inet_08)

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

#    generic tcp server on Windows

#    loading header files
import re,sys,SocketServer,threading

#    multi-threaded tcp server
class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):

  def handle(self):
    #    current thread
    cur_thread = threading.currentThread()
    #    customer data
    self.data="on"
    #    stop on empty chain
    while self.data:
      #  the client's text lines are read using the readline method
      self.data =self.rfile.readline().strip()
      #    console monitoring
      print "client %s : %s (%s)" % (self.client_address[0],self.data,cur_thread.getName())
      #    reply to customer
      response = "%s: %s" % (cur_thread.getName(), self.data.upper())
      #  self.wfile is the write stream to client
      self.wfile.write(response)                    

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

#    ------------------------------------------------------ main        
#    call syntax: argv[0] port
#  the server is launched on the port named

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

# ------------------------------------- check the call
#  there must be one argument and one argument alone
nbArguments=len(sys.argv)
if nbArguments!=2:
    print syntaxe
    sys.exit(1)

#  the port must be digital
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)

#    server launch - serves each client on a thread
host="localhost"
server = ThreadedTCPServer((host,int(port)), ThreadedTCPRequestHandler)

#  the server is launched in a thread
#  each customer will be served in an additional thread
server_thread = threading.Thread(target=server.serve_forever)
#  starts the server - infinite waiting loop for clients
server_thread.start()
#    follow-up
print "Serveur d'echo a l'ecoute sur le port % s" % (port)

Hinweise:

  • Zeile 39: sys.argv steht für die Argumente des Skripts. Diese müssen hier die Form „Skriptname Port“ haben. Es müssen also zwei Argumente vorhanden sein. sys.argv[0] ist dann „Skriptname“ und sys.argv[1] ist „Port“;
  • Zeile 53: Der Echo-Server ist eine Instanz der Klasse *ThreadedTcpServer*. Der Konstruktor dieser Klasse erwartet zwei Parameter:
    • Parameter 1: ein Tupel mit zwei Elementen (host,port), das den Rechner und den Listening-Port des Servers angibt;
    • Parameter 2: der Name der Klasse, die für die Bearbeitung von Client-Anfragen zuständig ist.
  • Zeile 57: Ein Thread wird erstellt (aber noch nicht gestartet). Dieser Thread führt die Methode [serve_forever] des TCP-Servers aus. Diese Methode ist eine Schleife, die auf Client-Verbindungen wartet. Sobald eine Client-Verbindung erkannt wird, wird eine Instanz der Klasse ThreadedTCPRequestHandler erstellt. Deren Methode handle ist für die Kommunikation mit dem Client zuständig;
  • Zeile 59: Der Echo-Service-Thread wird gestartet. Ab diesem Zeitpunkt können sich Clients mit dem Service verbinden;
  • Zeile 27: Die Echo-Server-Klasse. Sie leitet sich von zwei Klassen ab: *SocketServer.ThreadingMixIn* und SocketServer.TCPServer. Dadurch wird sie zu einem multithreaded TCP-Server: Der Server läuft in einem Thread, und jeder Client wird in einem zusätzlichen Thread bedient;
  • Zeile 9: Die Klasse, die Client-Anfragen verarbeitet. Sie leitet sich von der Klasse *SocketServer.StreamRequestHandler* ab. Damit erbt sie zwei Attribute:
    • rfile: Dies ist der Lesestrom für vom Client gesendete Daten – kann als Textdatei behandelt werden;
    • wfile: Dies ist der Schreibstrom, der zum Senden von Daten an den Client verwendet wird – kann als Textdatei behandelt werden.
  • Zeile 11: Die Handle-Methode verarbeitet Client-Anfragen;
  • Zeile 13: Der Thread, der diese „handle“-Methode ausführt;
  • Zeile 17: Schleife zur Verarbeitung von Client-Anfragen. Die Schleife endet, wenn der Client eine Leerzeile sendet;
  • Zeile 19: Liest die Client-Anfrage;
  • Zeile 21: self.client_address[0] steht für die IP-Adresse des Clients. cur_thread.getName() ist der Name des Threads, der die handle-Methode ausführt;
  • Zeile 23: Die Antwort an den Client besteht aus zwei Komponenten – dem Namen des Threads, der den Client bedient, und dem vom Client gesendeten Befehl, umgewandelt in Großbuchstaben.

Das Client-Programm (inet_06)

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

#    ------------------------------------------------------ main        
#    generic tcp client
#    call syntax: argv[0] host port
#    client connects to echo service (host,port)
#  the server returns the lines typed by the client

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

# ------------------------------------- check the call
#  there must be two arguments
nbArguments=len(sys.argv)
if nbArguments!=3:
    print syntaxe
    sys.exit(1)

#  we recover the arguments
hote=sys.argv[1]
#  the port must be digital
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:
  #    client connection to server
  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:
    #    input loop
    ligne=raw_input("Commande (rien pour arreter): ").strip()
    while ligne!="":
        #  send the line to the server
        connexion.send("%s\n" % (ligne))
        #  we're waiting for the answer
        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:
    #  close the connection
    connexion.close()

Ergebnisse

Der Server wird in einem Befehlsfenster gestartet:

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

Ein erster Client wird in einem zweiten Fenster gestartet:

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

Der Client erhält als Antwort auf den Befehl, den er an den Server sendet, denselben Befehl in Großbuchstaben. Ein zweiter Client wird in einem dritten Fenster gestartet:

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

Die Serverkonsole sieht dann wie folgt aus:

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)

Clients werden ordnungsgemäß in verschiedenen Threads bedient. Um einen Client zu beenden, geben Sie einfach einen leeren Befehl ein.

11.6. Generischer TCP-Server

Wir schlagen vor, ein Python-Skript zu schreiben, das

  • als TCP-Server fungiert, der jeweils einen Client bedienen kann und
  • vom Client gesendete Textzeilen akzeptiert,
  • Textzeilen von der Tastatur entgegennimmt und diese an den Client zurücksendet.

Somit fungiert der Benutzer an der Tastatur als Server:

  • er sieht die vom Client gesendeten Textzeilen auf seiner Konsole;
  • er antwortet dem Client, indem er die Antwort über die Tastatur eingibt.

Es kann sich somit an jede Art von Client anpassen. Deshalb nennen wir es einen „generischen TCP-Server“. Es ist ein praktisches Werkzeug, um TCP-Kommunikationsprotokolle zu erkunden. Im folgenden Beispiel ist der TCP-Client ein Webbrowser, was es uns ermöglicht, das von Web-Clients verwendete HTTP-Protokoll zu erkunden.


Das Programm (inet_10)

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

#    generic tcp server

#    loading header files
import re,sys,SocketServer,threading

#    generic tcp server
class MyTCPHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        #  the customer is displayed
        print "client %s" % (self.client_address[0])
        #  create a thread for reading customer orders
        thread_lecture = threading.Thread(target=self.lecture)
        thread_lecture.start()
        #    stop on cmde 'bye
        #    cmde reading typed on keyboard
        cmde=raw_input("--> ")
        while cmde!="bye":
            #  send cmde to customer. self.wfile is the write flow to the customer
            self.wfile.write("%s\n" % (cmde))
            #    next cmde reading
            cmde=raw_input("--> ")

    def lecture(self):
        #  displays all lines sent by the client until the bye command is received
        ligne=""
        while ligne!="bye":
            #  the client's text lines are read using the readline method
            ligne = self.rfile.readline().strip()
            #    console monitoring
            print "<--- %s : %s" % (self.client_address[0], ligne)

#    ------------------------------------------------------ main        
#    call syntax: argv[0] port
#  the server is launched on the port named

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

# ------------------------------------- check the call
#  there must be one argument and one argument alone
nbArguments=len(sys.argv)
if nbArguments!=2:
    print syntaxe
    sys.exit(1)

#  the port must be digital
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)

#    server launch
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()

Hinweise:

  • Zeile 57: Der TCP-Server ist eine Instanz der Klasse SocketServer.TCPServer. Sein Konstruktor akzeptiert zwei Parameter:
    • Der erste Parameter ist ein Tupel mit zwei Elementen (host, port), wobei „host“ den Rechner bezeichnet, auf dem der Dienst läuft (in der Regel „localhost“), und „port“ den Port angibt, auf dem der Dienst auf Client-Anfragen wartet (lauscht);
    • Der zweite Parameter gibt die Dienstklasse des Clients an. Wenn ein Client eine Verbindung herstellt, wird eine Instanz der Dienstklasse erstellt, und deren handle-Methode muss die Verbindung mit dem Client verwalten.

Der Tcp SocketServer.TCPServer ist nicht multithreaded. Er bedient jeweils nur einen Client;

  • Zeile 59: Die Methode serve\_forever des Tcp-Servers wird ausgeführt. Dies ist eine Endlosschleife, die auf Clients wartet;
  • Zeile 9: Der TCP-Server ist hier eine von der Klasse *SocketServer.StreamRequestHandler* abgeleitete Klasse. Dadurch können die mit dem Client ausgetauschten Datenströme als Textdateien behandelt werden. Wir sind dieser Klasse bereits begegnet. Wir verfügen über die folgenden Methoden:
    • *readline*, um eine Textzeile vom Client zu lesen;
    • write, um Textzeilen an den Client zurückzusenden.
  • Zeile 12: *self.client\_address[0]* ist die IP-Adresse des Clients;
  • Zeile 14: Der TCP-Server kommuniziert mit dem Client über zwei Threads
    • ein Thread zum Lesen von Zeilen vom Client;
    • ein Thread zum Schreiben von Zeilen an den Client.
  • Zeile 14: Der Thread zum Lesen von Zeilen vom Client wird erstellt. Sein Parameter target gibt die vom Thread ausgeführte Methode an. Dies ist die in Zeile 25 definierte Methode;
  • Zeile 25: Der Lese-Thread wird gestartet;
  • Zeilen 25–32: Die vom Lese-Thread ausgeführte Methode;
  • Zeile 28: Der Lese-Thread liest alle vom Client gesendeten Textzeilen, bis die Zeile „bye“ empfangen wird;
  • Zeile 18: Wir befinden uns nun in dem Thread, der an den Client schreibt. Die Idee ist, dem Client alle Textzeilen zu senden, die der Benutzer über die Tastatur eingibt.

Die Ergebnisse

Der Server

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

Der Client-Browser

 

Die vom Server empfangene Anfrage

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 :

Die vom Benutzer über die Tastatur eingegebene Antwort des Servers (ohne das Zeichen -->)

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
  • Zeilen 1–5: An den Client gesendete HTTP-Antwort;
  • Zeile 6: An den Client gesendete HTML-Seite;
  • Zeile 7: Ende des Dialogs mit dem Client. Der Client-Dienst wird beendet und die Verbindung geschlossen, wodurch der Thread, der die vom Client gesendeten Textzeilen liest, abrupt unterbrochen wird;
  • Die Zeilen 1–5 der an den Client gesendeten HTTP-Antwort haben folgende Bedeutung:
  • Zeile 1: Die vom Client angeforderte Ressource wurde gefunden;
  • Zeile 2: Serveridentifikation;
  • Zeile 3: Der Server wird die Verbindung nach dem Senden der Ressource schließen;
  • Zeile 4: Art der vom Server gesendeten Ressource: ein HTML-Dokument;
  • Zeile 5: eine Leerzeile.

Die vom Browser angezeigte Seite [1]:

Wenn wir uns den vom Browser empfangenen Quellcode [2] ansehen, können wir feststellen, dass der vom Webbrowser empfangene HTML-Code tatsächlich derjenige ist, den wir an ihn gesendet haben.