11. De netwerkfuncties van Python
We gaan nu in op de netwerkfuncties van Python waarmee we kunnen programmeren met TCP / IP (Transfer Control Protocol / Internet Protocol).
![]() |
11.1. De naam of het adres IP van een computer op het internet opvragen
import sys, socket
#------------------------------------------------
def getIPandName(nomMachine):
#nomMachine: naam van de machine waarvan we het adres willen weten IP
# nomMachine-->adres IP
try:
ip=socket.gethostbyname(nomMachine)
print "ip[%s]=%s" % (nomMachine,ip)
except socket.error, erreur:
print "ip[%s]=%s" % (nomMachine,erreur)
return
# adres 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
# ---------------------------------------- hoofd
# constanten
HOTES=["istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","localhost","","xx"]
# adressen IP van de machines van HOTES
for i in range(len(HOTES)):
getIPandName(HOTES[i])
# einde
sys.exit()
Opmerkingen:
- regel 1: de netwerkfuncties van Python zijn ondergebracht in de module socket.
11.2. Een webclient
Een script waarmee de inhoud van de indexparagraaf van een website kan worden opgehaald.
import sys,socket
#-----------------------------------------------------------------------
def getIndex(site):
# leest de website URL en slaat deze op in het bestand site.html
# in eerste instantie geen fout
erreur=""
# het bestand site.html wordt aangemaakt
try:
html=open("%s.html" % (site),"w")
except IOError, erreur:
pass
# fout?
if erreur:
return "Erreur (%s) lors de la création du fichier %s.html" % (erreur,site)
# verbinding tot stand gebracht op poort 80 van de website
try:
connexion=socket.create_connection((site,80))
except socket.error, erreur:
pass
# terugkeren bij fout
if erreur:
return "Echec de la connexion au site (%s,80) : %s" % (site,erreur)
# verbinding vormt een bidirectionele communicatiestroom
# tussen de client (dit programma) en de benaderde webserver
# dit kanaal wordt gebruikt voor de uitwisseling van opdrachten en informatie
# het communicatieprotocol is HTTP
# de client verstuurt het get-commando om de URL op te vragen /
# 'get'-syntaxis: URL HTTP/1.0
# de headers van het protocol HTTP moeten eindigen met een lege regel
connexion.send("GET / HTTP/1.0\n\n")
# de server zal nu reageren via het verbindingskanaal. Hij zal alle
# zijn gegevens verzenden en vervolgens het kanaal sluiten. De client leest alles wat binnenkomt via het verbindingskanaal
# totdat het kanaal wordt gesloten
ligne=connexion.recv(1000)
while(ligne):
html.write(ligne)
ligne=connexion.recv(1000)
# sluit de client op zijn beurt de verbinding
connexion.close()
# het html-bestand wordt gesloten
html.close()
# terug
return "Transfert reussi du paragraphe index du site %s" % (site)
# --------------------------------------------- main
# de tekst HTML ophalen uit URL
# lijst met websites
SITES=("istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","xx")
# de indexpagina's van de websites uit de tabel SITES lezen
for i in range(len(SITES)):
# de indexpagina van de website SITES[i] lezen
resultat=getIndex(SITES[i])
# weergave van het resultaat
print resultat
# einde
sys.exit()
Opmerkingen:
- regel 57: de lijst met URL's van de websites waarvan de indexpagina wordt opgevraagd. Deze lijst is opgeslagen in het tekstbestand [nomsite.html];
- regel 62: de functie getIndex voert de taak uit;
- regel 4: de functie getIndex;
- regel 20: met de methode create_connection((site,port)) kan een verbinding worden gemaakt met een dienst TCP / IP die draait op poort port van de machine site;
- regel 35: met de methode send kunnen gegevens worden verzonden via een verbinding TCP / IP. Hier wordt tekst verzonden. Deze tekst volgt het protocol HTTP (HyperText Transfer Protocol);
- regel 40: met de methode recv kunnen gegevens worden ontvangen via een verbinding TCP / IP. Hier wordt het antwoord van de webserver in blokken van 1000 tekens gelezen en opgeslagen in het tekstbestand [nomsite.html].
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
Het ontvangen bestand voor de website [www.ibm.com]:
- regels 1-11 zijn de HTTP-headers van het antwoord van de server;
- regel 1: de server vraagt de client om door te sturen naar de in regel 8 aangegeven URL;
- regel 2: datum en tijdstip van het antwoord;
- regel 3: identiteit van de webserver;
- regel 4: door de server verzonden inhoud. Hier een pagina HTML die begint op regel 13;
- regel 12: de lege regel die de headers HTTP afsluit;
- regels 13-19: de pagina HTML die door de webserver is verzonden.
11.3. Een SMTP-client
Van de protocollen TCP / IP is SMTP (SendMail Transfer Protocol) het communicatieprotocol voor de berichtverzendservice.
# -*- coding=utf-8 -*-
import sys,socket
#-----------------------------------------------------------------------
def getInfos(fichier):
# geeft de informatie (smtp, afzender, ontvanger, bericht) weer die uit het tekstbestand [fichier] is gehaald
# regel 1: smtp, afzender, ontvanger
# volgende regels: de tekst van het bericht
# openen van [fichier]
erreur=""
try:
infos=open(fichier,"r")
except IOError, erreur:
return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
# lezen van de eerste regel
ligne=infos.readline()
# verwijdering van het einde-van-regel-teken
ligne=cutNewLineChar(ligne)
# ophalen van de velden smtp, afzender, ontvanger
champs=ligne.split(",")
# is het aantal velden correct?
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# "verwerking" van de opgehaalde informatie
# uit elk van de 3 velden worden de "spaties" verwijderd die voor of na de nuttige informatie staan
for i in range(3):
champs[i]=champs[i].strip()
# ophalen van de velden
(smtpServer,expediteur,destinataire)=champs
message=""
# lezen van de rest van het bericht
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# terug
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# bericht verzenden naar de SMTP-server smtpserver namens de afzender
# voor de ontvanger. Als verbose=True, wordt de communicatie tussen client en server bijgehouden
# de naam van de client wordt opgehaald
try:
client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
except socket.error, erreur:
return "Erreur IP / Nom du client : %s" % (erreur)
# een verbinding wordt geopend op poort 25 van smtpServer
try:
connexion=socket.create_connection((smtpServer,25))
except socket.error, erreur:
return "Echec de la connexion au site (%s,25) : %s" % (smtpServer,erreur)
# de verbinding vormt een bidirectionele communicatiestroom
# tussen de client (dit programma) en de benaderde SMTP-server
# dit kanaal wordt gebruikt voor de uitwisseling van opdrachten en informatie
# na het tot stand brengen van de verbinding stuurt de server een welkomstbericht dat wordt gelezen
erreur=sendCommand(connexion,"",verbose,1)
if(erreur) :
connexion.close()
return erreur
# commando ehlo:
erreur=sendCommand(connexion,"EHLO %s" % (client),verbose,1)
if erreur :
connexion.close()
return erreur
# commando mail from:
erreur=sendCommand(connexion,"MAIL FROM: <%s>" % (expediteur),verbose,1)
if erreur :
connexion.close()
return erreur
# commando rcpt to:
erreur=sendCommand(connexion,"RCPT TO: <%s>" % (destinataire),verbose,1)
if erreur :
connexion.close()
return erreur
# commando data
erreur=sendCommand(connexion,"DATA",verbose,1)
if erreur :
connexion.close()
return erreur
# voorbereiding van het te verzenden bericht
# het moet de volgende regels bevatten
# From: afzender
# To: ontvanger
# lege regel
# Bericht
# .
data="From: %s\r\nTo: %s\r\n%s\r\n.\r\n" % (expediteur,destinataire,message)
# bericht verzenden
erreur=sendCommand(connexion,data,verbose,0)
if erreur :
connexion.close()
return erreur
# commando afsluiten
erreur=sendCommand(connexion,"QUIT",verbose,1)
if erreur :
connexion.close()
return erreur
# einde
connexion.close()
return "Message envoye"
# --------------------------------------------------------------------------
def sendCommand(connexion,commande,verbose,withRCLF):
# commando verzenden naar het verbindingskanaal
# uitgebreide modus als verbose=1
# als withRCLF=1, voeg de reeks RCLF toe aan het commando
# gegevens
RCLF="\r\n" if withRCLF else ""
# commando verzenden als commando niet leeg is
if commande:
connexion.send("%s%s" % (commande,RCLF))
# eventuele echo
if verbose:
affiche(commande,1)
# antwoord met minder dan 1000 tekens lezen
reponse=connexion.recv(1000)
# eventuele echo
if verbose:
affiche(reponse,2)
# ophalen van foutcode
codeErreur=reponse[0:3]
# fout teruggestuurd door de server?
if int(codeErreur) >=500:
return reponse[4:]
# terugkeer zonder fout
return ""
# --------------------------------------------------------------------------
def affiche(echange,sens):
# uitwisseling weergeven op het scherm?
# als sens=1, toon -->uitwisseling
# als sens=2, toon <-- uitwisseling zonder de laatste 2 tekens RCLF
if sens==1:
print "--> [%s]" % (echange)
return
elif sens==2:
l=len(echange)
print "<-- [%s]" % echange[0:l-2]
return
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# verwijder het einde-van-regel-teken uit [ligne] indien aanwezig
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) waarmee een bericht kan worden verzonden
# de gegevens worden uit een bestand gehaald INFOS met de volgende regels
# regel 1: smtp, afzender, ontvanger
# volgende regels: de tekst van het bericht
# afzender: e-mailadres van de afzender
# ontvanger: e-mailadres ontvanger
# smtp: naam van de te gebruiken smtp-server
# communicatieprotocol SMTP client-server
# -> client maakt verbinding via poort 25 van de SMTP-server
# <- de server stuurt hem een welkomstbericht
# -> de client stuurt het commando EHLO: de naam van zijn computer
# <- de server antwoordt met OK of niet
# -> de client verstuurt het commando mail from: <afzender>
# <- server antwoordt met OK of niet
# -> de client verstuurt het commando rcpt to: <ontvanger>
# <- server antwoordt met OK of niet
# -> de client verstuurt het commando data
# <- server antwoordt met OK of niet
# -> de client verstuurt alle regels van zijn bericht en sluit af met een regel die alleen het teken bevat.
# <- server antwoordt met OK of niet
# -> de client verstuurt het commando ‘quit’
# <- de server antwoordt met OK of niet
# de antwoorden van de server hebben de vorm xxx tekst, waarbij xxx een getal van 3 cijfers is. Elk getal xxx >=500
# duidt op een fout. Het antwoord kan uit meerdere regels bestaan die allemaal beginnen met xxx-, behalve de laatste
# in de vorm xxx(spatie)
# de uitgewisselde tekstregels moeten eindigen met de tekens RC(#13) en LF(#10)
# # de parameters voor het verzenden van de e-mail
MAIL="mail2.txt"
# de e-mailinstellingen worden opgehaald
res=getInfos(MAIL)
# fout?
if res[0]:
print "%s" % (erreur)
sys.exit()
# e-mail verzenden in logboekmodus
(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)
# einde
sys.exit()
Opmerkingen:
- op een Windows-computer met antivirussoftware kan het zijn dat deze software het Python-script verhindert verbinding te maken met poort 25 van een SMTP-server. In dat geval moet de antivirussoftware worden uitgeschakeld. Voor McAfee kan dit bijvoorbeeld als volgt worden gedaan:
![]() |
- in [1] activeer je de console VirusScan
- bij [2] wordt de service [Protection lors de l'accès] gestopt
- bij [3] is deze gestopt
Het bestand infos.txt:
smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
De schermresultaten:
Het bericht zoals weergegeven door de e-mailclient Thunderbird:
![]() |
11.4. Een tweede SMTP-client
Dit tweede script doet hetzelfde als het vorige, maar maakt gebruik van de functionaliteiten van de module [smtplib].
# -*- coding=utf-8 -*-
import sys,socket, smtplib
#-----------------------------------------------------------------------
def getInfos(fichier):
# geeft de informatie (smtp, afzender, ontvanger, bericht) uit het tekstbestand [fichier] weer
# regel 1: smtp, afzender, ontvanger
# volgende regels: de tekst van het bericht
# het openen van [fichier]
erreur=""
try:
infos=open(fichier,"r")
except IOError, erreur:
return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
# lezen van de eerste regel
ligne=infos.readline()
# verwijdering van het einde-van-regel-teken
ligne=cutNewLineChar(ligne)
# ophalen van de velden smtp, afzender, ontvanger
champs=ligne.split(",")
# hebben we het juiste aantal velden?
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# "verwerking" van de opgehaalde informatie – de "spaties" ervoor of erna worden verwijderd
for i in range(3):
champs[i]=champs[i].strip()
# velden ophalen
(smtpServer,expediteur,destinataire)=champs
# het te verzenden bericht lezen
message=""
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# terug
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# bericht verzenden naar de SMTP-server smtpserver namens de afzender
# voor de ontvanger. Als verbose=True, wordt de communicatie tussen client en server bijgehouden
# er wordt gebruikgemaakt van de smtplib-bibliotheek
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)
# einde
return "Message envoye"
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# het einde-van-regel-teken van [ligne] wordt verwijderd, indien aanwezig
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) waarmee een bericht kan worden verzonden
# de gegevens worden uit een bestand gehaald INFOS met de volgende regels
# regel 1: smtp, afzender, ontvanger
# volgende regels: de tekst van het bericht
# afzender: e-mailadres van de afzender
# ontvanger: e-mailadres ontvanger
# smtp: naam van de te gebruiken smtp-server
# communicatieprotocol SMTP client-server
# -> client maakt verbinding via poort 25 van de SMTP-server
# <- de server stuurt hem een welkomstbericht
# -> de client stuurt het commando EHLO: de naam van zijn computer
# <- de server antwoordt met OK of niet
# -> de client verstuurt het commando mail from: <afzender>
# <- server antwoordt met OK of niet
# -> de client verstuurt het commando rcpt to: <ontvanger>
# <- server antwoordt met OK of niet
# -> de client verstuurt het commando data
# <- server antwoordt met OK of niet
# -> de client verstuurt alle regels van zijn bericht en sluit af met een regel die alleen het teken bevat.
# <- server antwoordt met OK of niet
# -> de client verstuurt het commando ‘quit’
# <- de server antwoordt met OK of niet
# de antwoorden van de server hebben de vorm xxx tekst, waarbij xxx een getal van 3 cijfers is. Elk getal xxx >=500
# duidt op een fout. Het antwoord kan uit meerdere regels bestaan die allemaal beginnen met xxx-, behalve de laatste
# in de vorm xxx(spatie)
# de uitgewisselde tekstregels moeten eindigen met de tekens RC(#13) en LF(#10)
# # de parameters voor het verzenden van de e-mail
MAIL="mail2.txt"
# de e-mailinstellingen worden opgehaald
res=getInfos(MAIL)
# fout?
if res[0]:
print "%s" % (erreur)
sys.exit()
# e-mail verzenden in logboekmodus
(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)
# einde
sys.exit()
Opmerkingen:
- dit script is identiek aan het vorige, op de functie sendmail na. Deze functie maakt nu gebruik van de functionaliteiten van de module [smtplib] (regel 3).
Het bestand 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
De schermresultaten:
11.5. Echo-client/server
We maken een echo-service aan. De server stuurt alle tekstregels die de client naar hem stuurt in hoofdletters terug. De service kan dankzij het gebruik van threads meerdere clients tegelijk bedienen.
# -*- coding=utf-8 -*-
# generieke TCP-server op Windows
# headerbestanden laden
import re,sys,SocketServer,threading
# multithreaded TCP-server
class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):
def handle(self):
# huidige thread
cur_thread = threading.currentThread()
# gegevens van de client
self.data="on"
# stoppen bij lege tekenreeks
while self.data:
# de tekstregels van de client worden gelezen met de readline-methode
self.data =self.rfile.readline().strip()
# console-uitvoer
print "client %s : %s (%s)" % (self.client_address[0],self.data,cur_thread.getName())
# antwoord naar de klant verzenden
response = "%s: %s" % (cur_thread.getName(), self.data.upper())
# self.wfile is de schrijfstroom naar de client
self.wfile.write(response)
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
# ------------------------------------------------------ main
# aanroepsyntaxis: argv[0] poort
# de server wordt gestart op de poort met de naam
# Gegevens
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- de aanroep wordt gecontroleerd
# er moet één enkel argument zijn
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# de poort moet een getal zijn
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)
# de server wordt gestart – bedient elke client in een eigen thread
host="localhost"
server = ThreadedTCPServer((host,int(port)), ThreadedTCPRequestHandler)
# de server wordt in een thread gestart
# elke client wordt bediend in een extra thread
server_thread = threading.Thread(target=server.serve_forever)
# start de server – oneindige lus om op clients te wachten
server_thread.start()
# vervolg
print "Serveur d'echo a l'ecoute sur le port % s" % (port)
Opmerkingen:
- regel 39: sys.argv staat voor de parameters van het script. Deze moeten hier de volgende vorm hebben: nom_du_script poort. Er moeten er dus twee zijn. sys.argv[0] wordt dan nom_du_script en sys.argv[1] wordt port ;
- regel 53: de echo-server is een instantie van de klasse ThreadedTcpServer. De constructor van deze klasse verwacht twee parameters:
- parameter 1: een tuple met twee elementen (host, port) die de machine en de luisterpoort van de server vastlegt;
- parameter 2: de naam van de klasse die verantwoordelijk is voor het verwerken van verzoeken van een client.
- regel 57: er wordt een thread aangemaakt (maar nog niet gestart). Deze thread voert de methode [serve_forever] van de server TCP uit. Deze methode is een lus die luistert naar verbindingen van klanten. Zodra een clientverbinding wordt gedetecteerd, wordt een instantie van de klasse ThreadedTCPRequestHandler aangemaakt. De methode handle van deze klasse is verantwoordelijk voor de communicatie met de client;
- regel 59: de thread van de echo-service wordt gestart. Vanaf dat moment kunnen clients verbinding maken met de service;
- regel 27: de klasse van de echo-server. Deze is afgeleid van twee klassen: SocketServer.ThreadingMixIn en SocketServer.TCPServer. Dit maakt het een multithreaded TCP-server: de server draait in één thread en elke klant wordt in een extra thread bediend;
- regel 9: de klasse die de verzoeken van de clients verwerkt. Deze is afgeleid van de klasse SocketServer.StreamRequestHandler. Daardoor erft hij twee attributen:
- rfile: dit is de leesstroom van de door de client verzonden gegevens – kan worden behandeld als een tekstbestand;
- wfile: dit is de schrijfstroom waarmee gegevens naar de klant kunnen worden verzonden – kan worden behandeld als een tekstbestand.
- regel 11: de methode handle verwerkt de verzoeken van de klanten;
- regel 13: de thread die deze methode uitvoert;
- regel 17: lus voor het verwerken van verzoeken van de klant. De lus eindigt wanneer de klant een lege regel verstuurt;
- regel 19: het lezen van het verzoek van de klant;
- regel 21: self.client_address[0] vertegenwoordigt het adres IP van de klant. cur_thread.getName() is de naam van de thread die de methode
handleuitvoert; - regel 23: het antwoord aan de klant bestaat uit twee onderdelen: de naam van de thread die de klant bedient en het commando dat de klant heeft verzonden, weergegeven in hoofdletters.
# -*- coding=utf-8 -*-
import re,sys,socket
# ------------------------------------------------------ main
# generieke TCP-client
# aanroepsyntaxis: argv[0] host poort
# de client maakt verbinding met de echo-service (host, poort)
# de server stuurt de door de klant ingevoerde regels terug
# syntaxis
syntaxe="syntaxe : %s hote port" % (sys.argv[0])
#------------------------------------- de aanroep wordt gecontroleerd
# er moeten twee argumenten zijn
nbArguments=len(sys.argv)
if nbArguments!=3:
print syntaxe
sys.exit(1)
# de argumenten worden opgehaald
hote=sys.argv[1]
# de poort moet een getal zijn
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:
# verbinding van de client met de 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:
# invoerlus
ligne=raw_input("Commande (rien pour arreter): ").strip()
while ligne!="":
# de regel wordt naar de server verzonden
connexion.send("%s\n" % (ligne))
# we wachten op het antwoord
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:
# de verbinding wordt verbroken
connexion.close()
De server wordt gestart in een eerste opdrachtvenster:
Een eerste client wordt gestart in een tweede venster:
De client ontvangt inderdaad als antwoord op het commando dat hij naar de server stuurt, datzelfde commando in hoofdletters. Een tweede client wordt gestart in een derde venster:
De serverconsole ziet er dan als volgt uit:
De clients worden inderdaad in verschillende threads bediend. Om een client te stoppen, volstaat het om een leeg commando in te voeren.
11.6. Generieke TCP-server
We stellen voor om een Python-script te schrijven dat
- een TCP-server is die één client tegelijk kan bedienen,
- die door de client verzonden tekstregels accepteert,
- en tekstregels van het toetsenbord accepteert die als antwoord naar de client worden verzonden.
Zo fungeert de gebruiker achter het toetsenbord als server:
- hij ziet op zijn console de door de client verzonden tekstregels;
- hij antwoordt door het antwoord op het toetsenbord in te typen.
Zo kan hij zich aanpassen aan elk type client. Daarom noemen we hem een "generieke TCP-server". Het is een handig hulpmiddel om TCP-communicatieprotocollen te verkennen. In het volgende voorbeeld is de TCP-client een webbrowser, waardoor we het HTTP-protocol kunnen verkennen dat door webclients wordt gebruikt.
![]() |
# -*- coding=utf-8 -*-
# generieke TCP-server
# het laden van de headerbestanden
import re,sys,SocketServer,threading
# generieke TCP-server
class MyTCPHandler(SocketServer.StreamRequestHandler):
def handle(self):
# de client wordt weergegeven
print "client %s" % (self.client_address[0])
# er wordt een thread aangemaakt om de opdrachten van de client te lezen
thread_lecture = threading.Thread(target=self.lecture)
thread_lecture.start()
# stoppen bij het commando 'bye'
# lezen van het via het toetsenbord ingevoerde commando
cmde=raw_input("--> ")
while cmde!="bye":
# commando naar de client verzenden. self.wfile is de schrijfstroom naar de client
self.wfile.write("%s\n" % (cmde))
# volgende opdracht lezen
cmde=raw_input("--> ")
def lecture(self):
# alle door de client verzonden regels worden weergegeven totdat het commando 'bye' wordt ontvangen
ligne=""
while ligne!="bye":
# de tekstregels van de client worden gelezen met de methode readline
ligne = self.rfile.readline().strip()
# console-log
print "<--- %s : %s" % (self.client_address[0], ligne)
# ------------------------------------------------------ main
# aanroepsyntaxis: argv[0] poort
# de server wordt gestart op de opgegeven poort
# Gegevens
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- de aanroep wordt gecontroleerd
# er moet één enkel argument zijn
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# de poort moet een getal zijn
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)
# de server wordt gestart
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()
Opmerkingen:
- regel 57: de TCP-server is een instantie van de klasse SocketServer.TCPServer. De constructor hiervan accepteert twee parameters:
- de eerste parameter is een tuple met twee elementen (host, poort), waarbij host de machine is waarop de service draait (meestal localhost) en port, de poort waarop de dienst verzoeken van clients verwacht (luistert);
- de tweede parameter specificeert de serviceklasse van een client. Wanneer een client verbinding maakt, wordt een instantie van de serviceklasse aangemaakt en is het de methode handle die de verbinding met de client moet beheren.
De TCP-server SocketServer.TCPServer is niet multithreaded. Hij bedient één client tegelijk;
- regel 59: de methode serve_forever van de TCP-server wordt uitgevoerd. Dit is een eindeloze lus waarin op clients wordt gewacht;
- regel 9: de TCP-server is hier een klasse die is afgeleid van de klasse SocketServer.StreamRequestHandler. Hierdoor kunnen de gegevensstromen die met de client worden uitgewisseld, worden beschouwd als tekstbestanden. We zijn deze klasse al eerder tegengekomen. We beschikken over de methoden:
- readline om een tekstregel van de client te lezen;
- write om tekstregels naar de klant terug te sturen.
- regel 12: self.client_address[0] is het IP-adres van de klant;
- regel 14: de TCP-server communiceert met de client via twee threads
- een thread voor het lezen van de regels van de klant;
- een thread voor het schrijven van regels naar de client.
- regel 14: de thread voor het lezen van de regels van de client wordt aangemaakt. De parameter target bepaalt de methode die door de thread wordt uitgevoerd. Dit is de methode die op regel 25 is gedefinieerd;
- regel 25: de lees-thread wordt gestart;
- regels 25-32: de methode die door de lees-thread wordt uitgevoerd;
- regel 28: de leesthread leest alle door de client verzonden tekstregels totdat de regel "bye" wordt ontvangen;
- regel 18: we bevinden ons hier in de schrijfthread naar de client. Het principe is om alle tekstregels die de gebruiker via het toetsenbord invoert naar de client te verzenden.
De server
De browser van de client
Het verzoek dat door de server is ontvangen
Het antwoord van de server, door de gebruiker via het toetsenbord ingevoerd (zonder het teken -->)
- regels 1-5: HTTP-antwoord verzonden naar de client;
- regel 6: de HTML-pagina die naar de klant is verzonden;
- regel 7: einde van de dialoog met de klant. De klantenservice wordt beëindigd en de verbinding wordt verbroken, waardoor de thread die de door de klant verzonden tekstregels leest, abrupt wordt onderbroken;
- de regels 1-5 van het HTTP-antwoord aan de klant hebben de volgende betekenis:
- regel 1: de door de klant opgevraagde bron is gevonden;
- regel 2: identificatie van de server;
- regel 3: de server zal de verbinding verbreken na het verzenden van de bron;
- regel 4: aard van de door de server verzonden bron: een HTML-document;
- regel 5: een lege regel.
De pagina die door de browser [1] wordt weergegeven:
![]() |
Als we de broncode weergeven die door de browser [2] is ontvangen, zien we dat de code HTML die door de webbrowser is ontvangen, inderdaad dezelfde is als die we naar de browser hadden verzonden.




