Skip to content

11. تابع‌های شبکه پایتون

اکنون به توابع شبکه‌ای پایتون می‌پردازیم که به ما امکان برنامه‌نویسی TCP / IP (پروتکل کنترل انتقال / پروتکل اینترنت) را می‌دهند.

  

11.1. به‌دست‌آوردن نام یا نشانی IP یک ماشین در اینترنت


برنامه‌نویسی (inet_01)


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


# ---------------------------------------- اصلی

# ثوابت
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()

یادداشت‌ها:

  • خط ۱: توابع شبکه پایتون در ماژول 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. یک کلاینت وب

یک اسکریپت برای بازیابی محتوای پاراگراف «index» در یک وب‌سایت.


برنامه (inet_02)


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)

    # باز کردن یک اتصال روی پورت ۸۰ سایت
    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
    # سربرگ‌های پروتکل 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)

# --------------------------------------------- اصلی    

#بازیابی متن 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()

یادداشت‌ها:

  • خط ۵۷: فهرست آدرس‌های وب‌سایتی که می‌خواهید صفحهٔ فهرست (index) آن‌ها را بازیابی کنید. این فهرست در فایل متنی [nomsite.html] ذخیره شده است؛
  • خط ۶۲: تابع getIndex این کار را انجام می‌دهد؛
  • خط ۴: تابع getIndex;
  • خط ۲۰: متد create_connection((site,port)) برای برقراری ارتباط با یک سرویس TCP / IP که روی پورت port در ماشین 'site' در حال اجرا است، استفاده می‌شود؛
  • خط ۳۵: روش send برای ارسال داده از طریق اتصال TCP / IP استفاده می‌شود. در این مورد، متن ارسال می‌شود. این متن از پروتکل HTTP (پروتکل انتقال HyperText) پیروی می‌کند؛
  • خط ۴۰: روش recv برای دریافت داده از طریق اتصال TCP / IP استفاده می‌شود. در اینجا پاسخ وب‌سرور به صورت بلوک‌های ۱۰۰۰ کاراکتری خوانده شده و در فایل متنی [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]:

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>
  • خطوط ۱–۱۱ سربرگ‌های HTTP از پاسخ سرور هستند؛
  • خط ۱: سرور به کلاینت دستور می‌دهد تا به URL مشخص‌شده در خط ۸ هدایت کند؛
  • خط ۲: تاریخ و زمان پاسخ؛
  • خط ۳: شناسه‌ی سرور وب؛
  • خط ۴: محتوایی که توسط سرور ارسال می‌شود. در این مورد، صفحه‌ای HTML که از خط ۱۳ آغاز می‌شود؛
  • خط ۱۲: خط خالی پایان‌دهنده سربرگ‌ها؛
  • خطوط ۱۳–۱۹: صفحه‌ی HTML ارسال‌شده توسط وب‌سرور.

11.3. یک کلاینت SMTP

در میان پروتکل‌های TCP / IP، SMTP (پروتکل انتقال SendMail) پروتکل ارتباطی برای سرویس ارسال پیام است.


برنامه (inet_03)


# -*- coding=utf-8 -*-

import sys,socket

