Skip to content

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.

النتائج

1
2
3
4
5
6
7
8
9
ip[istia.univ-angers.fr]=193.49.146.171
name[193.49.146.171]=istia.istia.univ-angers.fr
ip[www.univ-angers.fr]=193.49.144.40
name[193.49.144.40]=ametys-fo.univ-angers.fr
ip[www.ibm.com]=129.42.58.216
name[129.42.58.216]=[Errno 11004] host not found
ip[localhost]=127.0.0.1
name[127.0.0.1]=Gportpers3.ad.univ-angers.fr
ip[xx]=[Errno 11004] getaddrinfo failed

11.2. عميل ويب

نص برمجي لاسترداد محتوى الصفحة الرئيسية لموقع ويب.


البرنامج (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

يتم تشغيل العميل الأول في نافذة ثانية:

1
2
3
4
5
6
7
cmd>%python% inet_06.py localhost 100
Commande (rien pour arreter): cmde 1 du client 1
[cmde 1 du client 1]
<-- Thread-2: CMDE 1 DU CLIENT 1
Commande (rien pour arreter): cmde 2 du client 1
<-- Thread-2: CMDE 2 DU CLIENT 1
Commande (rien pour arreter):

يتلقى العميل، رداً على الأمر الذي يرسله إلى الخادم، نفس الأمر بأحرف كبيرة. يتم تشغيل عميل ثانٍ في نافذة ثالثة:

1
2
3
4
5
6
cmd>%python% inet_06.py localhost 100
Commande (rien pour arreter): cmde 1 du client 2
<-- Thread-3: CMDE 1 DU CLIENT 2
Commande (rien pour arreter): cmde 2 du client 2
<-- Thread-3: CMDE 2 DU CLIENT 2
Commande (rien pour arreter):

تبدو وحدة تحكم الخادم عندئذٍ كما يلي:

1
2
3
4
5
6
cmd>%python% inet_08.py 100
Serveur d'echo a l'ecoute sur le port 100
client 127.0.0.1 : cmde 1 du client 1 (Thread-2)
client 127.0.0.1 : cmde 2 du client 1 (Thread-2)
client 127.0.0.1 : cmde 1 du client 2 (Thread-3)
client 127.0.0.1 : cmde 2 du client 2 (Thread-3)

يتم خدمة العملاء بشكل صحيح في خيوط مختلفة. لإيقاف عميل، ما عليك سوى إدخال أمر فارغ.

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 :

استجابة الخادم التي كتبها المستخدم على لوحة المفاتيح (بدون علامة -->)

1
2
3
4
5
6
7
HTTP/1.1 200 OK
--> Server: serveur tcp generique
--> Connection: close
--> Content-Type: text/html
-->
--> <html><body><h2>Serveur tcp generique</h2></body></html>
--> bye
  • الأسطر 1-5: استجابة HTTP مرسلة إلى العميل؛
  • السطر 6: صفحة HTML مرسلة إلى العميل؛
  • السطر 7: نهاية الحوار مع العميل. ستنتهي خدمة العميل وسيتم إغلاق الاتصال، مما سيؤدي إلى مقاطعة مفاجئة للخيط الذي يقرأ سطور النص المرسلة من العميل؛
  • السطور 1-5 من استجابة HTTP المرسلة إلى العميل لها المعاني التالية:
  • السطر 1: تم العثور على المورد الذي طلبه العميل؛
  • السطر 2: تعريف الخادم؛
  • السطر 3: سيقوم الخادم بإغلاق الاتصال بعد إرسال المورد؛
  • السطر 4: نوع المورد الذي أرسله الخادم: مستند HTML؛
  • السطر 5: سطر فارغ.

الصفحة المعروضة بواسطة المتصفح [1]:

إذا اطلعنا على شفرة المصدر التي استلمها المتصفح [2]، فسنلاحظ أن شفرة HTML التي استلمها متصفح الويب هي بالفعل الشفرة التي أرسلناها إليه.