11. Funkcje sieciowe w języku Python
Przechodzimy teraz do funkcji sieciowych języka Python, które pozwalają nam programować w protokołach TCP / IP (Transfer Control Protocol / Internet Protocol).
![]() |
11.1. Pobieranie nazwy lub adresu IP komputera w Internecie
import sys, socket
#------------------------------------------------
def getIPandName(nomMachine):
#nomMachine: nazwa komputera, którego adres chcemy uzyskać 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
# ---------------------------------------- main
# stałe
HOTES=["istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","localhost","","xx"]
# adresy IP maszyn z HOTES
for i in range(len(HOTES)):
getIPandName(HOTES[i])
# koniec
sys.exit()
Uwagi:
- wiersz 1: funkcje sieciowe języka Python są zawarte w module socket.
11.2. Klient internetowy
Skrypt umożliwiający pobranie treści akapitu indeksowego ze strony internetowej.
import sys,socket
#-----------------------------------------------------------------------
def getIndex(site):
# odczytuje stronę URL i zapisuje ją w pliku site.html
# początkowo bez błędów
erreur=""
# utworzenie pliku site.html
try:
html=open("%s.html" % (site),"w")
except IOError, erreur:
pass
# błąd?
if erreur:
return "Erreur (%s) lors de la création du fichier %s.html" % (erreur,site)
# nawiązanie połączenia na porcie 80 serwisu
try:
connexion=socket.create_connection((site,80))
except socket.error, erreur:
pass
# powrót w przypadku błędu
if erreur:
return "Echec de la connexion au site (%s,80) : %s" % (site,erreur)
# Połączenie stanowi dwukierunkowy strumień komunikacji
# między klientem (tym programem) a serwerem WWW, z którym nawiązano połączenie
# kanał ten służy do wymiany poleceń i informacji
# protokołem komunikacji jest HTTP
# klient wysyła polecenie GET, aby zażądać pliku URL /
# składnia polecenia „get”: URL HTTP/1.0
# nagłówki (headers) protokołu HTTP muszą kończyć się pustym wierszem
connexion.send("GET / HTTP/1.0\n\n")
# serwer odpowie teraz na kanale połączenia. Wyśle wszystkie
# swoje dane, a następnie zamknie kanał. Klient odczytuje wszystko, co przychodzi z połączenia
# aż do zamknięcia kanału
ligne=connexion.recv(1000)
while(ligne):
html.write(ligne)
ligne=connexion.recv(1000)
# klient z kolei zamyka połączenie
connexion.close()
# zamknięcie pliku HTML
html.close()
# powrót
return "Transfert reussi du paragraphe index du site %s" % (site)
# --------------------------------------------- main
# pobieranie tekstu HTML z URL
# lista stron internetowych
SITES=("istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","xx")
# odczyt stron indeksowych witryn z tabeli SITES
for i in range(len(SITES)):
# odczyt strony indeksowej serwisu SITES[i]
resultat=getIndex(SITES[i])
# wyświetlanie wyniku
print resultat
# koniec
sys.exit()
Uwagi:
- wiersz 57: lista adresów URL stron internetowych, z których chcemy pobrać stronę główną. Lista ta jest zapisana w pliku tekstowym [nomsite.html];
- wiersz 62: zadanie to realizuje funkcja getIndex;
- wiersz 4: funkcja getIndex;
- wiersz 20: metoda create_connection((site,port)) pozwala na nawiązanie połączenia z usługą TCP / IP działającą na porcie port na komputerze site;
- wiersz 35: metoda send umożliwia wysyłanie danych poprzez połączenie TCP / IP. W tym przypadku wysyłany jest tekst. Tekst ten jest zgodny z protokołem HTTP (HyperText Transfer Protocol);
- wiersz 40: metoda recv umożliwia odbieranie danych poprzez połączenie TCP / IP. W tym przypadku odpowiedź serwera WWW jest odczytywana w blokach po 1000 znaków i zapisywana w pliku tekstowym [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
Plik otrzymany dla strony [www.ibm.com]:
- wiersze 1–11 to nagłówki HTTP z odpowiedzi serwera;
- wiersz 1: serwer prosi klienta o przekierowanie do adresu URL podanego w wierszu 8;
- wiersz 2: data i godzina odpowiedzi;
- wiersz 3: identyfikator serwera WWW;
- wiersz 4: treść wysłana przez serwer. W tym przypadku jest to strona HTML, która zaczyna się w wierszu 13;
- wiersz 12: pusty wiersz kończący nagłówki HTTP;
- wiersze 13–19: strona HTML wysłana przez serwer WWW.
11.3. Klient SMTP
Spośród protokołów TCP / IP, SMTP (SendMail Transfer Protocol) jest protokołem komunikacyjnym usługi wysyłania wiadomości.
# -*- coding=utf-8 -*-
import sys,socket
#-----------------------------------------------------------------------
def getInfos(fichier):
# zwraca informacje (SMTP, nadawca, odbiorca, treść wiadomości) pobrane z pliku tekstowego [fichier]
# wiersz 1: SMTP, nadawca, odbiorca
# kolejne wiersze: treść wiadomości
# otwarcie pliku [fichier]
erreur=""
try:
infos=open(fichier,"r")
except IOError, erreur:
return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
# odczyt pierwszego wiersza
ligne=infos.readline()
# usunięcie znaku końca wiersza
ligne=cutNewLineChar(ligne)
# pobieranie pól smtp, nadawca, odbiorca
champs=ligne.split(",")
# czy liczba pól jest prawidłowa?
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# „przetwarzanie” pobranych informacji
# usuwanie „spacji” poprzedzających lub następujących po informacji użytkowej z każdego z trzech pól
for i in range(3):
champs[i]=champs[i].strip()
# pobieranie pól
(smtpServer,expediteur,destinataire)=champs
message=""
# odczyt pozostałej części wiadomości
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# powrót
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# wysyła wiadomość na serwer SMTP smtpserver w imieniu nadawcy
# dla odbiorcy. Jeśli verbose=True, śledzi wymianę danych między klientem a serwerem
# pobierana jest nazwa klienta
try:
client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
except socket.error, erreur:
return "Erreur IP / Nom du client : %s" % (erreur)
# otwarcie połączenia na porcie 25 serwera smtpServer
try:
connexion=socket.create_connection((smtpServer,25))
except socket.error, erreur:
return "Echec de la connexion au site (%s,25) : %s" % (smtpServer,erreur)
# Połączenie stanowi dwukierunkowy strumień komunikacji
# między klientem (tym programem) a serwerem SMTP, z którym nawiązano połączenie
# kanał ten służy do wymiany poleceń i informacji
# po nawiązaniu połączenia serwer wysyła wiadomość powitalną, którą odczytujemy
erreur=sendCommand(connexion,"",verbose,1)
if(erreur) :
connexion.close()
return erreur
# polecenie „ehlo”:
erreur=sendCommand(connexion,"EHLO %s" % (client),verbose,1)
if erreur :
connexion.close()
return erreur
# polecenie mail from:
erreur=sendCommand(connexion,"MAIL FROM: <%s>" % (expediteur),verbose,1)
if erreur :
connexion.close()
return erreur
# polecenie rcpt to:
erreur=sendCommand(connexion,"RCPT TO: <%s>" % (destinataire),verbose,1)
if erreur :
connexion.close()
return erreur
# polecenie data
erreur=sendCommand(connexion,"DATA",verbose,1)
if erreur :
connexion.close()
return erreur
# przygotowanie wiadomości do wysłania
# musi zawierać następujące wiersze
# From: nadawca
# Do: odbiorca
# pusta linia
# Wiadomość
# .
data="From: %s\r\nTo: %s\r\n%s\r\n.\r\n" % (expediteur,destinataire,message)
# wysłanie wiadomości
erreur=sendCommand(connexion,data,verbose,0)
if erreur :
connexion.close()
return erreur
# polecenie quit
erreur=sendCommand(connexion,"QUIT",verbose,1)
if erreur :
connexion.close()
return erreur
# koniec
connexion.close()
return "Message envoye"
# --------------------------------------------------------------------------
def sendCommand(connexion,commande,verbose,withRCLF):
# wysyła polecenie na kanale połączenia
# tryb szczegółowy, jeśli verbose=1
# jeśli withRCLF=1, dodaj sekwencję RCLF do polecenia
# dane
RCLF="\r\n" if withRCLF else ""
# wysyłanie polecenia, jeśli polecenie nie jest puste
if commande:
connexion.send("%s%s" % (commande,RCLF))
# ewentualne echo
if verbose:
affiche(commande,1)
# odczyt odpowiedzi o długości mniejszej niż 1000 znaków
reponse=connexion.recv(1000)
# ewentualne echo
if verbose:
affiche(reponse,2)
# pobieranie kodu błędu
codeErreur=reponse[0:3]
# błąd zwrócony przez serwer?
if int(codeErreur) >=500:
return reponse[4:]
# powrót bez błędu
return ""
# --------------------------------------------------------------------------
def affiche(echange,sens):
# wyświetla wymianę na ekranie?
# jeśli sens=1, wyświetla -->wymiana
# jeśli sens=2, wyświetla <-- wymiana bez dwóch ostatnich znaków RCLF
if sens==1:
print "--> [%s]" % (echange)
return
elif sens==2:
l=len(echange)
print "<-- [%s]" % echange[0:l-2]
return
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# usuwa się znak końca linii z [ligne], jeśli istnieje
l=len(ligne)
while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
l-=1
return(ligne[0:l])
# main ----------------------------------------------------------------
# klient SMTP (protokół transferu SendMail) umożliwiający wysłanie wiadomości
# informacje są pobierane z pliku INFOS zawierającego następujące wiersze
# wiersz 1: smtp, nadawca, odbiorca
# kolejne wiersze: treść wiadomości
# nadawca: adres e-mail nadawcy
# odbiorca: adres e-mail odbiorcy
# SMTP: nazwa serwera SMTP, z którego należy korzystać
# protokół komunikacji SMTP klient-serwer
# -> klient łączy się z serwerem SMTP na porcie 25
# <- serwer wysyła mu wiadomość powitalną
# -> klient wysyła polecenie EHLO: nazwa swojego komputera
# <- serwer odpowiada OK lub nie
# -> klient wysyła polecenie mail from: <nadawca>
# <- serwer odpowiada OK lub nie
# -> klient wysyła polecenie rcpt to: <odbiorca>
# <- serwer odpowiada OK lub nie
# -> klient wysyła polecenie data
# <- serwer odpowiada OK lub nie
# -> klient wysyła wszystkie wiersze swojej wiadomości i kończy wierszem zawierającym tylko jeden znak .
# <- serwer odpowiada OK lub nie
# -> klient wysyła polecenie quit
# <- serwer odpowiada OK lub nie
# odpowiedzi serwera mają postać xxx tekst, gdzie xxx to trzycyfrowa liczba. Każda liczba xxx >=500
# oznacza błąd. Odpowiedź może składać się z kilku wierszy, z których wszystkie oprócz ostatniego zaczynają się od xxx-
# w postaci xxx (spacja)
# wymieniane wiersze tekstu muszą kończyć się znakami RC(#13) i LF(#10)
# # parametry wysyłania wiadomości
MAIL="mail2.txt"
# pobieramy parametry wiadomości
res=getInfos(MAIL)
# błąd?
if res[0]:
print "%s" % (erreur)
sys.exit()
# wysyłanie wiadomości w trybie szczegółowym
(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)
# koniec
sys.exit()
Uwagi:
- na komputerze z systemem Windows wyposażonym w program antywirusowy, program ten może uniemożliwić skryptowi w języku Python połączenie się z portem 25 serwera SMTP. W takim przypadku należy wyłączyć program antywirusowy. Na przykład w przypadku McAfee można postępować w następujący sposób:
![]() |
- w [1] należy włączyć konsolę VirusScan
- w przypadku [2] należy zatrzymać usługę [Protection lors de l'accès]
- w przypadku [3] usługa jest zatrzymana
Plik infos.txt:
smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
Wyniki na ekranie:
Wiadomość wyświetlana w kliencie poczty Thunderbird:
![]() |
11.4. Drugi klient SMTP
Ten drugi skrypt działa tak samo jak poprzedni, ale wykorzystuje funkcje modułu [smtplib].
# -*- coding=utf-8 -*-
import sys,socket, smtplib
#-----------------------------------------------------------------------
def getInfos(fichier):
# zwraca informacje (SMTP, nadawca, odbiorca, treść wiadomości) pobrane z pliku tekstowego [fichier]
# wiersz 1: SMTP, nadawca, odbiorca
# kolejne wiersze: treść wiadomości
# otwarcie pliku [fichier]
erreur=""
try:
infos=open(fichier,"r")
except IOError, erreur:
return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
# odczyt pierwszego wiersza
ligne=infos.readline()
# usunięcie znaku końca linii
ligne=cutNewLineChar(ligne)
# pobieranie pól smtp, nadawca, odbiorca
champs=ligne.split(",")
# czy liczba pól jest prawidłowa?
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# „przetwarzanie” pobranych informacji – usuwanie spacji poprzedzających lub następujących po nich
for i in range(3):
champs[i]=champs[i].strip()
# pobieranie pól
(smtpServer,expediteur,destinataire)=champs
# odczyt wiadomości do wysłania
message=""
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# powrót
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# wysyła wiadomość na serwer SMTP smtpserver w imieniu nadawcy
# dla odbiorcy. Jeśli verbose=True, śledzi wymianę danych między klientem a serwerem
# wykorzystuje się bibliotekę 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)
# koniec
return "Message envoye"
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# usuwa się znak końca linii z [ligne], jeśli istnieje
l=len(ligne)
while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
l-=1
return(ligne[0:l])
# main ----------------------------------------------------------------
# klient SMTP (protokół transferu SendMail) umożliwiający wysłanie wiadomości
# informacje są pobierane z pliku INFOS zawierającego następujące wiersze
# wiersz 1: smtp, nadawca, odbiorca
# kolejne wiersze: treść wiadomości
# nadawca: adres e-mail nadawcy
# odbiorca: adres e-mail odbiorcy
# SMTP: nazwa serwera SMTP, z którego należy korzystać
# protokół komunikacji klient-serwer SMTP
# -> klient łączy się z serwerem SMTP na porcie 25
# <- serwer wysyła mu wiadomość powitalną
# -> klient wysyła polecenie EHLO: nazwa swojego komputera
# <- serwer odpowiada OK lub nie
# -> klient wysyła polecenie mail from: <nadawca>
# <- serwer odpowiada OK lub nie
# -> klient wysyła polecenie rcpt to: <odbiorca>
# <- serwer odpowiada OK lub nie
# -> klient wysyła polecenie data
# <- serwer odpowiada OK lub nie
# -> klient wysyła wszystkie wiersze swojej wiadomości i kończy wierszem zawierającym tylko jeden znak .
# <- serwer odpowiada OK lub nie
# -> klient wysyła polecenie quit
# <- serwer odpowiada OK lub nie
# odpowiedzi serwera mają postać xxx tekst, gdzie xxx to trzycyfrowa liczba. Każda liczba xxx >=500
# oznacza błąd. Odpowiedź może składać się z kilku wierszy, z których wszystkie oprócz ostatniego zaczynają się od xxx-
# w postaci xxx (spacja)
# wymieniane wiersze tekstu muszą kończyć się znakami RC(#13) i LF(#10)
# # parametry wysyłania wiadomości
MAIL="mail2.txt"
# pobieramy parametry wiadomości
res=getInfos(MAIL)
# błąd?
if res[0]:
print "%s" % (erreur)
sys.exit()
# wysyłanie wiadomości w trybie szczegółowym
(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)
# koniec
sys.exit()
Uwagi:
- skrypt ten jest identyczny z poprzednim, z wyjątkiem funkcji sendmail. Funkcja ta korzysta teraz z możliwości modułu [smtplib] (wiersz 3).
Plik 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
Wyniki na ekranie:
11.5. Klient / serwer echa
Tworzymy usługę echo. Serwer zwraca wszystkie wiersze tekstu przesłane przez klienta, zamieniając je na wielkie litery. Usługa może obsługiwać wielu klientów jednocześnie dzięki wykorzystaniu wątków.
# -*- coding=utf-8 -*-
# ogólny serwer TCP w systemie Windows
# ładowanie plików nagłówkowych
import re,sys,SocketServer,threading
# wielowątkowy serwer TCP
class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):
def handle(self):
# bieżący wątek
cur_thread = threading.currentThread()
# dane klienta
self.data="on"
# zatrzymanie na pustym łańcuchu
while self.data:
# wiersze tekstu od klienta są odczytywane za pomocą metody readline
self.data =self.rfile.readline().strip()
# monitorowanie konsoli
print "client %s : %s (%s)" % (self.client_address[0],self.data,cur_thread.getName())
# wysyłanie odpowiedzi do klienta
response = "%s: %s" % (cur_thread.getName(), self.data.upper())
# self.wfile to strumień zapisu do klienta
self.wfile.write(response)
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
# ------------------------------------------------------ main
# składnia wywołania: argv[0] port
# serwer uruchomiono na porcie o nazwie
# Dane
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- sprawdzane jest wywołanie
# musi być jeden i tylko jeden argument
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# port musi być liczbą
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)
# uruchomienie serwera – obsługuje każdego klienta w osobnym wątku
host="localhost"
server = ThreadedTCPServer((host,int(port)), ThreadedTCPRequestHandler)
# serwer jest uruchamiany w wątku
# każdy klient będzie obsługiwany w osobnym wątku
server_thread = threading.Thread(target=server.serve_forever)
# uruchamia serwer – nieskończona pętla oczekiwania na klientów
server_thread.start()
# monitorowanie
print "Serveur d'echo a l'ecoute sur le port % s" % (port)
Uwagi:
- wiersz 39: sys.argv oznacza parametry skryptu. Muszą one mieć tutaj postać: nom_du_script port. Muszą więc występować w dwóch egzemplarzach. sys.argv[0] stanie się wówczas nom_du_script, a sys.argv[1] stanie się port ;
- wiersz 53: serwer echo jest instancją klasy ThreadedTcpServer. Konstruktor tej klasy oczekuje dwóch parametrów:
- parametr 1: krotka składająca się z dwóch elementów (host, port), która określa komputer i port nasłuchowy serwera;
- parametr 2: nazwa klasy odpowiedzialnej za przetwarzanie żądań klienta.
- wiersz 57: tworzony jest wątek (ale jeszcze nie uruchomiony). Wątek ten wykonuje metodę [serve_forever] serwera TCP. Metoda ta stanowi pętlę nasłuchującą na połączenia klienckie. Gdy tylko wykryte zostanie połączenie klienckie, utworzona zostanie instancja klasy ThreadedTCPRequestHandler. Jej metoda handle odpowiada za komunikację z klientem;
- wiersz 59: uruchamiany jest wątek usługi echa. Od tego momentu klienci mogą łączyć się z usługą;
- wiersz 27: klasa serwera echo. Wywodzi się ona z dwóch klas: SocketServer.ThreadingMixIn oraz SocketServer.TCPServer. To sprawia, że jest to serwer wielowątkowy typu TCP: serwer działa w jednym wątku, a każdy klient jest obsługiwany w dodatkowym wątku;
- wiersz 9: klasa przetwarzająca żądania klientów. Wywodzi się z klasy SocketServer.StreamRequestHandler. Dziedziczy zatem dwa atrybuty:
- rfile: strumień odczytu danych wysyłanych przez klienta – można go traktować jak plik tekstowy;
- wfile: strumień zapisu umożliwiający wysyłanie danych do klienta – można go traktować jako plik tekstowy.
- wiersz 11: metoda handle przetwarza żądania klientów;
- wiersz 13: wątek, który wykonuje tę metodę;
- wiersz 17: pętla przetwarzania żądań klienta. Pętla kończy się, gdy klient wyśle pusty wiersz;
- wiersz 19: odczyt żądania klienta;
- wiersz 21: self.client_address[0] oznacza adres klienta IP. cur_thread.getName() to nazwa wątku, który wykonuje metodę handle;
- wiersz 23: odpowiedź dla klienta składa się z dwóch elementów – nazwy wątku obsługującego klienta oraz wysłanego przez niego polecenia, podanego wielkimi literami.
# -*- coding=utf-8 -*-
import re,sys,socket
# ------------------------------------------------------ main
# ogólny klient TCP
# składnia wywołania: argv[0] host port
# klient łączy się z usługą echo (host, port)
# serwer zwraca wiersze wpisane przez klienta
# składnia
syntaxe="syntaxe : %s hote port" % (sys.argv[0])
#------------------------------------- sprawdzanie wywołania
# muszą być dwa argumenty
nbArguments=len(sys.argv)
if nbArguments!=3:
print syntaxe
sys.exit(1)
# pobierane są argumenty
hote=sys.argv[1]
# port musi być liczbą
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:
# połączenie klienta z serwerem
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:
# pętla wprowadzania danych
ligne=raw_input("Commande (rien pour arreter): ").strip()
while ligne!="":
# wysyłanie wiersza do serwera
connexion.send("%s\n" % (ligne))
# oczekiwanie na odpowiedź
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:
# zamykanie połączenia
connexion.close()
Serwer uruchamia się w pierwszym oknie poleceń:
Pierwszy klient uruchamia się w drugim oknie:
Klient otrzymuje w odpowiedzi na polecenie wysłane do serwera to samo polecenie zapisane wielkimi literami. Drugi klient zostaje uruchomiony w trzecim oknie:
Konsola serwera wygląda wówczas następująco:
Klienci są obsługiwani w różnych wątkach. Aby zatrzymać klienta, wystarczy wpisać puste polecenie.
11.6. Ogólny serwer TCP
Proponujemy napisanie skryptu w języku Python, który
- będzie serwerem TCP obsługującym jednego klienta na raz,
- przyjmujący wiersze tekstowe wysyłane przez klienta,
- przyjmował linie tekstu z klawiatury i wysyłał je w odpowiedzi do klienta.
W ten sposób to użytkownik korzystający z klawiatury pełni rolę serwera:
- widzi na swojej konsoli wiersze tekstowe wysyłane przez klienta;
- odpowiada mu, wpisując odpowiedź na klawiaturze.
W ten sposób może dostosować się do każdego rodzaju klienta. Dlatego nazwiemy go „ogólnym serwerem TCP”. Jest to praktyczne narzędzie do poznawania protokołów komunikacyjnych TCP. W poniższym przykładzie klientem TCP będzie przeglądarka internetowa, co pozwoli nam poznać protokół HTTP używany przez klientów internetowych.
![]() |
# -*- coding=utf-8 -*-
# ogólny serwer TCP
# ładowanie plików nagłówkowych
import re,sys,SocketServer,threading
# ogólny serwer TCP
class MyTCPHandler(SocketServer.StreamRequestHandler):
def handle(self):
# wyświetlanie klienta
print "client %s" % (self.client_address[0])
# tworzenie wątku do odczytu poleceń klienta
thread_lecture = threading.Thread(target=self.lecture)
thread_lecture.start()
# zatrzymanie po komendzie „bye”
# odczyt polecenia wpisanego z klawiatury
cmde=raw_input("--> ")
while cmde!="bye":
# wysyłanie polecenia do klienta. self.wfile to strumień zapisu do klienta
self.wfile.write("%s\n" % (cmde))
# odczyt kolejnego polecenia
cmde=raw_input("--> ")
def lecture(self):
# wyświetlane są wszystkie wiersze wysłane przez klienta do momentu otrzymania polecenia „bye”
ligne=""
while ligne!="bye":
# wiersze tekstowe od klienta są odczytywane za pomocą metody readline
ligne = self.rfile.readline().strip()
# monitorowanie konsoli
print "<--- %s : %s" % (self.client_address[0], ligne)
# ------------------------------------------------------ main
# składnia wywołania: argv[0] port
# serwer uruchamia się na porcie o nazwie
# Dane
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- sprawdzane jest wywołanie
# musi być jeden i tylko jeden argument
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# port musi być liczbą
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)
# uruchomienie serwera
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()
Uwagi:
- wiersz 57: serwer TCP będzie instancją klasy SocketServer.TCPServer. Jej konstruktor przyjmuje dwa parametry:
- pierwszy parametr to krotka składająca się z dwóch elementów (host, port), gdzie host to komputer, na którym działa usługa (zazwyczaj localhost), a port to port, na którym usługa oczekuje (nasłuchuje) na żądania klientów;
- drugi parametr określa klasę usługi klienta. Gdy klient nawiązuje połączenie, tworzona jest instancja klasy usługi i to jej metoda handle musi zarządzać połączeniem z klientem.
Serwer TCP SocketServer.TCPServer nie jest wielowątkowy. Obsługuje tylko jednego klienta na raz;
- wiersz 59: wykonywana jest metoda serve_forever serwera TCP. Jest to pętla bez końca, w której serwer oczekuje na klientów;
- wiersz 9: serwer TCP jest tutaj klasą wywodzącą się z klasy SocketServer.StreamRequestHandler. Pozwala to traktować strumienie danych wymienianych z klientem jako pliki tekstowe. Z tą klasą mieliśmy już do czynienia. Dysponujemy następującymi metodami:
- readline do odczytu linii tekstu od klienta;
- write do wysyłania mu w zamian wierszy tekstu.
- wiersz 12: self.client_address[0] to adres IP klienta;
- wiersz 14: serwer TCP będzie komunikował się z klientem za pomocą dwóch wątków
- jednego wątku do odczytu wierszy od klienta;
- wątku do zapisywania wierszy dla klienta.
- wiersz 14: tworzony jest wątek odczytu wierszy od klienta. Jego parametr target określa metodę wykonywaną przez ten wątek. Jest to metoda zdefiniowana w wierszu 25;
- wiersz 25: uruchamiany jest wątek odczytu;
- wiersze 25–32: metoda wykonywana przez wątek odczytu;
- wiersz 28: wątek odczytu odczytuje wszystkie wiersze tekstu wysłane przez klienta aż do momentu odebrania wiersza „bye”;
- wiersz 18: znajdujemy się tutaj w wątku pisania do klienta. Zasadą jest wysyłanie do klienta wszystkich wierszy tekstu wpisanych przez użytkownika na klawiaturze.
Serwer
Przeglądarka klienta
Żądanie otrzymane przez serwer
Odpowiedź serwera wpisana przez użytkownika na klawiaturze (bez znaku -->)
- wiersze 1–5: odpowiedź HTTP wysłana do klienta;
- wiersz 6: strona HTML wysłana do klienta;
- wiersz 7: koniec dialogu z klientem. Obsługa klienta zostanie zakończona, a połączenie zamknięte, co spowoduje nagłe przerwanie wątku odczytu wierszy tekstu wysłanych przez klienta;
- wiersze 1–5 odpowiedzi HTTP wysłanej do klienta mają następujące znaczenie:
- wiersz 1: zasób żądany przez klienta został znaleziony;
- wiersz 2: identyfikacja serwera;
- wiersz 3: serwer zamknie połączenie po wysłaniu zasobu;
- wiersz 4: rodzaj zasobu wysłanego przez serwer: dokument HTML;
- wiersz 5: pusty wiersz.
Strona wyświetlana przez przeglądarkę [1]:
![]() |
Jeśli wyświetlimy kod źródłowy odebrany przez przeglądarkę [2], zauważymy, że kod HTML odebrany przez przeglądarkę internetową jest rzeczywiście tym, który został do niej wysłany.