#-----------------------------------------------------------------------
def getInfos(fichier):
    #اطلاعات (SMTP، فرستنده، گیرنده، پیام) را که از فایل متنی [fichier] گرفته شده است، بازمی‌گرداند.
    #خط ۱: 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 باشد، تعاملات کلاینت-سرور را ثبت می‌کند

    # نام کلاینت را بازیابی می‌کند
    try:
        client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
    except socket.error, erreur:
        return "Erreur IP / Nom du client : %s" % (erreur)
    
    # یک اتصال روی پورت ۲۵ از 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 به:
    erreur=sendCommand(connexion,"RCPT TO: <%s>" % (destinataire),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # دستور داده
    erreur=sendCommand(connexion,"DATA",verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # در حال آماده‌سازی پیام برای ارسال
    # باید شامل خطوط زیر باشد
    # از: فرستنده
    # به: گیرنده
    # خط خالی
    # پیام
    # .
    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)
        # خواندن پاسخ با کمتر از ۱۰۰۰ کاراکتر
        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 باشد، -->transaction را نمایش می‌دهد
    # اگر direction=2 باشد، نمایش <-- exchange بدون دو کاراکتر آخر 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 که شامل خطوط زیر است، گرفته می‌شود
# خط ۱: SMTP، فرستنده، گیرنده
# خطوط بعدی: متن پیام

# فرستنده: آدرس ایمیل فرستنده
#گیرنده: ایمیل گیرنده
# SMTP: نام سرور SMTP مورد استفاده


#پروتکل ارتباطی: SMTP کلاینت-سرور
# -> کلاینت به پورت ۲۵ سرور SMTP متصل می‌شود
# <- سرور یک پیام خوش‌آمدگویی برای کلاینت ارسال می‌کند
# -> کلاینت فرمان EHLO را ارسال می‌کند: نام میزبان آن
#<- سرور با OK پاسخ می‌دهد یا خیر
# -> کلاینت فرمان «mail from: <sender>» را ارسال می‌کند
# <- سرور با OK پاسخ می‌دهد یا خیر
# -> مشتری فرمان rcpt را به: <recipient> ارسال می‌کند
#<- سرور با OK پاسخ می‌دهد یا خیر
#-> کلاینت فرمان 'data' را ارسال می‌کند
# <- سرور با OK پاسخ می‌دهد یا خیر
#-> کلاینت تمام خطوط پیام خود را ارسال می‌کند و با یک خط حاوی یک کاراکتر به پایان می‌رسد.
# <- سرور با OK پاسخ می‌دهد یا خیر
# -> کلاینت فرمان 'quit' را ارسال می‌کند
# <- سرور با OK پاسخ می‌دهد یا خیر

#پاسخ‌های سرور به شکل xxx متن هستند، که در آن xxx یک عدد سه‌رقمی است. هر عددی که xxx ≥ ۵۰۰ باشد
# نشان‌دهنده خطا است. پاسخ ممکن است شامل چندین خط باشد که همه با 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()

یادداشت‌ها:

  • روی یک سیستم ویندوزی با نرم‌افزار آنتی‌ویروس، ممکن است آنتی‌ویروس از اتصال اسکریپت پایتون به پورت ۲۵ یک سرور 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

خروجی صفحه:

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):
    #اطلاعات (SMTP، فرستنده، گیرنده، پیام) را که از فایل متنی [fichier] استخراج شده، بازمی‌گرداند.
    # خط ۱: 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 که شامل خطوط زیر است، گرفته می‌شود
# خط ۱: SMTP، فرستنده، گیرنده
# خطوط بعدی: متن پیام

# ارسال‌کننده: آدرس ایمیل ارسال‌کننده
#گیرنده: آدرس ایمیل گیرنده
# SMTP: نام سرور SMTP مورد استفاده


# SMTP پروتکل ارتباطی کلاینت-سرور
# -> کلاینت به پورت ۲۵ سرور SMTP متصل می‌شود
# <- سرور یک پیام خوش‌آمدگویی برای کلاینت ارسال می‌کند
# -> کلاینت فرمان EHLO را ارسال می‌کند: نام میزبان آن
#<- سرور با OK پاسخ می‌دهد یا خیر
# -> کلاینت فرمان «mail from: <sender>» را ارسال می‌کند
#<- سرور با OK پاسخ می‌دهد یا خیر
#-> مشتری فرمان 'rcpt to: <recipient>' را ارسال می‌کند
#<- سرور با OK پاسخ می‌دهد یا خیر
# -> کلاینت فرمان 'data' را ارسال می‌کند
# <- سرور با OK پاسخ می‌دهد یا خیر
#-> کلاینت تمام خطوط پیام خود را ارسال می‌کند و با یک خط حاوی یک کاراکتر به پایان می‌رسد.
# <- سرور با OK پاسخ می‌دهد یا خیر
# -> کلاینت فرمان 'quit' را ارسال می‌کند
# <- سرور با OK پاسخ می‌دهد یا خیر

#پاسخ‌های سرور به شکل xxx text هستند، که در آن xxx یک عدد سه‌رقمی است. هر عددی که xxx ≥ ۵۰۰ باشد
# نشان‌دهنده خطا است. پاسخ ممکن است شامل چندین خط باشد که همه با 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] (خط ۳) استفاده می‌کند.

نتایج

فایل 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. کلاینت/سرور اکو

یک سرویس اکو ایجاد شده است. سرور تمام خطوط متنی را که توسط کلاینت ارسال می‌شود، به صورت حروف بزرگ بازمی‌گرداند. این سرویس می‌تواند با استفاده از نخ‌ها (threads) به طور همزمان به چندین کلاینت خدمت‌رسانی کند.


برنامه سرور (inet_08)


# -*- coding=utf-8 -*-

# سرور عمومی TCP در ویندوز

# بارگذاری فایل‌های سرآیند
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)

