Skip to content

11. Python'un ağ işlevleri

Şimdi, TCP / IP (Transfer Control Protocol / Internet Protocol) programlama yapmamızı sağlayan Python ağ işlevlerini ele alacağız.

  

11.1. İnternetteki bir makinenin adını veya IP adresini alma


Program (inet_01)


import sys, socket

#------------------------------------------------
def getIPandName(nomMachine):
    #nomMachine : adresini öğrenmek istediğimiz makinenin adı IP
    # nomMachine-->IP adresi
    try:
        ip=socket.gethostbyname(nomMachine)
        print "ip[%s]=%s" % (nomMachine,ip)
    except socket.error, erreur:
        print "ip[%s]=%s" % (nomMachine,erreur)
        return

    # adres 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


# ---------------------------------------- ana

# sabitler
HOTES=["istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","localhost","","xx"]

# HOTES makinelerinin IP adresleri
for i in range(len(HOTES)):
    getIPandName(HOTES[i])
# son
sys.exit()

Notlar:

    1. satır: Python ağ işlevleri, socket modülünde kapsüllenmiştir.

Sonuçlar

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. Bir web istemcisi

Bir web sitesinin "index" paragrafının içeriğini alan bir komut dosyası.


Program (inet_02)


import sys,socket

  #-----------------------------------------------------------------------
def getIndex(site):
    # URL sitesini okur ve site.html dosyasına kaydeder
  
    # başlangıçta hata yok
    erreur=""
    # site.html dosyasının oluşturulması
    try:
        html=open("%s.html" % (site),"w")
    except IOError, erreur:
        pass
    # hata mı?
    if erreur:
        return "Erreur (%s) lors de la création du fichier %s.html" % (erreur,site)

    # sitenin 80 numaralı bağlantı noktasında bağlantı açılıyor
    try:
        connexion=socket.create_connection((site,80))
    except socket.error, erreur:
        pass
    # hata durumunda geri dönüş
    if erreur:
        return "Echec de la connexion au site (%s,80) : %s" % (site,erreur)

    # bağlantı, iki yönlü bir iletişim akışını temsil eder
    # istemci (bu program) ile erişilen web sunucusu arasında
    # bu kanal, komut ve bilgi alışverişi için kullanılır
    # iletişim protokolü HTTP'tir

    # istemci, URL / dosyasını istemek için get komutunu gönderir
    # "get" komutunun sözdizimi: URL HTTP/1.0
    # HTTP protokolünün başlıkları (headers) boş bir satırla bitmelidir
    connexion.send("GET / HTTP/1.0\n\n")

    # sunucu şimdi bağlantı kanalı üzerinden yanıt verecektir. Tüm
    # verilerini gönderecek ve ardından kanalı kapatacaktır. İstemci, bağlantı kanalından gelen her şeyi okur
    # kanal kapanana kadar gelen tüm verileri okur
    ligne=connexion.recv(1000)
    while(ligne):
        html.write(ligne)
        ligne=connexion.recv(1000)

    # İstemci de sırayla bağlantıyı kapatır
    connexion.close()
    # html dosyasının kapatılması
    html.close()
    # geri dönüş
    return "Transfert reussi du paragraphe index du site %s" % (site)

# --------------------------------------------- ana program    

# URL'ten HTML metnini al

# web siteleri listesi
SITES=("istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","xx")

# SITES tablosundaki sitelerin ana sayfalarını okuma
for i in range(len(SITES)):
      # SITES[i] sitesinin ana sayfasını okuma
      resultat=getIndex(SITES[i])
      # sonuç görüntüleme
      print resultat

# son
sys.exit()

