11. وظائف الشبكة في لغة بايثون
ننتقل الآن إلى وظائف الشبكة في لغة Python التي تتيح لنا البرمجة باستخدام بروتوكولي TCP / IP (بروتوكول التحكم في النقل / بروتوكول الإنترنت).
![]() |
11.1. الحصول على اسم أو عنوان IP لجهاز على الإنترنت
import sys, socket
#------------------------------------------------
def getIPandName(nomMachine):
#nomMachine: اسم الجهاز الذي نريد الحصول على عنوانه IP
# nomMachine-->عنوان IP
try:
ip=socket.gethostbyname(nomMachine)
print "ip[%s]=%s" % (nomMachine,ip)
except socket.error, erreur:
print "ip[%s]=%s" % (nomMachine,erreur)
return
# العنوان IP --> nomMachine
try:
name=socket.gethostbyaddr(ip)
print "name[%s]=%s" % (ip,name[0])
except socket.error, erreur:
print "name[%s]=%s" % (ip,erreur)
return
# ---------------------------------------- main
# الثوابت
HOTES=["istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","localhost","","xx"]
# عناوين IP لأجهزة HOTES
for i in range(len(HOTES)):
getIPandName(HOTES[i])
# النهاية
sys.exit()
ملاحظات:
- السطر 1: وظائف الشبكة في لغة Python مغلفة في الوحدة النمطية socket.
11.2. عميل ويب
نص برمجي يسمح بالحصول على محتوى الفقرة الرئيسية لموقع ويب.
import sys,socket
#-----------------------------------------------------------------------
def getIndex(site):
# يقرأ موقع URL ويخزنه في الملف site.html
# في البداية لم تحدث أية أخطاء
erreur=""
# إنشاء الملف site.html
try:
html=open("%s.html" % (site),"w")
except IOError, erreur:
pass
# خطأ؟
if erreur:
return "Erreur (%s) lors de la création du fichier %s.html" % (erreur,site)
# فتح اتصال على المنفذ 80 للموقع
try:
connexion=socket.create_connection((site,80))
except socket.error, erreur:
pass
# العودة في حالة حدوث خطأ
if erreur:
return "Echec de la connexion au site (%s,80) : %s" % (site,erreur)
# يمثل الاتصال تدفقًا ثنائي الاتجاه للاتصالات
# بين العميل (هذا البرنامج) وخادم الويب الذي تم الاتصال به
# تُستخدم هذه القناة لتبادل الأوامر والمعلومات
# بروتوكول الاتصال هو HTTP
# يرسل العميل الأمر get لطلب URL /
# صيغة الأمر get هي URL HTTP/1.0
# يجب أن تنتهي رؤوس (headers) بروتوكول HTTP بسطر فارغ
connexion.send("GET / HTTP/1.0\n\n")
# سيقوم الخادم الآن بالرد على قناة الاتصال. سيقوم بإرسال جميع
# بياناته ثم يغلق القناة. يقرأ العميل كل ما يصل من قناة الاتصال
# حتى يتم إغلاق القناة
ligne=connexion.recv(1000)
while(ligne):
html.write(ligne)
ligne=connexion.recv(1000)
# يقوم العميل بدوره بإغلاق الاتصال
connexion.close()
# إغلاق ملف html
html.close()
# العودة
return "Transfert reussi du paragraphe index du site %s" % (site)
# --------------------------------------------- main
# الحصول على النص HTML من URL
# قائمة المواقع الإلكترونية
SITES=("istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","xx")
# قراءة صفحات الفهرس لمواقع الجدول SITES
for i in range(len(SITES)):
# قراءة صفحة الفهرس لموقع SITES[i]
resultat=getIndex(SITES[i])
# عرض النتيجة
print resultat
# النهاية
sys.exit()
ملاحظات:
- السطر 57: قائمة عناوين URL لمواقع الويب التي نريد الحصول على صفحتها الرئيسية. يتم تخزين هذه القائمة في الملف النصي [nomsite.html]؛
- السطر 62: تقوم الدالة getIndex بهذه المهمة؛
- السطر 4: الدالة getIndex؛
- السطر 20: تسمح الطريقة create_connection((site,port)) بإنشاء اتصال بخدمة TCP / IP تعمل على المنفذ port الخاص بجهاز site؛
- السطر 35: تتيح الطريقة send إرسال البيانات عبر اتصال TCP / IP. في هذه الحالة، يتم إرسال نص. ويخضع هذا النص لبروتوكول HTTP (بروتوكول النقل HyperText)؛
- السطر 40: تتيح الطريقة recv استقبال البيانات عبر اتصال TCP / IP. هنا، تُقرأ استجابة خادم الويب على شكل كتل مكونة من 1000 حرف وتُسجل في الملف النصي [nomsite.html].
Transfert reussi du paragraphe index du site istia.univ-angers.fr
Transfert reussi du paragraphe index du site www.univ-angers.fr
Transfert reussi du paragraphe index du site www.ibm.com
Echec de la connexion au site (xx,80) : [Errno 11001] getaddrinfo failed
الملف المستلم للموقع [www.ibm.com]:
- الأسطر من 1 إلى 11 هي رؤوس HTTP من استجابة الخادم؛
- السطر 1: يطلب الخادم من العميل إعادة التوجيه إلى URL المحدد في السطر 8؛
- السطر 2: تاريخ ووقت الرد؛
- السطر 3: هوية خادم الويب؛
- السطر 4: المحتوى المرسل من الخادم. هنا صفحة HTML تبدأ من السطر 13؛
- السطر 12: السطر الفارغ الذي ينهي رؤوس HTTP؛
- الأسطر 13-19: الصفحة HTML المرسلة من خادم الويب.
11.3. عميل SMTP
من بين بروتوكولات TCP / IP، يُعد SMTP (بروتوكول النقل SendMail) بروتوكول الاتصال الخاص بخدمة إرسال الرسائل.
# -*- coding=utf-8 -*-
import sys,socket
#-----------------------------------------------------------------------
def getInfos(fichier):
# يعرض المعلومات (SMTP، المرسل، المستلم، الرسالة) المأخوذة من الملف النصي [fichier]
# السطر 1: SMTP، المرسل، المستلم
# الأسطر التالية: نص الرسالة
# فتح [fichier]
erreur=""
try:
infos=open(fichier,"r")
except IOError, erreur:
return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
# قراءة السطر الأول
ligne=infos.readline()
# حذف علامة نهاية السطر
ligne=cutNewLineChar(ligne)
# استرداد الحقول smtp، المرسل، المستلم
champs=ligne.split(",")
# هل عدد الحقول صحيح؟
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# «معالجة» المعلومات المسترجعة
# إزالة «المسافات» التي تسبق أو تتبع المعلومات المفيدة من كل حقل من الحقول الثلاثة
for i in range(3):
champs[i]=champs[i].strip()
# استرجاع الحقول
(smtpServer,expediteur,destinataire)=champs
message=""
# قراءة بقية الرسالة
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# العودة
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# إرسال الرسالة إلى خادم SMTP smtpserver نيابة عن المرسل
# إلى المستلم. إذا كانت القيمة verbose=True، يتم تتبع التبادلات بين العميل والخادم
# يتم استرداد اسم العميل
try:
client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
except socket.error, erreur:
return "Erreur IP / Nom du client : %s" % (erreur)
# فتح اتصال على المنفذ 25 لـ smtpServer
try:
connexion=socket.create_connection((smtpServer,25))
except socket.error, erreur:
return "Echec de la connexion au site (%s,25) : %s" % (smtpServer,erreur)
# يمثل الاتصال تدفقًا ثنائي الاتجاه للاتصالات
# بين العميل (هذا البرنامج) وخادم SMTP الذي تم الاتصال به
# يُستخدم هذا القناة لتبادل الأوامر والمعلومات
# بعد الاتصال، يرسل الخادم رسالة ترحيب يتم قراءتها
erreur=sendCommand(connexion,"",verbose,1)
if(erreur) :
connexion.close()
return erreur
# الأمر ehlo:
erreur=sendCommand(connexion,"EHLO %s" % (client),verbose,1)
if erreur :
connexion.close()
return erreur
# الأمر mail from:
erreur=sendCommand(connexion,"MAIL FROM: <%s>" % (expediteur),verbose,1)
if erreur :
connexion.close()
return erreur
# الأمر rcpt to:
erreur=sendCommand(connexion,"RCPT TO: <%s>" % (destinataire),verbose,1)
if erreur :
connexion.close()
return erreur
# الأمر data
erreur=sendCommand(connexion,"DATA",verbose,1)
if erreur :
connexion.close()
return erreur
# تحضير الرسالة المراد إرسالها
# يجب أن تحتوي على الأسطر التالية
# من: المرسل
# إلى: المستلم
# سطر فارغ
# الرسالة
# .
data="From: %s\r\nTo: %s\r\n%s\r\n.\r\n" % (expediteur,destinataire,message)
# إرسال الرسالة
erreur=sendCommand(connexion,data,verbose,0)
if erreur :
connexion.close()
return erreur
# أمر الخروج
erreur=sendCommand(connexion,"QUIT",verbose,1)
if erreur :
connexion.close()
return erreur
# نهاية
connexion.close()
return "Message envoye"
# --------------------------------------------------------------------------
def sendCommand(connexion,commande,verbose,withRCLF):
# إرسال الأمر إلى قناة الاتصال
# وضع التفصيل إذا كان verbose=1
# إذا كان withRCLF=1، يُضاف التسلسل RCLF إلى الأمر
# البيانات
RCLF="\r\n" if withRCLF else ""
# إرسال الأمر إذا كان الأمر غير فارغ
if commande:
connexion.send("%s%s" % (commande,RCLF))
# رد محتمل
if verbose:
affiche(commande,1)
# قراءة الرد الذي يقل عن 1000 حرف
reponse=connexion.recv(1000)
# رد محتمل
if verbose:
affiche(reponse,2)
# استرداد رمز الخطأ
codeErreur=reponse[0:3]
# هل أرسل الخادم خطأً؟
if int(codeErreur) >=500:
return reponse[4:]
# العودة دون أخطاء
return ""
# --------------------------------------------------------------------------
def affiche(echange,sens):
# عرض التبادل؟ على الشاشة
# إذا كانت القيمة sens=1، يعرض -->التبادل
# إذا كانت القيمة sens=2، يُعرض <-- التبادل بدون الحرفين الأخيرين RCLF
if sens==1:
print "--> [%s]" % (echange)
return
elif sens==2:
l=len(echange)
print "<-- [%s]" % echange[0:l-2]
return
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# يتم حذف علامة نهاية السطر من [ligne] إن وجدت
l=len(ligne)
while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
l-=1
return(ligne[0:l])
# اليد ----------------------------------------------------------------
# عميل SMTP (بروتوكول النقل SendMail) الذي يسمح بإرسال رسالة
# يتم أخذ المعلومات من ملف INFOS الذي يحتوي على الأسطر التالية
# السطر 1: smtp، المرسل، المستلم
# الأسطر التالية: نص الرسالة
# المرسل: البريد الإلكتروني للمرسل
# المستلم: البريد الإلكتروني للمستلم
# SMTP: اسم خادم SMTP المطلوب استخدامه
# بروتوكول الاتصال SMTP بين العميل والخادم
# -> يتصل العميل بالمنفذ 25 لخادم SMTP
# <- يرسل له الخادم رسالة ترحيب
# -> العميل يرسل الأمر EHLO: اسم جهازه
# <- يرد الخادم بـ OK أو لا يرد
# -> يرسل العميل الأمر mail from: <مرسل>
# <- يرد الخادم بـ OK أو لا
# -> يرسل العميل الأمر rcpt to: <المستلم>
# <- يرد الخادم بـ OK أو لا
# -> يرسل العميل الأمر data
# <- الخادم يرد بـ OK أو لا
# -> يرسل العميل جميع أسطر رسالته وينهيها بسطر يحتوي على حرف واحد فقط.
# <- الخادم يرد بـ OK أو لا
# -> يرسل العميل الأمر quit
# <- يرد الخادم بـ OK أو لا
# تكون ردود الخادم على شكل xxx نص، حيث xxx هو رقم مكون من 3 أرقام. أي رقم xxx >=500
# يشير إلى وجود خطأ. قد تتضمن الإجابة عدة أسطر تبدأ جميعها بـ xxx- باستثناء السطر الأخير
# بالصيغة xxx (مسافة)
# يجب أن تنتهي أسطر النص المتبادلة بالأحرف RC(#13) و LF(#10)
# # معلمات إرسال البريد
MAIL="mail2.txt"
# يتم استرداد معلمات البريد
res=getInfos(MAIL)
# خطأ؟
if res[0]:
print "%s" % (erreur)
sys.exit()
# إرسال البريد في الوضع التفصيلي
(smtpServer,expediteur,destinataire,message)=res[1:]
print "Envoi du message [%s,%s,%s]" % (smtpServer,expediteur,destinataire)
resultat=sendmail(smtpServer,expediteur,destinataire,message,True)
print "Resultat de l'envoi : %s" % (resultat)
# النهاية
sys.exit()
ملاحظات:
- على جهاز يعمل بنظام Windows مزود ببرنامج مكافحة فيروسات، قد يمنع هذا البرنامج البرنامج النصي Python من الاتصال بالمنفذ 25 لخادم SMTP. لذا، يجب تعطيل برنامج مكافحة الفيروسات. بالنسبة لـ McAfee على سبيل المثال، يمكن اتباع الخطوات التالية:
![]() |
- في [1]، يتم تنشيط وحدة التحكم VirusScan
- في حالة [2]، يتم إيقاف الخدمة [Protection lors de l'accès]
- في [3]، تكون الخدمة متوقفة
الملف infos.txt:
smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
نتائج العرض على الشاشة:
الرسالة التي يقرأها برنامج البريد الإلكتروني Thunderbird:
![]() |
11.4. عميل SMTP ثانٍ
يقوم هذا البرنامج النصي الثاني بنفس وظيفة البرنامج النصي السابق، ولكنه يستخدم وظائف الوحدة النمطية [smtplib].
# -*- coding=utf-8 -*-
import sys,socket, smtplib
#-----------------------------------------------------------------------
def getInfos(fichier):
# استرجاع المعلومات (SMTP، المرسل، المستلم، الرسالة) المأخوذة من الملف النصي [fichier]
# السطر 1: SMTP، المرسل، المستلم
# الأسطر التالية: نص الرسالة
# فتح ملف [fichier]
erreur=""
try:
infos=open(fichier,"r")
except IOError, erreur:
return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
# قراءة السطر الأول
ligne=infos.readline()
# حذف علامة نهاية السطر
ligne=cutNewLineChar(ligne)
# استرداد الحقول smtp، المرسل، المستلم
champs=ligne.split(",")
# هل عدد الحقول صحيح؟
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# «معالجة» المعلومات المسترجعة - إزالة «المسافات» التي تسبقها أو تليها
for i in range(3):
champs[i]=champs[i].strip()
# استرداد الحقول
(smtpServer,expediteur,destinataire)=champs
# قراءة الرسالة المراد إرسالها
message=""
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# العودة
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# إرسال الرسالة إلى خادم SMTP smtpserver نيابة عن المرسل
# إلى المستلم. إذا كانت القيمة verbose=True، يتم تتبع التبادلات بين العميل والخادم
# يتم استخدام مكتبة smtplib
try:
server = smtplib.SMTP(smtpServer)
if verbose:
server.set_debuglevel(1)
server.sendmail(expediteur, destinataire, message)
server.quit()
except Exception, erreur:
return "Erreur envoi du message : %s" % (erreur)
# نهاية
return "Message envoye"
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# يتم حذف علامة نهاية السطر من [ligne] إن وجدت
l=len(ligne)
while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
l-=1
return(ligne[0:l])
# الرئيسية ----------------------------------------------------------------
# العميل SMTP (بروتوكول النقل SendMail) الذي يسمح بإرسال رسالة
# يتم أخذ المعلومات من ملف INFOS الذي يحتوي على الأسطر التالية
# السطر 1: smtp، المرسل، المستلم
# الأسطر التالية: نص الرسالة
# المرسل: البريد الإلكتروني للمرسل
# المستلم: البريد الإلكتروني للمستلم
# SMTP: اسم خادم SMTP المطلوب استخدامه
# بروتوكول الاتصال SMTP بين العميل والخادم
# -> يتصل العميل بالمنفذ 25 لخادم SMTP
# <- يرسل له الخادم رسالة ترحيب
# -> العميل يرسل الأمر EHLO: اسم جهازه
# <- يرد الخادم بـ OK أو لا يرد
# -> يرسل العميل الأمر mail from: <المرسل>
# <- يرد الخادم بـ OK أو لا
# -> يرسل العميل الأمر rcpt to: <المستلم>
# <- يرد الخادم بـ OK أو لا
# -> يرسل العميل الأمر data
# <- الخادم يرد بـ OK أو لا
# -> يرسل العميل جميع أسطر رسالته وينهيها بسطر يحتوي على حرف واحد فقط.
# <- يرد الخادم بـ OK أو لا
# -> يرسل العميل الأمر quit
# <- يرد الخادم بـ OK أو لا
# تكون ردود الخادم على شكل xxx نص، حيث xxx هو رقم مكون من 3 أرقام. أي رقم xxx >=500
# يشير إلى وجود خطأ. قد تتضمن الإجابة عدة أسطر تبدأ جميعها بـ xxx- باستثناء السطر الأخير
# على الشكل xxx (مسافة)
# يجب أن تنتهي أسطر النص المتبادلة بالأحرف RC(#13) و LF(#10)
# # معلمات إرسال البريد
MAIL="mail2.txt"
# يتم استرداد معلمات البريد
res=getInfos(MAIL)
# خطأ؟
if res[0]:
print "%s" % (erreur)
sys.exit()
# إرسال البريد في الوضع التفصيلي
(smtpServer,expediteur,destinataire,message)=res[1:]
print "Envoi du message [%s,%s,%s]" % (smtpServer,expediteur,destinataire)
resultat=sendmail(smtpServer,expediteur,destinataire,message,True)
print "Resultat de l'envoi : %s" % (resultat)
# النهاية
sys.exit()
ملاحظات:
- هذا البرنامج النصي مطابق للبرنامج النصي السابق باستثناء الدالة sendmail. تستخدم هذه الدالة الآن ميزات الوحدة النمطية [smtplib] (السطر 3).
الملف mail2.txt:
smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
From: serge.tahe@univ-angers.fr
To: serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
نتائج العرض على الشاشة:
11.5. العميل / خادم الصدى
نقوم بإنشاء خدمة الصدى. يقوم الخادم بإرجاع جميع أسطر النص التي يرسلها العميل إليه بأحرف كبيرة. يمكن للخدمة خدمة عدة عملاء في وقت واحد بفضل استخدام الخيوط (threads).
# -*- coding=utf-8 -*-
# خادم TCP عام على نظام Windows
# تحميل ملفات الرؤوس
import re,sys,SocketServer,threading
# خادم TCP متعدد الخيوط
class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):
def handle(self):
# الخيط الحالي
cur_thread = threading.currentThread()
# بيانات العميل
self.data="on"
# التوقف عند سلسلة فارغة
while self.data:
# يتم قراءة أسطر النص الواردة من العميل باستخدام طريقة readline
self.data =self.rfile.readline().strip()
# تتبع وحدة التحكم
print "client %s : %s (%s)" % (self.client_address[0],self.data,cur_thread.getName())
# إرسال الرد إلى العميل
response = "%s: %s" % (cur_thread.getName(), self.data.upper())
# self.wfile هو تدفق الكتابة إلى العميل
self.wfile.write(response)
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
# ------------------------------------------------------ main
# صيغة الاستدعاء: argv[0] المنفذ
# يتم تشغيل الخادم على المنفذ المسمى
# البيانات
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- يتم التحقق من الاستدعاء
# يجب أن يكون هناك وسيطة واحدة فقط
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# يجب أن يكون المنفذ رقميًا
port=sys.argv[1]
modele=r"^\s*\d+\s*$"
if not re.match(modele,port):
print "Le port %s n'est pas un nombre entier positif\n" % (port)
sys.exit(2)
# تشغيل الخادم - يخدم كل عميل في مؤشر ترابط منفصل
host="localhost"
server = ThreadedTCPServer((host,int(port)), ThreadedTCPRequestHandler)
# يتم تشغيل الخادم في مؤشر ترابط
# سيتم خدمة كل عميل في مؤشر ترابط إضافي
server_thread = threading.Thread(target=server.serve_forever)
# تشغيل الخادم - حلقة انتظار لا نهائية للعملاء
server_thread.start()
# المتابعة
print "Serveur d'echo a l'ecoute sur le port % s" % (port)
ملاحظات:
- السطر 39: يمثل sys.argv معلمات البرنامج النصي. يجب أن تكون هنا بالصيغة: nom_du_script port. لذا يجب أن يكون هناك اثنان منها. sys.argv[0] سيصبح عندئذٍ nom_du_script، وsys.argv[1] سيصبح port ؛
- السطر 53: خادم الإيكو هو مثيل للفئة ThreadedTcpServer. يتطلب منشئ هذه الفئة معلمتين:
- المعلمة 1: مجموعة مكونة من عنصرين (host,port) تحدد الجهاز ومنفذ الاستماع للخادم؛
- المعلمة 2: اسم الفئة المسؤولة عن معالجة طلبات العميل.
- السطر 57: يتم إنشاء مؤشر ترابط (ولكن لم يتم تشغيله بعد). يقوم مؤشر الترابط هذا بتنفيذ الطريقة [serve_forever] الخاصة بالخادم TCP. هذه الطريقة عبارة عن حلقة للاستماع إلى اتصالات العملاء. بمجرد اكتشاف اتصال عميل، سيتم إنشاء مثيل للفئة ThreadedTCPRequestHandler. وتقوم طريقتها handle بالتواصل مع العميل؛
- السطر 59: يتم تشغيل مؤشر ترابط خدمة الصدى. ومنذ هذه اللحظة، يمكن للعملاء الاتصال بالخدمة؛
- السطر 27: فئة خادم الصدى. وهي مشتقة من فئتين: SocketServer.ThreadingMixIn و SocketServer.TCPServer. وهذا يجعلها خادم TCP متعدد الخيوط: يعمل الخادم في خيط واحد ويتم خدمة كل عميل في خيط إضافي؛
- السطر 9: الفئة التي تعالج طلبات العملاء. وهي مشتقة من الفئة SocketServer.StreamRequestHandler. وبذلك ترث خاصيتين:
- rfile: وهو تدفق قراءة البيانات المرسلة من العميل - يمكن معالجته كملف نصي؛
- wfile: وهو تدفق الكتابة الذي يسمح بإرسال البيانات إلى العميل - يمكن معالجته كملف نصي.
- السطر 11: تعالج الطريقة handle طلبات العملاء؛
- السطر 13: الخيط الذي ينفذ هذه الطريقة؛
- السطر 17: حلقة معالجة طلبات العميل. تنتهي الحلقة عندما يرسل العميل سطرًا فارغًا؛
- السطر 19: قراءة طلب العميل؛
- السطر 21: تمثل self.client_address[0] عنوان العميل IP. cur_thread.getName() هو اسم الخيط الذي ينفذ الطريقة handle؛
- السطر 23: تتكون الإجابة الموجهة إلى العميل من مكونين - اسم الخيط الذي يخدم العميل والأمر الذي أرسله، مكتوبًا بأحرف كبيرة.
# -*- coding=utf-8 -*-
import re,sys,socket
# ------------------------------------------------------ main
# عميل TCP عام
# صيغة الاستدعاء: argv[0] المضيف المنفذ
# يقوم العميل بالاتصال بخدمة الصدى (المضيف، المنفذ)
# يقوم الخادم بإعادة إرسال الأسطر التي كتبها العميل
# الصيغة
syntaxe="syntaxe : %s hote port" % (sys.argv[0])
#------------------------------------- يتم التحقق من الاستدعاء
# يجب أن يكون هناك حجتان
nbArguments=len(sys.argv)
if nbArguments!=3:
print syntaxe
sys.exit(1)
# يتم استرداد المعلمات
hote=sys.argv[1]
# يجب أن يكون المنفذ رقميًا
port=sys.argv[2]
modele=r"^\s*\d+\s*$"
if not re.match(modele,port):
print "Le port %s foit être un nombre entier positif" % (port)
sys.exit(2)
try:
# اتصال العميل بالخادم
connexion=socket.create_connection((hote,int(port)))
except socket.error, erreur:
print "Echec de la connexion au site (%s,%s) : %s" % (hote,port,erreur)
sys.exit(3)
try:
# حلقة الإدخال
ligne=raw_input("Commande (rien pour arreter): ").strip()
while ligne!="":
# إرسال السطر إلى الخادم
connexion.send("%s\n" % (ligne))
# في انتظار الرد
reponse=connexion.recv(1000)
print "<-- %s" % (reponse)
ligne=raw_input("Commande (rien pour arreter): ")
except socket.error, erreur:
print "Echec de la connexion au site (%s,%s) : %s" % (hote,port,erreur)
sys.exit(3)
finally:
# إغلاق الاتصال
connexion.close()
يتم تشغيل الخادم في نافذة أوامر أولى:
يتم تشغيل العميل الأول في نافذة ثانية:
يتلقى العميل بالفعل، ردًا على الأمر الذي أرسله إلى الخادم، هذا الأمر نفسه مكتوبًا بأحرف كبيرة. يتم تشغيل عميل ثانٍ في نافذة ثالثة:
تصبح وحدة التحكم الخاصة بالخادم كما يلي:
يتم خدمة العملاء بالفعل في خيوط مختلفة. لإيقاف عميل ما، يكفي كتابة أمر فارغ.
11.6. خادم TCP عام
نقترح كتابة برنامج نصي بلغة Python
- سيكون بمثابة خادم TCP قادر على خدمة عميل واحد في كل مرة،
- يقبل أسطر النص المرسلة من العميل،
- ويقبل سطور النص الواردة من لوحة المفاتيح والتي يتم إرسالها كرد على العميل.
وبالتالي، فإن المستخدم الذي يستخدم لوحة المفاتيح هو الذي يقوم بدور الخادم:
- حيث يرى على شاشته الأسطر النصية المرسلة من العميل؛
- ويرد عليه عن طريق كتابة الرد على لوحة المفاتيح.
وبذلك يمكنه التكيف مع أي نوع من العملاء. ولهذا السبب سنطلق عليه اسم «خادم TCP عام». وهو أداة مفيدة لاكتشاف بروتوكولات الاتصال TCP. في المثال التالي، سيكون عميل TCP هو متصفح ويب، مما سيسمح لنا باكتشاف بروتوكول HTTP الذي يستخدمه عملاء الويب.
![]() |
# -*- coding=utf-8 -*-
# خادم TCP عام
# تحميل ملفات الرؤوس
import re,sys,SocketServer,threading
# خادم TCP عام
class MyTCPHandler(SocketServer.StreamRequestHandler):
def handle(self):
# عرض العميل
print "client %s" % (self.client_address[0])
# يتم إنشاء مؤشر ترابط لقراءة أوامر العميل
thread_lecture = threading.Thread(target=self.lecture)
thread_lecture.start()
# التوقف عند الأمر 'bye'
# قراءة الأمر المكتوب على لوحة المفاتيح
cmde=raw_input("--> ")
while cmde!="bye":
# إرسال الأمر إلى العميل. self.wfile هو تدفق الكتابة إلى العميل
self.wfile.write("%s\n" % (cmde))
# قراءة الأمر التالي
cmde=raw_input("--> ")
def lecture(self):
# يتم عرض جميع الأسطر المرسلة من العميل حتى استلام الأمر «bye»
ligne=""
while ligne!="bye":
# يتم قراءة أسطر النص الواردة من العميل باستخدام طريقة readline
ligne = self.rfile.readline().strip()
# متابعة وحدة التحكم
print "<--- %s : %s" % (self.client_address[0], ligne)
# ------------------------------------------------------ main
# صيغة الاستدعاء: argv[0] المنفذ
# يتم تشغيل الخادم على المنفذ المسمى
# البيانات
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- يتم التحقق من الاستدعاء
# يجب أن يكون هناك وسيطة واحدة فقط
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# يجب أن يكون المنفذ رقمياً
port=sys.argv[1]
modele=r"^\s*\d+\s*$"
if not re.match(modele,port):
print "Le port %s n'est pas un nombre entier positif\n" % (port)
sys.exit(2)
# تشغيل الخادم
host="localhost"
server = SocketServer.TCPServer((host, int(port)), MyTCPHandler)
print "Service tcp generique lance sur le port %s. Arret par Ctrl-C" % (port)
server.serve_forever()
ملاحظات:
- السطر 57: سيكون خادم TCP مثيلًا للفئة SocketServer.TCPServer. يقبل منشئ هذه الفئة معلمتين:
- المعلمة الأولى عبارة عن مجموعة مكونة من عنصرين (host, port) حيث host هي الآلة التي تعمل عليها الخدمة (عادةً ما تكون localhost) و port، المنفذ الذي تنتظر (تستمع) الخدمة من خلاله طلبات العملاء؛
- المعلمة الثانية تحدد فئة خدمة العميل. عندما يتصل عميل، يتم إنشاء مثيل لفئة الخدمة، ويجب أن تتولى طريقتها handle إدارة الاتصال مع العميل.
خادم TCP SocketServer.TCPServer ليس متعدد الخيوط. فهو يخدم عميلاً واحداً في كل مرة؛
- السطر 59: يتم تنفيذ الأسلوب serve_forever الخاص بخادم Tcp. وهي حلقة لا نهائية لانتظار العملاء؛
- السطر 9: خادم TCP هنا هو فئة مشتقة من الفئة SocketServer.StreamRequestHandler. وهذا يسمح باعتبار تدفقات البيانات المتبادلة مع العميل كملفات نصية. وقد سبق أن تعرفنا على هذه الفئة. ولدينا الطرق التالية:
- readline لقراءة سطر نصي قادم من العميل؛
- write لإرسال أسطر نصية إليه.
- السطر 12: self.client_address[0] هو عنوان IP للعميل؛
- السطر 14: سيتواصل خادم TCP مع العميل باستخدام خيطين
- خيط لقراءة الأسطر الواردة من العميل؛
- خيط لكتابة الأسطر إلى العميل.
- السطر 14: يتم إنشاء مؤشر الترابط الخاص بقراءة الأسطر الواردة من العميل. ويحدد المعامل الخاص به target الطريقة التي ينفذها مؤشر الترابط. وهي الطريقة المحددة في السطر 25؛
- السطر 25: يتم تشغيل مؤشر الترابط الخاص بالقراءة؛
- الأسطر 25-32: الطريقة التي ينفذها مؤشر الترابط الخاص بالقراءة؛
- السطر 28: يقوم مؤشر الترابط الخاص بالقراءة بقراءة جميع أسطر النص المرسلة من العميل حتى استلام السطر "bye"؛
- السطر 18: نحن هنا في مؤشر الترابط الخاص بالكتابة إلى العميل. المبدأ هو إرسال جميع أسطر النص التي يكتبها المستخدم على لوحة المفاتيح إلى العميل.
الخادم
متصفح العميل
الطلب الذي تلقّاه الخادم
رد الخادم الذي كتبه المستخدم على لوحة المفاتيح (بدون علامة -->)
- الأسطر 1-5: استجابة HTTP المرسلة إلى العميل؛
- السطر 6: صفحة HTML المرسلة إلى العميل؛
- السطر 7: نهاية الحوار مع العميل. ستنتهي الخدمة المقدمة للعميل وسيتم إغلاق الاتصال، مما سيؤدي إلى قطع مؤقت لسلسلة قراءة أسطر النص المرسلة من قبل العميل؛
- الأسطر 1-5 من استجابة HTTP المرسلة إلى العميل لها المعاني التالية:
- السطر 1: تم العثور على المورد الذي طلبه العميل؛
- السطر 2: تعريف الخادم؛
- السطر 3: سيقوم الخادم بإغلاق الاتصال بعد إرسال المورد؛
- السطر 4: طبيعة المورد الذي أرسله الخادم: مستند HTML؛
- السطر 5: سطر فارغ.
الصفحة المعروضة بواسطة متصفح [1]:
![]() |
إذا قمنا بعرض شفرة المصدر التي استلمها متصفح [2]، نلاحظ أن الشفرة HTML التي استلمها متصفح الويب هي بالفعل تلك التي أرسلناها إليه.