یادداشت‌ها:

  • خط ۳۹: sys.argv نشان‌دهنده پارامترهای اسکریپت است. در اینجا، آنها باید به شکل: nom_du_script پورت باشند. بنابراین باید دو عدد از آنها وجود داشته باشد. sys.argv[0] سپس به nom_du_script تبدیل می‌شود و sys.argv[1] به port تبدیل می‌شود ;
  • خط ۵۳: سرور اکو نمونه‌ای از کلاس ThreadedTcpServer است. سازنده این کلاس دو پارامتر می‌گیرد:
    • پارامتر ۱: یک تپل دو عنصری (میزبان، پورت) که ماشین و پورت گوش‌دادن سرور را مشخص می‌کند؛
    • پارامتر ۲: نام کلاسی که مسئول رسیدگی به درخواست‌های مشتری است.
  • خط ۵۷: یک تِرد ایجاد می‌شود (اما هنوز شروع نشده است). این تِرد متد [serve_forever] از سرور TCP را اجرا می‌کند. این متد یک حلقه است که منتظر اتصالات کلاینت می‌ماند. به محض تشخیص اتصال مشتری، یک نمونه از کلاس ThreadedTCPRequestHandler ایجاد خواهد شد. متد handle آن مسئول برقراری ارتباط با مشتری است؛
  • خط ۵۹: نخ سرویس اکو راه‌اندازی می‌شود. از این نقطه به بعد، کلاینت‌ها می‌توانند به سرویس متصل شوند؛
  • خط ۲۷: کلاس سرور اکو. این کلاس از دو کلاس SocketServer.ThreadingMixIn و SocketServer.TCPServer ارث می‌برد. این باعث می‌شود که یک سرور چندرشته‌ای TCP باشد: سرور در یک رشته اجرا می‌شود و به هر کلاینت در یک رشته اضافی خدمت‌رسانی می‌شود؛
  • خط ۹: کلاسی که درخواست‌های مشتری را پردازش می‌کند. این کلاس از کلاس SocketServer.StreamRequestHandler ارث می‌برد. بنابراین دو ویژگی را به ارث می‌برد:
    • rfile: که جریان خواندن داده‌های ارسال‌شده توسط کلاینت است – می‌توان آن را به‌عنوان یک فایل متنی در نظر گرفت؛
    • wfile: که جریان خروجی است که برای ارسال داده‌ها به کلاینت استفاده می‌شود – می‌توان آن را به‌عنوان یک فایل متنی در نظر گرفت.
  • خط ۱۱: متد handle درخواست‌های مشتری را مدیریت می‌کند؛
  • خط ۱۳: تریدی که این متد را اجرا می‌کند 'handle' است؛
  • خط ۱۷: حلقه برای پردازش درخواست‌های مشتری. این حلقه زمانی پایان می‌یابد که مشتری یک خط خالی ارسال کند؛
  • خط ۱۹: خواندن درخواست مشتری؛
  • خط ۲۱: self.client_address[0] نشان‌دهنده آدرس مشتری IP است. cur_thread.getName() نام تریدی است که متد handle را اجرا می‌کند؛
  • خط ۲۳: پاسخ به مشتری دو مؤلفه دارد – نام رشته‌ای که به مشتری سرویس می‌دهد و دستوری که مشتری ارسال کرده است، به حروف بزرگ.

برنامهٔ مشتری (inet_06)


# -*- coding=utf-8 -*-
import re,sys,socket

# ------------------------------------------------------ اصلی        
# کلاینت عمومی TCP
# دستور فراخوانی: argv[0] host port
# کلاینت به سرویس اکو (میزبان، پورت) متصل می‌شود
# سرور خطوط تایپ‌شده توسط کلاینت را بازمی‌گرداند

# دستور زبان
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()

نتایج

سرور در یک پنجرهٔ فرمان اولیه راه‌اندازی می‌شود:

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)

کلاینت‌ها در واقع در نخ‌های (thread) مختلف اجرا می‌شوند. برای متوقف کردن یک کلاینت، کافی است یک دستور خالی وارد کنید.

11.6. سرور عمومی TCP

ما پیشنهاد می‌کنیم یک اسکریپت پایتون بنویسیم که

  • به عنوان یک سرور TCP عمل می‌کند که قادر است به طور همزمان به یک مشتری خدمت‌رسانی کند،
  • خطوط متنی ارسال‌شده توسط کلاینت را می‌پذیرد،
  • خطوط متنی را از صفحه‌کلید می‌پذیرد و آن‌ها را به کلاینت بازمی‌فرستد.

به این ترتیب، کاربر پشت صفحه‌کلید نقش سرور را ایفا می‌کند:

  • آنها خطوط متنی را که توسط کلاینت ارسال می‌شود در کنسول خود می‌بینند؛
  • و با تایپ پاسخ روی صفحه‌کلید به مشتری پاسخ می‌دهد.