Notlar:

    1. satır: Ana sayfasını almak istediğimiz web sitelerinin URL listesi. Bu liste, [nomsite.html] adlı metin dosyasında saklanır;
    1. satır: getIndex işlevi bu işi yapar;
    1. satır: getIndex işlevi;
  • satır 20: create_connection((site,port)) yöntemi, site makinesinin port numaralı bağlantı noktasında çalışan bir TCP / IP hizmeti ile bağlantı kurulmasını sağlar;
    1. satır: send yöntemi, bir TCP / IP bağlantısı üzerinden veri gönderilmesini sağlar. Burada gönderilen metin verisidir. Bu metin, HTTP protokolüne (HyperText Aktarım Protokolü) uygundur;
    1. satır: recv yöntemi, TCP / IP bağlantısı üzerinden veri alınmasını sağlar. Burada, web sunucusunun yanıtı 1000 karakterlik bloklar halinde okunur ve [nomsite.html] metin dosyasına kaydedilir.

Sonuçlar

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] sitesi için alınan dosya:

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. satırlar, sunucunun yanıtındaki HTTP başlıklarıdır;
    1. satır: sunucu, istemciden 8. satırda belirtilen URL adresine yönlendirilmesini ister;
    1. satır: yanıtın tarihi ve saati;
    1. satır: web sunucusunun kimliği;
    1. satır: sunucu tarafından gönderilen içerik. Burada, 13. satırda başlayan bir HTML sayfası;
    1. satır: HTTP başlıklarını sonlandıran boş satır;
  • 13-19. satırlar: web sunucusu tarafından gönderilen HTML sayfası.

11.3. Bir SMTP istemcisi

TCP / IP protokolleri arasında, SMTP (SendMail Aktarım Protokolü), mesaj gönderme hizmetinin iletişim protokolüdür.


Program (inet_03)


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

import sys,socket

#-----------------------------------------------------------------------
def getInfos(fichier):
    # [fichier] metin dosyasından alınan bilgileri (smtp, gönderen, alıcı, mesaj) döndürür
    # 1. satır: smtp, gönderen, alıcı
    # sonraki satırlar: mesaj metni

    # [fichier] dosyasının açılması
    erreur=""
    try:
        infos=open(fichier,"r")
    except IOError, erreur:
        return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
  
    # 1. satırın okunması
    ligne=infos.readline()
    # satır sonu işaretinin silinmesi
    ligne=cutNewLineChar(ligne)
    # smtp, gönderen, alıcı alanlarının alınması
    champs=ligne.split(",")
    # alan sayısı doğru mu?
    if len(champs)!=3 :
        return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
    # Toplanan bilgilerin "işlenmesi"
    # 3 alanın her birinden, yararlı bilginin önündeki veya arkasındaki "boşluklar" kaldırılır
    for i in range(3):
        champs[i]=champs[i].strip()
    # alanların alınması
    (smtpServer,expediteur,destinataire)=champs
    message=""
    # mesajın geri kalan kısmının okunması
    ligne=infos.readline()
    while ligne!='':
        message+=ligne
        ligne=infos.readline()
    infos.close()
    # geri dönüş
    return ("",smtpServer,expediteur,destinataire,message)

