11. وظائف الشبكة في Python
سنناقش الآن وظائف الشبكة في بايثون، والتي تسمح لنا بتنفيذ برمجة TCP/IP (بروتوكول التحكم في الإرسال/بروتوكول الإنترنت).
11.1. الحصول على اسم أو عنوان IP لجهاز على الإنترنت
البرنامج (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()
|
ملاحظات:
- السطر 1: وظائف الشبكة في Python مغلفة في وحدة socket.
النتائج
| 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. عميل ويب
نص برمجي لاسترداد محتوى الصفحة الرئيسية لموقع ويب.
البرنامج (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()
|
ملاحظات:
- السطر 57: قائمة عناوين URL للمواقع الإلكترونية التي نريد صفحاتها الرئيسية. يتم تخزين هذه القائمة في الملف النصي [sitename.html]؛
- السطر 62: تقوم الدالة getIndex بالمهمة؛
- السطر 4: دالة getIndex؛
- السطر 20: تسمح لك طريقة create_connection((site,port)) بإنشاء اتصال بخدمة TCP/IP تعمل على المنفذ port على جهاز الموقع؛
- السطر 35: تسمح طريقة send بإرسال البيانات عبر اتصال TCP/IP. هنا، يتم إرسال نص. يتبع هذا النص بروتوكول HTTP (بروتوكول نقل النص التشعبي)؛
- السطر 40: تُستخدم طريقة recv لاستقبال البيانات عبر اتصال TCP/IP. هنا، يتم قراءة استجابة خادم الويب في كتل مكونة من 1000 حرف وحفظها في الملف النصي [sitename.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]:
| 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>
|
- الأسطر 1–11 هي رؤوس HTTP لاستجابة الخادم؛
- السطر 1: يوجه الخادم العميل إلى إعادة التوجيه إلى عنوان URL المحدد في السطر 8؛
- السطر 2: تاريخ ووقت الاستجابة؛
- السطر 3: هوية خادم الويب؛
- السطر 4: المحتوى المرسل من الخادم. هنا، صفحة HTML تبدأ في السطر 13؛
- السطر 12: السطر الفارغ الذي ينهي رؤوس HTTP؛
- الأسطر 13-19: صفحة HTML المرسلة من خادم الويب.
11.3. عميل SMTP
من بين بروتوكولات TCP/IP، يعد SMTP (بروتوكول نقل البريد البسيط) بروتوكول الاتصال لخدمة توصيل الرسائل.
البرنامج (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()
|
ملاحظات:
- على جهاز يعمل بنظام Windows ومزود ببرنامج مكافحة فيروسات، قد يمنع برنامج مكافحة الفيروسات البرنامج النصي Python من الاتصال بالمنفذ 25 لخادم SMTP. لذلك يجب تعطيل برنامج مكافحة الفيروسات. بالنسبة لبرنامج McAfee، على سبيل المثال، يمكنك القيام بما يلي:
- في [1]، افتح وحدة تحكم VirusScan
- في [2]، أوقف خدمة [On-Access Protection]
- في [3]، تم إيقافها
النتائج
ملف infos.txt:
smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
نتائج الشاشة:
| 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
|
الرسالة كما تظهر في عميل البريد الإلكتروني Thunderbird:
11.4. عميل SMTP ثانٍ
يقوم هذا البرنامج النصي الثاني بنفس وظيفة البرنامج النصي السابق، لكنه يستخدم ميزات وحدة [smtplib].
البرنامج (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()
|
ملاحظات:
- هذا البرنامج النصي مطابق للبرنامج النصي السابق باستثناء وظيفة 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
نتائج الشاشة:
| 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. صدى العميل/الخادم
نقوم بإنشاء خدمة إيكو. يقوم الخادم بإرجاع جميع الأسطر النصية المرسلة من العميل بأحرف كبيرة. يمكن للخدمة خدمة عدة عملاء في وقت واحد باستخدام الخيوط.
برنامج الخادم (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)
|
ملاحظات:
- السطر 39: sys.argv يمثل حجج البرنامج النصي. هنا، يجب أن تكون في الصيغة: اسم_البرنامج_النصي المنفذ. لذلك يجب أن يكون هناك اثنان منها. سيكون sys.argv[0] هو اسم_البرنامج_النصي و sys.argv[1] هو المنفذ؛
- السطر 53: خادم echo هو مثيل لفئة ThreadedTcpServer. يتطلب منشئ هذه الفئة معلمتين:
- المعلمة 1: مجموعة مكونة من عنصرين (host,port) تحدد الجهاز ومنفذ الاستماع للخادم؛
- المعلمة 2: اسم الفئة المسؤولة عن معالجة طلبات العملاء.
- السطر 57: يتم إنشاء مؤشر ترابط (ولكن لم يتم تشغيله بعد). يقوم مؤشر الترابط هذا بتنفيذ طريقة [serve_forever] لخادم TCP. هذه الطريقة عبارة عن حلقة تستمع لاتصالات العملاء. بمجرد اكتشاف اتصال عميل، سيتم إنشاء مثيل لفئة ThreadedTCPRequestHandler. طريقة handle الخاصة بها مسؤولة عن التواصل مع العميل؛
- السطر 59: يتم تشغيل مؤشر ترابط خدمة الصدى. من هذه النقطة فصاعدًا، يمكن للعملاء الاتصال بالخدمة؛
- السطر 27: فئة خادم echo. وهي مشتقة من فئتين:
*SocketServer.ThreadingMixIn* وSocketServer.TCPServer. وهذا يجعلها خادم TCP متعدد الخيوط: يعمل الخادم في خيط واحد، ويتم خدمة كل عميل في خيط إضافي؛
- السطر 9: الفئة التي تتعامل مع طلبات العملاء. وهي مشتقة من فئة SocketServer.StreamRequestHandler. وبالتالي، فإنها ترث سمتين:
- rfile: وهو تيار القراءة للبيانات المرسلة من العميل — يمكن التعامل معه كملف نصي؛
- wfile: وهو دفق الكتابة المستخدم لإرسال البيانات إلى العميل — يمكن التعامل معه كملف نصي.
- السطر 11: تعالج طريقة handle طلبات العميل؛
- السطر 13: الخيط الذي ينفذ طريقة handle هذه؛
- السطر 17: حلقة لمعالجة طلبات العميل. تنتهي الحلقة عندما يرسل العميل سطرًا فارغًا؛
- السطر 19: يقرأ طلب العميل؛
- السطر 21: يمثل self.client_address[0] عنوان IP للعميل. cur_thread.getName() هو اسم الخيط الذي ينفذ طريقة handle؛
- السطر 23: تتكون الاستجابة للعميل من مكونين — اسم الخيط الذي يخدم العميل والأمر الذي أرسله العميل، محولًا إلى أحرف كبيرة.
برنامج العميل (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()
|
النتائج
يتم تشغيل الخادم في نافذة الأوامر:
cmd>%python% inet_08.py 100
Serveur d'echo a l'ecoute sur le port 100
يتم تشغيل العميل الأول في نافذة ثانية:
| 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):
|
يتلقى العميل، رداً على الأمر الذي يرسله إلى الخادم، نفس الأمر بأحرف كبيرة. يتم تشغيل عميل ثانٍ في نافذة ثالثة:
| 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):
|
تبدو وحدة تحكم الخادم عندئذٍ كما يلي:
| 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)
|
يتم خدمة العملاء بشكل صحيح في خيوط مختلفة. لإيقاف عميل، ما عليك سوى إدخال أمر فارغ.
11.6. خادم TCP عام
نقترح كتابة برنامج نصي بلغة Python
- يعمل كخادم TCP قادر على خدمة عميل واحد في كل مرة،
- يقبل الأسطر النصية المرسلة من العميل،
- ويقبل الأسطر النصية من لوحة المفاتيح ويعيد إرسالها إلى العميل.
وبالتالي، يعمل المستخدم الذي يستخدم لوحة المفاتيح كخادم:
- يرى أسطر النص المرسلة من العميل على شاشة وحدة التحكم الخاصة به؛
- ويستجيب للعميل عن طريق كتابة الرد على لوحة المفاتيح.
وبالتالي، يمكنه التكيف مع أي نوع من العملاء. ولهذا السبب سنسميه "خادم TCP عام". إنه أداة عملية لاستكشاف بروتوكولات اتصال TCP. في المثال التالي، سيكون عميل TCP هو متصفح الويب، مما سيسمح لنا باستكشاف بروتوكول HTTP المستخدم من قبل عملاء الويب.
البرنامج (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()
|
ملاحظات:
- السطر 57: سيكون خادم TCP مثيلًا لفئة SocketServer.TCPServer. يقبل منشئه معلمتين:
- المعلمة الأولى عبارة عن مجموعة مكونة من عنصرين (المضيف، المنفذ)، حيث يمثل «المضيف» الجهاز الذي تعمل عليه الخدمة (عادةً ما يكون «localhost») ويمثل «المنفذ» المنفذ الذي تنتظر (تستمع) الخدمة من خلاله لطلبات العملاء؛
- تحدد المعلمة الثانية فئة خدمة العميل. عندما يتصل عميل، يتم إنشاء مثيل لفئة الخدمة، ويجب أن تدير طريقة 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: نحن الآن في الخيط الذي يكتب إلى العميل. الفكرة هي إرسال جميع أسطر النص التي كتبها المستخدم على لوحة المفاتيح إلى العميل.
النتائج
الخادم
dos>%python% inet_10.py 100
Service tcp generique lance sur le port 100. Arret par Ctrl-C
متصفح العميل
الطلب الذي تلقّاه الخادم
| 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 :
|
استجابة الخادم التي كتبها المستخدم على لوحة المفاتيح (بدون علامة -->)
| 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
|
- الأسطر 1-5: استجابة HTTP مرسلة إلى العميل؛
- السطر 6: صفحة HTML مرسلة إلى العميل؛
- السطر 7: نهاية الحوار مع العميل. ستنتهي خدمة العميل وسيتم إغلاق الاتصال، مما سيؤدي إلى مقاطعة مفاجئة للخيط الذي يقرأ سطور النص المرسلة من العميل؛
- السطور 1-5 من استجابة HTTP المرسلة إلى العميل لها المعاني التالية:
- السطر 1: تم العثور على المورد الذي طلبه العميل؛
- السطر 2: تعريف الخادم؛
- السطر 3: سيقوم الخادم بإغلاق الاتصال بعد إرسال المورد؛
- السطر 4: نوع المورد الذي أرسله الخادم: مستند HTML؛
- السطر 5: سطر فارغ.
الصفحة المعروضة بواسطة المتصفح [1]:
إذا اطلعنا على شفرة المصدر التي استلمها المتصفح [2]، فسنلاحظ أن شفرة HTML التي استلمها متصفح الويب هي بالفعل الشفرة التي أرسلناها إليه.