بنابراین می‌تواند خود را با هر نوع کلاینتی تطبیق دهد. به همین دلیل ما آن را «سرور عمومی TCP» می‌نامیم. این ابزار برای بررسی پروتکل‌های ارتباطی TCP بسیار مفید است. در مثال زیر، کلاینت TCP یک مرورگر وب خواهد بود که به ما امکان می‌دهد پروتکل HTTP مورد استفاده کلاینت‌های وب را بررسی کنیم.


برنامه (inet_10)


# -*- 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] port
# سرور روی پورت مشخص‌شده راه‌اندازی می‌شود

# داده‌ها
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()

یادداشت‌ها:

  • خط ۵۷: سرور TCP یک نمونه از کلاس SocketServer.TCPServer خواهد بود. سازنده آن دو پارامتر می‌گیرد:
    • پارامتر اول یک تپل دو عنصری (host, port) است که در آن host ماشینی است که سرویس روی آن اجرا می‌شود (معمولاً localhost) و port، پورتی که سرویس در آن منتظر درخواست‌های مشتری می‌ماند (گوش می‌دهد)؛
    • پارامتر دوم کلاس سرویس کلاینت را مشخص می‌کند. هنگامی که یک کلاینت متصل می‌شود، یک نمونه از کلاس سرویس ایجاد می‌شود و این متد نمونه است که باید اتصال با کلاینت را مدیریت کند.

سرور TCP SocketServer.TCPServer چندرشته‌ای نیست. این سرور به طور همزمان به یک مشتری خدمات ارائه می‌دهد؛

  • خط ۵۹: متد serve_forever سرور TCP اجرا می‌شود. این یک حلقه بی‌نهایت است که منتظر کلاینت‌ها می‌ماند؛
  • خط ۹: سرور TCP در اینجا کلاسی است که از کلاس SocketServer.StreamRequestHandler ارث‌بری شده است. این امکان را می‌دهد که جریان‌های داده‌ای که با کلاینت مبادله می‌شوند، به‌عنوان فایل‌های متنی در نظر گرفته شوند. ما قبلاً با این کلاس آشنا شده‌ایم. متدهای زیر را داریم:
    • readline برای خواندن یک خط متن از کلاینت؛
    • write برای ارسال خطوط متنی به سمت کلاینت؛
  • خط ۱۲: self.client_address[0] آدرس IP کلاینت است؛
  • خط ۱۴: سرور TCP با استفاده از دو تِرد با کلاینت ارتباط برقرار می‌کند
    • یک نخ برای خواندن خطوط از کلاینت؛
    • یک تِرد برای نوشتن خطوط به کلاینت.
  • خط ۱۴: نخ خواندن خطوط از کلاینت ایجاد می‌شود. پارامتر آن target متدی را که باید توسط نخ اجرا شود مشخص می‌کند. این همان متدی است که در خط ۲۵ تعریف شده است؛
  • خط ۲۵: نخ خواندن راه‌اندازی می‌شود؛
  • خطوط ۲۵–۳۲: متدی که توسط نخ خواندن اجرا می‌شود؛
  • خط ۲۸: نخ خواندن تمام خطوط متنی ارسال‌شده توسط کلاینت را تا دریافت خط «bye» می‌خواند؛
  • خط ۱۸: این ترید است که به کلاینت می‌نویسد. اصل کار این است که تمام خطوط متنی را که کاربر از روی صفحه‌کلید تایپ می‌کند، برای کلاینت ارسال کند.

نتایج

سرور

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
  • خطوط ۱–۵: پاسخ HTTP ارسال‌شده به کلاینت؛
  • خط ۶: صفحهٔ HTML ارسال‌شده به کلاینت؛
  • خط ۷: پایان تعامل با کلاینت. سرویس کلاینت متوقف شده و اتصال بسته خواهد شد، که باعث قطع ناگهانی نخ (thread) خواندن خطوط متنی ارسال‌شده توسط کلاینت می‌شود؛
  • خطوط ۱–۵ از پاسخ HTTP ارسال‌شده به کلاینت معانی زیر را دارند:
  • خط ۱: منبعی که توسط کلاینت درخواست شده است یافت شده است؛
  • خط ۲: شناسایی سرور؛
  • خط ۳: سرور پس از ارسال منبع، اتصال را قطع خواهد کرد؛
  • خط ۴: نوع منبعی که توسط سرور ارسال شده است: یک سند HTML؛
  • خط ۵: یک خط خالی.

صفحه‌ای که توسط مرورگر [1] نمایش داده می‌شود:

اگر کد منبع دریافتی توسط مرورگر [2] را مشاهده کنیم، می‌توانیم ببینیم که کدی که مرورگر وب HTML دریافت کرده، دقیقاً همان چیزی است که ما برای آن ارسال کردیم.