#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
    # mesajı, gönderen adına smtpserver SMTP sunucusuna gönderir
    # alıcıya. verbose=True ise, istemci-sunucu iletişimini izler

    # istemcinin adı alınır
    try:
        client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
    except socket.error, erreur:
        return "Erreur IP / Nom du client : %s" % (erreur)
    
    # smtpServer'in 25 numaralı bağlantı noktasında bir bağlantı açılır
    try:
        connexion=socket.create_connection((smtpServer,25))
    except socket.error, erreur:
        return "Echec de la connexion au site (%s,25) : %s" % (smtpServer,erreur)

    # bağlantı, iki yönlü bir iletişim akışını temsil eder
    # istemci (bu program) ile bağlanılan SMTP sunucusu arasında
    # bu kanal, komut ve bilgi alışverişi için kullanılır

    # bağlantı kurulduktan sonra sunucu, okunan bir karşılama mesajı gönderir
    erreur=sendCommand(connexion,"",verbose,1)
    if(erreur) :
        connexion.close()
        return erreur
    # ehlo komutu:
    erreur=sendCommand(connexion,"EHLO %s" % (client),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # "mail from" komutu:
    erreur=sendCommand(connexion,"MAIL FROM: <%s>" % (expediteur),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # rcpt to komutu:
    erreur=sendCommand(connexion,"RCPT TO: <%s>" % (destinataire),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # data komutu
    erreur=sendCommand(connexion,"DATA",verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # gönderilecek mesajın hazırlanması
    # şu satırları içermelidir
    # From: gönderen
    # Kime: alıcı
    # boş satır
    # Mesaj
    # .
    data="From: %s\r\nTo: %s\r\n%s\r\n.\r\n" % (expediteur,destinataire,message)
    # mesaj gönderiliyor
    erreur=sendCommand(connexion,data,verbose,0)
    if erreur :
        connexion.close()
        return erreur
    # çıkış komutu
    erreur=sendCommand(connexion,"QUIT",verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # son
    connexion.close()
    return "Message envoye"

# --------------------------------------------------------------------------
def sendCommand(connexion,commande,verbose,withRCLF):
    # bağlantı kanalına komut gönder
    # verbose=1 ise ayrıntılı mod
    # withRCLF=1 ise, komuta RCLF dizisini ekler

    # veriler
    RCLF="\r\n" if withRCLF else ""
    # komut boş değilse komut gönder
    if commande:
        connexion.send("%s%s" % (commande,RCLF))
        # olası yanıt
        if verbose:
            affiche(commande,1)
        # 1000 karakterden kısa yanıtı oku
        reponse=connexion.recv(1000)
        # olası yankı
        if verbose:
            affiche(reponse,2)
        # hata kodunu al
        codeErreur=reponse[0:3]
        # sunucu tarafından gönderilen hata mı?
        if int(codeErreur) >=500:
            return reponse[4:]
        # hatasız dönüş
        return ""

# --------------------------------------------------------------------------
def affiche(echange,sens):
    # değiş tokuşu ekrana mı gösterir?
    # eğer sens=1 ise -->değişim'i göster
    # eğer sens=2 ise <-- son 2 karakter hariç değiş tokuşu göster RCLF
    if sens==1:
        print "--> [%s]" % (echange)
        return
    elif sens==2:
        l=len(echange)
        print "<-- [%s]" % echange[0:l-2]
        return

      
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
    # varsa, [ligne]'teki satır sonu işaretini siler
    l=len(ligne)
    while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
        l-=1
    return(ligne[0:l])

# ana sayfa ----------------------------------------------------------------
  
# mesaj göndermeye olanak tanıyan SMTP istemcisi (SendMail Aktarım Protokolü)
# bilgiler, aşağıdaki satırları içeren bir dosyadan alınır: INFOS
# 1. satır: smtp, gönderen, alıcı
# sonraki satırlar: mesaj metni

# gönderen: gönderenin e-posta adresi
# alıcı: alıcının e-posta adresi
# smtp: kullanılacak smtp sunucusunun adı


# iletişim protokolü SMTP istemci-sunucu
# -> istemci, SMTP sunucusunun 25 numaralı bağlantı noktasına bağlanır
# <- sunucu, istemciye bir hoş geldin mesajı gönderir
# -> istemci EHLO komutunu gönderir: makinesinin adı
# <- sunucu OK yanıtını gönderir veya göndermez
# -> istemci "mail from: <gönderen>" komutunu gönderir
# <- sunucu OK yanıtını verir veya vermez
# -> istemci rcpt to: <alıcı> komutunu gönderir
# <- sunucu OK yanıtını verir veya vermez
# -> istemci data komutunu gönderir
# <- sunucu OK ile yanıt verir veya vermez
# -> istemci, mesajının tüm satırlarını gönderir ve tek bir karakter içeren bir satırla bitirir.
# <- sunucu OK yanıtını verir veya vermez
# -> istemci quit komutunu gönderir
# <- sunucu OK yanıtını verir veya vermez

# sunucunun yanıtları xxx metin biçimindedir; burada xxx, 3 basamaklı bir sayıdır. Herhangi bir xxx sayısı >=500
# bir hatayı bildirir. Yanıt, son satır hariç hepsi xxx- ile başlayan birden fazla satır içerebilir
# şu biçimdedir: xxx(boşluk)

# değiş tokuş edilen metin satırları, RC(#13) ve LF(#10) karakterleriyle bitmelidir

# # e-posta gönderim parametreleri
MAIL="mail2.txt" 
# e-posta parametreleri alınır
res=getInfos(MAIL)
# hata mı?
if res[0]:
      print "%s" % (erreur)
      sys.exit()
# ayrıntılı modda e-posta gönderimi
(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)
# son
sys.exit()

Notlar:

  • Antivirüs programı yüklü bir Windows bilgisayarında, antivirüs programı Python betiğinin bir SMTP sunucusunun 25 numaralı bağlantı noktasına bağlanmasını engelleyebilir. Bu durumda antivirüs programını devre dışı bırakmak gerekir. Örneğin McAfee için şu şekilde işlem yapılabilir:
  • [1]'te, VirusScan konsolunu etkinleştirin
  • [2]'te, [Protection lors de l'accès] hizmetini durdurun
  • [3]'te, hizmet durdurulur

Sonuçlar

infos.txt dosyası:

smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
Subject: test

ligne1
ligne2

ligne3

Ekran sonuçları:

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 e-posta okuyucusu tarafından okunan mesaj:

  

11.4. İkinci bir SMTP istemcisi

Bu ikinci komut dosyası, önceki komut dosyasıyla aynı işlevi görür, ancak [smtplib] modülünün özelliklerini kullanır.


Program (inet_04)


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

import sys,socket, smtplib

#-----------------------------------------------------------------------
def getInfos(fichier):
    # metin dosyasından alınan bilgileri (smtp, gönderen, alıcı, mesaj) döndürür [fichier]
    # 1. satır: smtp, gönderen, alıcı
    # sonraki satırlar: mesaj metni

    # [fichier] dosyasının açılması
    erreur=""
    try:
        infos=open(fichier,"r")
    except IOError, erreur:
        return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
  
    # 1. satırın okunması
    ligne=infos.readline()
    # satır sonu işaretinin silinmesi
    ligne=cutNewLineChar(ligne)
    # smtp, gönderen, alıcı alanlarının alınması
    champs=ligne.split(",")
    # alan sayısı doğru mu?
    if len(champs)!=3 :
        return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
    # Alınan bilgilerin "işlenmesi" - bilgilerin başındaki veya sonundaki "boşluklar" kaldırılır
    for i in range(3):
        champs[i]=champs[i].strip()
    # alanların alınması
    (smtpServer,expediteur,destinataire)=champs
    # gönderilecek mesajın okunması
    message=""
    ligne=infos.readline()
    while ligne!='':
        message+=ligne
        ligne=infos.readline()
    infos.close()
    # geri dönüş
    return ("",smtpServer,expediteur,destinataire,message)

#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
    # mesajı, gönderen adına smtpserver SMTP sunucusuna gönderir
    # alıcıya. verbose=True ise, istemci-sunucu iletişimini izler

    # smtplib kütüphanesi kullanılır 
    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)
    # son
    return "Message envoye"
      
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
    # varsa, [ligne] satır sonu işaretini siler
    l=len(ligne)
    while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
        l-=1
    return(ligne[0:l])

# ana ----------------------------------------------------------------
  
# mesaj göndermeye olanak tanıyan SMTP istemcisi (SendMail Aktarım Protokolü)
# bilgiler, aşağıdaki satırları içeren bir dosyadan alınır: INFOS
# 1. satır: smtp, gönderen, alıcı
# sonraki satırlar: mesaj metni

# gönderen: gönderenin e-posta adresi
# alıcı: alıcının e-posta adresi
# smtp: kullanılacak smtp sunucusunun adı


# iletişim protokolü SMTP istemci-sunucu
# -> istemci, SMTP sunucusunun 25 numaralı bağlantı noktasına bağlanır
# <- sunucu, istemciye bir hoş geldin mesajı gönderir
# -> istemci EHLO komutunu gönderir: makinesinin adı
# <- sunucu OK yanıtını gönderir veya göndermez
# -> istemci "mail from: <gönderen>" komutunu gönderir
# <- sunucu OK yanıtını verir veya vermez
# -> istemci rcpt to: <alıcı> komutunu gönderir
# <- sunucu OK yanıtını verir veya vermez
# -> istemci data komutunu gönderir
# <- sunucu OK ile yanıt verir veya vermez
# -> istemci, mesajının tüm satırlarını gönderir ve tek bir karakter içeren bir satırla bitirir.
# <- sunucu OK yanıtını verir veya vermez
# -> istemci quit komutunu gönderir
# <- sunucu OK yanıtını verir veya vermez

# sunucunun yanıtları xxx metin biçimindedir; burada xxx, 3 basamaklı bir sayıdır. Herhangi bir xxx sayısı >=500
# bir hatayı bildirir. Yanıt, son satır hariç hepsi xxx- ile başlayan birden fazla satır içerebilir
# şu biçimdedir: xxx(boşluk)

# değiş tokuş edilen metin satırları, RC(#13) ve LF(#10) karakterleriyle bitmelidir

# # e-posta gönderim parametreleri
MAIL="mail2.txt" 
# e-posta parametreleri alınır
res=getInfos(MAIL)
# hata mı?
if res[0]:
      print "%s" % (erreur)
      sys.exit()
# ayrıntılı modda e-posta gönderimi
(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)
# son
sys.exit()

Notlar:

  • Bu komut dosyası, sendmail işlevi hariç, öncekiyle aynıdır. Bu işlev artık [smtplib] modülünün özelliklerini kullanmaktadır (3. satır).

Sonuçlar

mail2.txt dosyası:

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

Ekran sonuçları:

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. Echo istemcisi / sunucusu

Bir yankı hizmeti oluşturulur. Sunucu, istemciden aldığı tüm metin satırlarını büyük harflerle geri gönderir. Hizmet, iş parçacıkları sayesinde aynı anda birden fazla istemciye hizmet verebilir.


Sunucu programı (inet_08)


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

# Windows'ta genel TCP sunucusu

# başlık dosyalarının yüklenmesi
import re,sys,SocketServer,threading

# çok iş parçacıklı TCP sunucusu
class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):

  def handle(self):
    # çalışan iş parçacığı
    cur_thread = threading.currentThread()
    # istemci verileri
    self.data="on"
    # boş dizede durma
    while self.data:
      # istemciden gelen metin satırları readline yöntemi ile okunur
      self.data =self.rfile.readline().strip()
      # konsol izleme
      print "client %s : %s (%s)" % (self.client_address[0],self.data,cur_thread.getName())
      # müşteriye yanıt gönderme
      response = "%s: %s" % (cur_thread.getName(), self.data.upper())
      # self.wfile, istemciye yazma akışıdır
      self.wfile.write(response)                    

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

# ------------------------------------------------------ ana işlev        
# çağırma sözdizimi: argv[0] bağlantı noktası
# sunucu, belirtilen bağlantı noktasında başlatılır

# Veriler
syntaxe="syntaxe : %s port" % (sys.argv[0])

#------------------------------------- çağrı kontrol ediliyor
# tek bir argüman bulunmalıdır
nbArguments=len(sys.argv)
if nbArguments!=2:
    print syntaxe
    sys.exit(1)

# bağlantı noktası sayısal olmalıdır
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)

# sunucunun başlatılması - her istemciye bir iş parçacığı üzerinden hizmet verir
host="localhost"
server = ThreadedTCPServer((host,int(port)), ThreadedTCPRequestHandler)

# sunucu bir iş parçacığında başlatılıyor
# her istemciye ayrı bir iş parçacığıyla hizmet verilir
server_thread = threading.Thread(target=server.serve_forever)
# sunucuyu başlatır - istemcileri bekleyen sonsuz döngü
server_thread.start()
# izleme
print "Serveur d'echo a l'ecoute sur le port % s" % (port)

Notlar:

    1. satır: sys.argv, komut dosyasının parametrelerini temsil eder. Bu parametreler burada şu biçimde olmalıdır: nom_du_script port. Dolayısıyla iki tane olmalıdır. sys.argv[0], nom_du_script olacak ve sys.argv[1], port olacaktır ;
    1. satır: yankı sunucusu, ThreadedTcpServer sınıfının bir örneğidir. Bu sınıfın oluşturucusu iki parametre bekler:
      1. parametre: sunucunun çalışacağı makineyi ve dinleme bağlantı noktasını belirleyen iki elemanlı bir tuple (host, port);
    2. parametre 2: bir istemcinin isteklerini işlemekle görevli sınıfın adı.
    1. satır: bir iş parçacığı oluşturulur (ancak henüz başlatılmaz). Bu iş parçacığı, TCP sunucusunun [serve_forever] yöntemini yürütür. Bu yöntem, istemci bağlantılarını dinleyen bir döngüdür. Bir istemci bağlantısı algılandığında, ThreadedTCPRequestHandler sınıfının bir örneği oluşturulur. Bu sınıfın handle yöntemi, istemciyle iletişimi sağlar;
    1. satır: yankı hizmetinin iş parçacığı başlatılır. Bu andan itibaren istemciler hizmete bağlanabilir;
    1. satır: yankı sunucusu sınıfı. Bu sınıf, SocketServer.ThreadingMixIn ve SocketServer.TCPServer olmak üzere iki sınıftan türetilmiştir. Bu da onu çok iş parçacıklı bir TCP sunucusu yapar: sunucu bir iş parçacığında çalışır ve her müşteriye ayrı bir iş parçacığında hizmet verilir;
    1. satır: istemcilerin isteklerini işleyen sınıf. SocketServer.StreamRequestHandler sınıfından türetilmiştir. Böylece iki özniteliği miras alır:
    2. rfile: bu, istemci tarafından gönderilen verilerin okuma akışıdır - bir metin dosyası gibi işlenebilir;
    3. wfile: müşteriye veri gönderilmesini sağlayan yazma akışıdır; bir metin dosyası gibi işlenebilir.
    1. satır: handle yöntemi, istemcilerin isteklerini işler;
    1. satır: Bu yöntemi çalıştıran parçacığı;
    1. satır: müşteri isteklerini işleyen döngü. Döngü, müşteri boş bir satır gönderdiğinde sona erer;
    1. satır: Müşterinin isteğinin okunması;
    1. satır: self.client_address[0], müşterinin IP adresini temsil eder. cur_thread.getName(), handle yöntemini çalıştıran iş parçacığının adıdır;
    1. satır: Müşteriye verilen yanıt iki bileşenden oluşur: müşteriye hizmet veren iş parçacığının adı ve müşterinin gönderdiği komut (büyük harflerle yazılmış).

İstemci programı (inet_06)


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

# ------------------------------------------------------ main        
# genel tcp istemcisi
# çağırma sözdizimi: argv[0] ana bilgisayar bağlantı noktası
# istemci yankı hizmetine bağlanır (ana bilgisayar, bağlantı noktası)
# sunucu, istemci tarafından yazılan satırları geri gönderir

# sözdizimi
syntaxe="syntaxe : %s hote port" % (sys.argv[0])

#------------------------------------- çağrı kontrol ediliyor
# iki argüman olması gerekir
nbArguments=len(sys.argv)
if nbArguments!=3:
    print syntaxe
    sys.exit(1)

# argümanlar alınır
hote=sys.argv[1]
# bağlantı noktası sayısal olmalıdır
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:
  # müşterinin sunucuya bağlanması
  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:
    # giriş döngüsü
    ligne=raw_input("Commande (rien pour arreter): ").strip()
    while ligne!="":
        # satır sunucuya gönderiliyor
        connexion.send("%s\n" % (ligne))
        # yanıt bekleniyor
        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:
    # bağlantı sonlandırılıyor
    connexion.close()

Sonuçlar

Sunucu, ilk komut penceresinde başlatılır:

cmd>%python% inet_08.py 100
Serveur d'echo a l'ecoute sur le port 100

İkinci bir pencerede ilk istemci başlatılır:

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):

