11. Мережеві функції Python
Тепер ми розглянемо мережеві функції Python, які дозволяють нам здійснювати програмування TCP / IP (Transfer Control Protocol / Internet Protocol).
![]() |
11.1. Отримання імені або адреси IP комп’ютера в Інтернеті
import sys, socket
#------------------------------------------------
def getIPandName(nomMachine):
#nomMachine : ім'я комп'ютера, адресу якого потрібно отримати IP
# nomMachine-->адреса IP
try:
ip=socket.gethostbyname(nomMachine)
print "ip[%s]=%s" % (nomMachine,ip)
except socket.error, erreur:
print "ip[%s]=%s" % (nomMachine,erreur)
return
# адреса 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
# константи
HOTES=["istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","localhost","","xx"]
# адреси IP машин з HOTES
for i in range(len(HOTES)):
getIPandName(HOTES[i])
# кінець
sys.exit()
Примітки:
- рядок 1: мережеві функції Python інкапсульовані в модулі socket.
11.2. Веб-клієнт
Скрипт, що дозволяє отримати вміст вступного абзацу веб-сайту.
import sys,socket
#-----------------------------------------------------------------------
def getIndex(site):
# зчитує сайт URL та зберігає його у файлі site.html
# спочатку помилок немає
erreur=""
# створення файлу site.html
try:
html=open("%s.html" % (site),"w")
except IOError, erreur:
pass
# помилка?
if erreur:
return "Erreur (%s) lors de la création du fichier %s.html" % (erreur,site)
# відкриття з'єднання на порту 80 сайту
try:
connexion=socket.create_connection((site,80))
except socket.error, erreur:
pass
# повернення у разі помилки
if erreur:
return "Echec de la connexion au site (%s,80) : %s" % (site,erreur)
# з'єднання являє собою двосторонній канал зв'язку
# між клієнтом (цією програмою) та веб-сервером, до якого звертаються
# цей канал використовується для обміну командами та інформацією
# протокол взаємодії — HTTP
# клієнт надсилає команду get для запиту URL /
# синтаксис get: URL HTTP/1.0
# заголовки (headers) протоколу HTTP повинні закінчуватися порожнім рядком
connexion.send("GET / HTTP/1.0\n\n")
# сервер тепер відповість на каналі з’єднання. Він надішле всі
# свої дані, а потім закриє канал. Клієнт зчитує все, що надходить по каналу з’єднання
# до закриття каналу
ligne=connexion.recv(1000)
while(ligne):
html.write(ligne)
ligne=connexion.recv(1000)
# у свою чергу клієнт закриває з’єднання
connexion.close()
# закриття HTML-файлу
html.close()
# повернення
return "Transfert reussi du paragraphe index du site %s" % (site)
# --------------------------------------------- main
# отримати текст HTML з URL
# список веб-сайтів
SITES=("istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","xx")
# читання індексних сторінок сайтів з таблиці SITES
for i in range(len(SITES)):
# читання індексних сторінок сайту SITES[i]
resultat=getIndex(SITES[i])
# виведення результату
print resultat
# кінець
sys.exit()
Примітки:
- рядок 57: список URL-адрес веб-сайтів, з яких потрібно отримати головну сторінку. Цей список зберігається у текстовому файлі [nomsite.html];
- рядок 62: функція getIndex виконує цю роботу;
- рядок 4: функція getIndex;
- рядок 20: метод create_connection((site,port)) дозволяє встановити з’єднання зі службою TCP / IP, що працює на порту port комп’ютера site;
- рядок 35: метод send дозволяє надсилати дані через з’єднання TCP / IP. У цьому випадку надсилається текст. Цей текст відповідає протоколу HTTP (HyperText Transfer Protocol);
- рядок 40: метод recv дозволяє отримувати дані через з’єднання TCP / IP. Тут відповідь веб-сервера зчитується блоками по 1000 символів і записується у текстовий файл [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
Файл, отриманий для сайту [www.ibm.com]:
- рядки 1–11 — це заголовки HTTP у відповіді сервера;
- рядок 1: сервер просить клієнта перенаправитися на URL, вказаний у рядку 8;
- рядок 2: дата та час відповіді;
- рядок 3: ідентифікатор веб-сервера;
- рядок 4: вміст, надісланий сервером. У даному випадку це сторінка HTML, яка починається з рядка 13;
- рядок 12: порожній рядок, що завершує заголовки HTTP;
- рядки 13–19: сторінка HTML, надіслана веб-сервером.
11.3. SMTP-клієнт
Серед протоколів TCP / IP, SMTP (SendMail Transfer Protocol) є протоколом зв’язку служби відправлення повідомлень.
# -*- coding=utf-8 -*-
import sys,socket
#-----------------------------------------------------------------------
def getInfos(fichier):
# повертає інформацію (SMTP, відправник, одержувач, повідомлення), взяту з текстового файлу [fichier]
# рядок 1: smtp, відправник, одержувач
# наступні рядки: текст повідомлення
# відкриття [fichier]
erreur=""
try:
infos=open(fichier,"r")
except IOError, erreur:
return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
# читання першого рядка
ligne=infos.readline()
# видалення позначки кінця рядка
ligne=cutNewLineChar(ligne)
# витяг полів smtp, відправник, одержувач
champs=ligne.split(",")
# чи правильна кількість полів?
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# «обробка» отриманої інформації
# видалення з кожного з 3 полів «пробілів», що передують або йдуть після корисної інформації
for i in range(3):
champs[i]=champs[i].strip()
# витяг полів
(smtpServer,expediteur,destinataire)=champs
message=""
# читання решти повідомлення
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# повернення
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# надсилає повідомлення на SMTP-сервер smtpserver від імені відправника
# для одержувача. Якщо verbose=True, здійснює відстеження обміну даними між клієнтом і сервером
# отримується ім’я клієнта
try:
client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
except socket.error, erreur:
return "Erreur IP / Nom du client : %s" % (erreur)
# відкриття з'єднання на порту 25 сервера smtpServer
try:
connexion=socket.create_connection((smtpServer,25))
except socket.error, erreur:
return "Echec de la connexion au site (%s,25) : %s" % (smtpServer,erreur)
# з'єднання являє собою двосторонній канал зв'язку
# між клієнтом (цією програмою) та SMTP-сервером, до якого встановлено з’єднання
# цей канал використовується для обміну командами та інформацією
# після встановлення з’єднання сервер надсилає привітальне повідомлення, яке ми читаємо
erreur=sendCommand(connexion,"",verbose,1)
if(erreur) :
connexion.close()
return erreur
# команда ehlo:
erreur=sendCommand(connexion,"EHLO %s" % (client),verbose,1)
if erreur :
connexion.close()
return erreur
# команда «mail from»:
erreur=sendCommand(connexion,"MAIL FROM: <%s>" % (expediteur),verbose,1)
if erreur :
connexion.close()
return erreur
# команда rcpt to:
erreur=sendCommand(connexion,"RCPT TO: <%s>" % (destinataire),verbose,1)
if erreur :
connexion.close()
return erreur
# команда data
erreur=sendCommand(connexion,"DATA",verbose,1)
if erreur :
connexion.close()
return erreur
# підготовка повідомлення для відправлення
# воно має містити такі рядки
# From: відправник
# To: одержувач
# порожній рядок
# Повідомлення
# .
data="From: %s\r\nTo: %s\r\n%s\r\n.\r\n" % (expediteur,destinataire,message)
# відправлення повідомлення
erreur=sendCommand(connexion,data,verbose,0)
if erreur :
connexion.close()
return erreur
# команда виходу
erreur=sendCommand(connexion,"QUIT",verbose,1)
if erreur :
connexion.close()
return erreur
# кінець
connexion.close()
return "Message envoye"
# --------------------------------------------------------------------------
def sendCommand(connexion,commande,verbose,withRCLF):
# відправляє команду в канал з'єднання
# режим детального виведення, якщо verbose=1
# якщо withRCLF=1, додає послідовність RCLF до команди
# дані
RCLF="\r\n" if withRCLF else ""
# відправлення команди, якщо команда не порожня
if commande:
connexion.send("%s%s" % (commande,RCLF))
# можливе відлуння
if verbose:
affiche(commande,1)
# читання відповіді довжиною менше 1000 символів
reponse=connexion.recv(1000)
# можливе відлуння
if verbose:
affiche(reponse,2)
# отримання коду помилки
codeErreur=reponse[0:3]
# помилка, повернута сервером?
if int(codeErreur) >=500:
return reponse[4:]
# повернення без помилки
return ""
# --------------------------------------------------------------------------
def affiche(echange,sens):
# відобразити обмін на екрані?
# якщо sens=1, відображає -->обмін
# якщо sens=2, виводить <-- обмін без останніх 2 символів RCLF
if sens==1:
print "--> [%s]" % (echange)
return
elif sens==2:
l=len(echange)
print "<-- [%s]" % echange[0:l-2]
return
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# видаляється позначка кінця рядка з [ligne], якщо вона є
l=len(ligne)
while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
l-=1
return(ligne[0:l])
# головний ----------------------------------------------------------------
# клієнт SMTP (протокол передачі SendMail), що дозволяє надсилати повідомлення
# інформація береться з файлу INFOS, що містить такі рядки
# рядок 1: smtp, відправник, одержувач
# наступні рядки: текст повідомлення
# відправник: електронна адреса відправника
# адресат: електронна адреса адресата
# smtp: назва сервера smtp, який слід використовувати
# протокол зв’язку SMTP «клієнт-сервер»
# -> клієнт підключається до порту 25 сервера SMTP
# <- сервер надсилає йому привітальне повідомлення
# -> клієнт надсилає команду EHLO: ім’я свого комп’ютера
# <- сервер відповідає OK або ні
# -> клієнт надсилає команду mail from: <відправник>
# <- сервер відповідає OK або ні
# -> клієнт надсилає команду rcpt to: <адресат>
# <- сервер відповідає OK або ні
# -> клієнт надсилає команду data
# <- сервер відповідає OK або ні
# -> клієнт надсилає всі рядки свого повідомлення і завершує його рядком, що містить лише один символ .
# <- сервер відповідає OK або ні
# -> клієнт надсилає команду quit
# <- сервер відповідає OK або ні
# відповіді сервера мають вигляд xxx текст, де xxx — трицифрове число. Будь-яке число xxx >=500
# вказує на помилку. Відповідь може містити кілька рядків, які починаються з xxx-, за винятком останнього
# у форматі xxx (пробіл)
# обмінювані рядки тексту повинні закінчуватися символами RC(#13) та LF(#10)
# # параметри відправлення листа
MAIL="mail2.txt"
# отримуємо параметри листа
res=getInfos(MAIL)
# помилка?
if res[0]:
print "%s" % (erreur)
sys.exit()
# відправлення листа у режимі детального виведення
(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)
# кінець
sys.exit()
Примітки:
- на комп’ютері з ОС Windows, на якому встановлено антивірус, останній може заблокувати підключення скрипта Python до порту 25 сервера SMTP. У такому разі необхідно вимкнути антивірус. Наприклад, для McAfee можна вчинити так:
![]() |
- на [1] увімкнути консоль VirusScan
- у [2] зупиняємо службу [Protection lors de l'accès]
- у [3] — служба зупинена
Файл infos.txt:
smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
Результати на екрані:
Повідомлення, прочитане поштовим клієнтом Thunderbird:
![]() |
11.4. Другий SMTP-клієнт
Цей другий скрипт виконує ті самі дії, що й попередній, але використовує функції модуля [smtplib].
# -*- coding=utf-8 -*-
import sys,socket, smtplib
#-----------------------------------------------------------------------
def getInfos(fichier):
# повертає інформацію (SMTP, відправник, одержувач, повідомлення), взяту з текстового файлу [fichier]
# рядок 1: SMTP, відправник, одержувач
# наступні рядки: текст повідомлення
# відкриття файлу [fichier]
erreur=""
try:
infos=open(fichier,"r")
except IOError, erreur:
return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
# читання першого рядка
ligne=infos.readline()
# видалення позначки кінця рядка
ligne=cutNewLineChar(ligne)
# витяг полів smtp, відправник, одержувач
champs=ligne.split(",")
# чи правильна кількість полів?
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# «обробка» отриманої інформації — видалення «пробілів», що передують або йдуть за нею
for i in range(3):
champs[i]=champs[i].strip()
# витяг полів
(smtpServer,expediteur,destinataire)=champs
# читання повідомлення, яке потрібно надіслати
message=""
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# повернення
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# надсилає повідомлення на SMTP-сервер smtpserver від імені відправника
# для одержувача. Якщо verbose=True, здійснює відстеження обміну даними між клієнтом і сервером
# використовується бібліотека 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)
# кінець
return "Message envoye"
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# видаляється позначка кінця рядка [ligne], якщо вона існує
l=len(ligne)
while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
l-=1
return(ligne[0:l])
# main ----------------------------------------------------------------
# клієнт SMTP (протокол передачі SendMail), що дозволяє надсилати повідомлення
# інформація береться з файлу INFOS, що містить такі рядки
# рядок 1: smtp, відправник, одержувач
# наступні рядки: текст повідомлення
# відправник: електронна адреса відправника
# адресат: електронна адреса адресата
# smtp: назва сервера smtp, який слід використовувати
# протокол зв’язку SMTP «клієнт-сервер»
# -> клієнт підключається до порту 25 сервера SMTP
# <- сервер надсилає йому привітальне повідомлення
# -> клієнт надсилає команду EHLO: ім’я свого комп’ютера
# <- сервер відповідає OK або ні
# -> клієнт надсилає команду mail from: <відправник>
# <- сервер відповідає OK або ні
# -> клієнт надсилає команду rcpt to: <адресат>
# <- сервер відповідає OK або ні
# -> клієнт надсилає команду data
# <- сервер відповідає OK або ні
# -> клієнт надсилає всі рядки свого повідомлення і завершує його рядком, що містить лише один символ .
# <- сервер відповідає OK або ні
# -> клієнт надсилає команду quit
# <- сервер відповідає OK або ні
# відповіді сервера мають вигляд xxx текст, де xxx — це трицифрове число. Будь-яке число xxx >=500
# вказує на помилку. Відповідь може містити кілька рядків, які починаються з xxx-, за винятком останнього
# у форматі xxx (пробіл)
# обмінювані рядки тексту повинні закінчуватися символами RC(#13) та LF(#10)
# # параметри відправлення листа
MAIL="mail2.txt"
# отримуємо параметри листа
res=getInfos(MAIL)
# помилка?
if res[0]:
print "%s" % (erreur)
sys.exit()
# відправлення листа в режимі детального виведення
(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)
# кінець
sys.exit()
Примітки:
- цей скрипт ідентичний попередньому, за винятком функції sendmail. Ця функція тепер використовує можливості модуля [smtplib] (рядок 3).
Файл 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
Результати на екрані:
11.5. Клієнт / сервер відлуння
Створюємо службу відлуння. Сервер повертає великими літерами всі рядки тексту, які йому надсилає клієнт. Служба може обслуговувати декількох клієнтів одночасно завдяки використанню потоків.
# -*- coding=utf-8 -*-
# загальний TCP-сервер у Windows
# завантаження файлів заголовків
import re,sys,SocketServer,threading
# багатопотоковий TCP-сервер
class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):
def handle(self):
# поточний потік
cur_thread = threading.currentThread()
# дані клієнта
self.data="on"
# зупинка при порожньому ланцюжку
while self.data:
# текстові рядки клієнта зчитуються за допомогою методу readline
self.data =self.rfile.readline().strip()
# відстеження консолі
print "client %s : %s (%s)" % (self.client_address[0],self.data,cur_thread.getName())
# відправлення відповіді клієнту
response = "%s: %s" % (cur_thread.getName(), self.data.upper())
# self.wfile — потік запису до клієнта
self.wfile.write(response)
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
# ------------------------------------------------------ main
# синтаксис виклику: argv[0] порт
# сервер запущено на порту з назвою
# Дані
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- перевіряється виклик
# має бути лише один аргумент
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# порт повинен бути числовим
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)
# запуск сервера — обслуговує кожного клієнта в окремому потоці
host="localhost"
server = ThreadedTCPServer((host,int(port)), ThreadedTCPRequestHandler)
# сервер запускається в окремому потоці
# кожен клієнт обслуговуватиметься в окремому потоці
server_thread = threading.Thread(target=server.serve_forever)
# запускає сервер — нескінченний цикл очікування клієнтів
server_thread.start()
# спостереження
print "Serveur d'echo a l'ecoute sur le port % s" % (port)
Примітки:
- рядок 39: sys.argv — це параметри скрипта. Вони повинні мати такий вигляд: nom_du_script порт. Отже, їх має бути два. sys.argv[0] тоді стане nom_du_script, а sys.argv[1] — port ;
- рядок 53: сервер відлуння є екземпляром класу ThreadedTcpServer. Конструктор цього класу очікує два параметри:
- параметр 1: кортеж із двох елементів (host, port), що визначає комп’ютер та порт, на якому сервер приймає запити;
- параметр 2: ім’я класу, відповідального за обробку запитів клієнта.
- рядок 57: створюється потік (але ще не запускається). Цей потік виконує метод [serve_forever] сервера TCP. Цей метод — це цикл, що очікує на підключення клієнтів. Як тільки буде виявлено клієнтське з’єднання, буде створено екземпляр класу ThreadedTCPRequestHandler. Його метод handle відповідає за взаємодію з клієнтом;
- рядок 59: запускається потік служби відлуння. З цього моменту клієнти можуть підключатися до служби;
- рядок 27: клас сервера ехо. Він походить від двох класів: SocketServer.ThreadingMixIn та SocketServer.TCPServer. Це робить його багатопотоковим сервером TCP: сервер виконується в одному потоці, а кожен клієнт обслуговується в окремому додатковому потоці;
- рядок 9: клас, що обробляє запити клієнтів. Він походить від класу SocketServer.StreamRequestHandler. Таким чином, він успадковує два атрибути:
- rfile: це потік читання даних, надісланих клієнтом — його можна розглядати як текстовий файл;
- wfile: це потік запису, що дозволяє надсилати дані клієнту — його можна обробляти як текстовий файл.
- рядок 11: метод handle обробляє запити клієнтів;
- рядок 13: потік, що виконує цей метод;
- рядок 17: цикл обробки запитів клієнта. Цикл завершується, коли клієнт надсилає порожній рядок;
- рядок 19: читання запиту клієнта;
- рядок 21: self.client_address[0] — це адреса клієнта IP. cur_thread.getName() — це ім’я потоку, що виконує метод handle;
- рядок 23: відповідь клієнту складається з двох частин — імені потоку, що обслуговує клієнта, та команди, яку він надіслав, написаної великими літерами.
# -*- coding=utf-8 -*-
import re,sys,socket
# ------------------------------------------------------ main
# загальний TCP-клієнт
# синтаксис виклику: argv[0] хост порт
# клієнт підключається до служби відлуння (хост, порт)
# сервер повертає рядки, введені клієнтом
# синтаксис
syntaxe="syntaxe : %s hote port" % (sys.argv[0])
#------------------------------------- перевіряється виклик
# має бути два аргументи
nbArguments=len(sys.argv)
if nbArguments!=3:
print syntaxe
sys.exit(1)
# отримуємо аргументи
hote=sys.argv[1]
# порт повинен бути числовим
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:
# підключення клієнта до сервера
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:
# цикл введення даних
ligne=raw_input("Commande (rien pour arreter): ").strip()
while ligne!="":
# відправляємо рядок на сервер
connexion.send("%s\n" % (ligne))
# очікування відповіді
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:
# завершення з'єднання
connexion.close()
Сервер запускається у першому командному вікні:
Перший клієнт запускається у другому вікні:
Клієнт отримує у відповідь на команду, яку він надсилає серверу, цю саму команду, написану великими літерами. Другий клієнт запускається у третьому вікні:
Консоль сервера виглядає наступним чином:
Клієнти дійсно обслуговуються в різних потоках. Щоб зупинити клієнта, достатньо ввести порожню команду.
11.6. Загальний TCP-сервер
Ми пропонуємо написати скрипт на Python, який
- буде TCP-сервером, здатним обслуговувати по одному клієнту за раз,
- приймав би текстові рядки, надіслані клієнтом,
- приймав текстові рядки з клавіатури, які надсилаються у відповідь клієнту.
Таким чином, саме користувач за клавіатурою виконує роль сервера:
- він бачить на своїй консолі текстові рядки, надіслані клієнтом;
- він відповідає клієнту, вводячи відповідь з клавіатури.
Таким чином, він може адаптуватися до будь-якого типу клієнта. Саме тому його називатимуть «універсальним TCP-сервером». Це зручний інструмент для вивчення протоколів зв’язку TCP. У наведеному нижче прикладі клієнтом TCP буде веб-браузер, що дозволить нам вивчити протокол HTTP, який використовують веб-клієнти.
![]() |
# -*- coding=utf-8 -*-
# загальний TCP-сервер
# завантаження файлів заголовків
import re,sys,SocketServer,threading
# загальний TCP-сервер
class MyTCPHandler(SocketServer.StreamRequestHandler):
def handle(self):
# відображення клієнта
print "client %s" % (self.client_address[0])
# створено потік для зчитування команд клієнта
thread_lecture = threading.Thread(target=self.lecture)
thread_lecture.start()
# зупинка за командою «bye»
# читання команди, введеної з клавіатури
cmde=raw_input("--> ")
while cmde!="bye":
# відправлення команди клієнту. self.wfile — це потік запису до клієнта
self.wfile.write("%s\n" % (cmde))
# читання наступної команди
cmde=raw_input("--> ")
def lecture(self):
# виводимо всі рядки, надіслані клієнтом, до отримання команди «bye»
ligne=""
while ligne!="bye":
# текстові рядки від клієнта зчитуються за допомогою методу readline
ligne = self.rfile.readline().strip()
# відстеження консолі
print "<--- %s : %s" % (self.client_address[0], ligne)
# ------------------------------------------------------ main
# синтаксис виклику: argv[0] порт
# сервер запускається на вказаному порту
# Дані
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- перевіряється виклик
# має бути лише один аргумент
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# порт повинен бути числовим
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)
# запуск сервера
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()
Примітки:
- рядок 57: сервером TCP буде екземпляр класу SocketServer.TCPServer. Його конструктор приймає два параметри:
- перший параметр — це кортеж із двох елементів (host, port), де host — це машина, на якій працює служба (зазвичай localhost), а port — — порт, на якому служба очікує (прислухається) на запити клієнтів;
- другий параметр визначає клас обслуговування клієнта. Коли клієнт підключається, створюється екземпляр класу обслуговування, і саме його метод handle повинен керувати з’єднанням із клієнтом.
TCP-сервер SocketServer.TCPServer не є багатопотоковим. Він обслуговує лише одного клієнта за раз;
- рядок 59: виконується метод serve_forever сервера Tcp. Це нескінченний цикл очікування клієнтів;
- рядок 9: сервер TCP тут є класом, похідним від класу SocketServer.StreamRequestHandler. Це дозволяє розглядати потоки даних, що обмінюються з клієнтом, як текстові файли. Ми вже зустрічали цей клас. У нашому розпорядженні є методи:
- readline для читання рядка тексту, що надходить від клієнта;
- write — для відправки йому текстових рядків.
- рядок 12: self.client_address[0] — це IP-адреса клієнта;
- рядок 14: сервер TCP буде взаємодіяти з клієнтом за допомогою двох потоків
- потоку для читання рядків від клієнта;
- потік для запису рядків клієнту.
- рядок 14: створюється потік для читання рядків від клієнта. Його параметр target визначає метод, який виконуватиме потік. Це той метод, що визначений у рядку 25;
- рядок 25: запускається потік читання;
- рядки 25–32: метод, що виконується потоком читання;
- рядок 28: потік читання зчитує всі рядки тексту, надіслані клієнтом, до отримання рядка «bye»;
- рядок 18: ми перебуваємо у потоці запису для клієнта. Принцип полягає в тому, щоб надсилати клієнту всі рядки тексту, введені користувачем з клавіатури.
Сервер
Браузер клієнта
Запит, отриманий сервером
Відповідь сервера, введена користувачем з клавіатури (без знака -->)
- рядки 1–5: відповідь HTTP, надіслана клієнту;
- рядок 6: HTML-сторінка, надіслана клієнту;
- рядок 7: кінець діалогу з клієнтом. Обслуговування клієнта завершиться, і з’єднання буде закрито, що різко перерве потік читання рядків тексту, надісланих клієнтом;
- рядки 1–5 HTTP-відповіді, надісланої клієнту, мають таке значення:
- рядок 1: ресурс, запитуваний клієнтом, було знайдено;
- рядок 2: ідентифікація сервера;
- рядок 3: сервер закриє з’єднання після надсилання ресурсу;
- рядок 4: тип ресурсу, надісланого сервером: HTML-документ;
- рядок 5: порожній рядок.
Сторінка, що відображається браузером [1]:
![]() |
Якщо переглянути вихідний код, отриманий браузером [2], можна побачити, що код HTML, отриманий веб-браузером, дійсно є тим, який йому було надіслано.