İstemci, sunucuya gönderdiği komuta yanıt olarak, aynı komutu büyük harflerle yazılmış haliyle almaktadır. Üçüncü bir pencerede ikinci bir istemci başlatılır:

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):

Sunucu konsolu şu şekilde görünür:

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)

İstemciler farklı iş parçacıklarında hizmet almaktadır. Bir istemciyi durdurmak için boş bir komut girmek yeterlidir.

11.6. Genel TCP Sunucusu

Şu özelliklere sahip bir Python betiği yazmayı planlıyoruz:

  • aynı anda tek bir istemciye hizmet verebilen,
  • istemci tarafından gönderilen metin satırlarını kabul eden,
  • klavyeden gelen ve müşteriye yanıt olarak gönderilen metin satırlarını kabul eden bir TCP sunucusu olacak.

Böylece, klavyeyi kullanan kullanıcı sunucu görevi görür:

  • konsolunda istemci tarafından gönderilen metin satırlarını görür;
  • klavyeye yanıtı yazarak istemciye cevap verir.

Böylelikle her tür istemciye uyum sağlayabilir. Bu nedenle buna "genel TCP sunucusu" denir. Bu, TCP iletişim protokollerini keşfetmek için kullanışlı bir araçtır. Aşağıdaki örnekte, TCP istemcisi bir web tarayıcısı olacaktır; bu da web istemcileri tarafından kullanılan HTTP protokolünü keşfetmemizi sağlayacaktır.


Program (inet_10)


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

# genel TCP sunucusu

# başlık dosyaları yükleniyor
import re,sys,SocketServer,threading

# genel TCP sunucusu
class MyTCPHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        # istemci görüntüleniyor
        print "client %s" % (self.client_address[0])
        # istemciden gelen komutları okumak için bir iş parçacığı oluşturuluyor
        thread_lecture = threading.Thread(target=self.lecture)
        thread_lecture.start()
        # 'bye' komutuyla sonlandırma
        # klavyeden girilen komut okunur
        cmde=raw_input("--> ")
        while cmde!="bye":
            # komutun müşteriye gönderilmesi. self.wfile, müşteriye yönelik yazma akışıdır
            self.wfile.write("%s\n" % (cmde))
            # sonraki komut okunuyor
            cmde=raw_input("--> ")

    def lecture(self):
        # "bye" komutu alınana kadar istemci tarafından gönderilen tüm satırlar görüntülenir
        ligne=""
        while ligne!="bye":
            # Müşteriden gelen metin satırları readline yöntemi ile okunur
            ligne = self.rfile.readline().strip()
            # konsol izleme
            print "<--- %s : %s" % (self.client_address[0], ligne)
    
# ------------------------------------------------------ main        
# çağırma sözdizimi: argv[0] bağlantı noktası
# sunucu, belirtilen bağlantı noktasında başlatılır

# Veriler
syntaxe="syntaxe : %s port" % (sys.argv[0])

#------------------------------------- çağrı kontrol ediliyor
# tek bir argüman bulunmalıdır
nbArguments=len(sys.argv)
if nbArguments!=2:
    print syntaxe
    sys.exit(1)

# bağlantı noktası sayısal olmalıdır
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)

# sunucunun başlatılması
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()

Notlar:

    1. satır: TCP sunucusu, SocketServer.TCPServer sınıfının bir örneği olacaktır. Bu sınıfın oluşturucusu iki parametre kabul eder:
    2. İlk parametre, iki elemanlı bir tuple’dır (host, port); burada host, hizmetin çalıştığı makinedir (genellikle localhost) ve port, hizmetin istemcilerin isteklerini beklediği (dinlediği) bağlantı noktasıdır;
    3. ikinci parametre, bir müşterinin hizmet sınıfını belirtir. Bir müşteri bağlandığında, hizmet sınıfının bir örneği oluşturulur ve müşteri ile olan bağlantıyı yönetmesi gereken yöntem handle'tir.

SocketServer.TCPServer TCP sunucusu çok iş parçacıklı değildir. Aynı anda yalnızca bir müşteriye hizmet verir;

    1. satır: Tcp sunucusunun serve_forever yöntemi yürütülür. Bu, istemcileri bekleyen sonsuz bir döngüdür;
    1. satır: Tcp sunucusu burada SocketServer.StreamRequestHandler sınıfından türetilmiş bir sınıftır. Bu, istemciyle alışverişi yapılan veri akışlarını metin dosyaları olarak ele almamızı sağlar. Bu sınıfa daha önce rastlamıştık. Elimizde şu yöntemler bulunmaktadır:
    2. readline, müşteriden gelen bir metin satırını okumak için;
    3. write, müşteriye metin satırları göndermek için kullanılır.
    1. satır: self.client_address[0], müşterinin IP adresidir;
    1. satır: TCP sunucusu, iki iş parçacığı kullanarak istemciyle iletişim kuracaktır
    2. müşteriden gelen satırları okumak için bir iş parçacığı;
    3. müşteriye satır yazmak için bir iş parçacığı.
    1. satır: Müşteriden gelen satırları okumak için iş parçacığı oluşturulur. target parametresi, iş parçacığı tarafından yürütülecek yöntemi belirler. Bu, 25. satırda tanımlanan yöntemdir;
    1. satır: okuma iş parçacığı başlatılır;
  • 25-32. satırlar: okuma iş parçacığı tarafından yürütülen yöntem;
    1. satır: okuma iş parçacığı, "bye" satırı alınana kadar istemci tarafından gönderilen tüm metin satırlarını okur;
    1. satır: Burada, müşteriye yazma iş parçacığı içindeyiz. Buradaki mantık, kullanıcının klavyeden girdiği tüm metin satırlarını müşteriye göndermektir.

Sonuçlar

Sunucu

dos>%python% inet_10.py 100
Service tcp generique lance sur le port 100. Arret par Ctrl-C

İstemci tarayıcısı

 

Sunucu tarafından alınan istek

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 :

Kullanıcının klavyeden girdiği sunucu yanıtı (--> işareti hariç)

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. satırlar: İstemciye gönderilen HTTP yanıtı;
    1. satır: İstemciye gönderilen HTML sayfası;
    1. satır: istemciyle olan iletişimin sonu. İstemciye sunulan hizmet sona erecek ve bağlantı kapatılacak; bu da istemci tarafından gönderilen metin satırlarını okuyan iş parçacığını aniden kesintiye uğratacaktır;
  • Müşteriye gönderilen HTTP yanıtının 1-5. satırlarının anlamları şöyledir:
    1. satır: Müşteri tarafından istenen kaynak bulundu;
    1. satır: sunucunun kimliği;
    1. satır: sunucu, kaynağı gönderdikten sonra bağlantıyı kapatacaktır;
    1. satır: Sunucu tarafından gönderilen kaynağın türü: bir HTML belgesi;
    1. satır: Boş bir satır.

[1] tarayıcısı tarafından görüntülenen sayfa:

[2] tarayıcısı tarafından alınan kaynak kodunu görüntülediğimizde, web tarayıcısı tarafından alınan HTML kodunun, kendisine gönderilen kodla aynı olduğunu görüyoruz.