Ping Ring del 8: Python


tirsdag 21. september 2010 Diverse prosjekter Ping Ring Python Samtidighet

Dette er del 8 i artikkelserien Ping Ring hvor jeg implementerer et og samme program i et utall ulike programmeringsspråk - for å se om det er noe å lære gjennom å gjøre det. Introduksjonen kan du lese her.

Etter å ha implementert Ping Ring i de programmeringspråkene som sitter sånn noenlunde i fingrene, fikk jeg lyst til å gjøre en versjon i et språk jeg ikke kunne fra før. Følgende er dermed mitt første Python-program. Som i de andre implementasjonene har jeg forsøkt å følge de idiomene som gjelder i språket, men med null erfaring er det opp til deg å vurdere om jeg har lykkes med det.

Etter litt googling og 20 minutter utvikling kom jeg frem til en fungerende Ping Ring. Det virket som om den vanlige måten å løse samtighet på i Python er å arve fra Thread-klassen, så jeg valgte å gjøre det. Implementasjonen skiller seg derfor noe fra de andre objektorienterte løsningene (C#/Ruby/Boo).

1 import sys
2 import time
3 import datetime
4 import socket
5 from threading import Thread
6
7 last_ping_time = datetime.datetime.now() # Global state
8
9 class TcpThread(Thread):
10   def __init__ (self, self_port, other_port):
11     Thread.__init__(self)
12     self.port = self_port
13     self.other = other_port
14   def get_socket(self):
15     return socket.socket(socket.AF_INET, socket.SOCK_STREAM)
16
17 class Listener(TcpThread):
18   def run(self):
19     while 1:
20       s = self.get_socket()
21       s.bind(('127.0.0.1', self.port))
22       s.listen(1)
23       conn, addr = s.accept()
24       ping = conn.recv(1024)
25       self.process(ping)
26       conn.close()
27   def process(self, message):
28     global last_ping_time
29     last_ping_time = datetime.datetime.now()
30     print "Received", message
31     Pinger(self.port, self.other).start()
32
33 class Pinger(TcpThread):
34   def run(self):
35     time.sleep(1)
36     s = self.get_socket()
37     try:
38       s.connect(('127.0.0.1', self.other))
39       s.send('PING from %(port)s' % {'port': self.port})
40       s.close()
41     except Exception:
42       print "*** Failed sending ping!"
43
44 class Alerter(TcpThread):
45   def __init__ (self, self_port, other_port, max_delay):
46     TcpThread.__init__(self, self_port, other_port)
47     self.max_delay = datetime.timedelta(seconds=max_delay)
48   def run(self):
49     global last_ping_time
50     while 1:
51       time.sleep(5)
52       ping_delay = datetime.datetime.now() - last_ping_time
53       if ping_delay > self.max_delay:
54         print '*** ALERT, RING BROKEN!' + \
55             ' No ping in %(delay)s.' % {'delay': ping_delay}
56         Pinger(self.port, self.other).start()
57
58 this_port = int(sys.argv[1])
59 other_port = int(sys.argv[2])
60 max_delay = int(sys.argv[3])
61 initial_ping = sys.argv[4] == "true"
62
63 print "** Python Ring Server (", this_port, ")"
64
65 if initial_ping: Pinger(this_port, other_port).start()
66 Listener(this_port, other_port).start()
67 Alerter(this_port, other_port, max_delay).start()

På forhånd trodde jeg jeg ville like Python ganske bra. Jeg har jo litt erfaring med Python-lignende syntax fra Boo, og synes den er ganske elegant.

Det dukket imidlertid raskt opp et par ting jeg hadde problemer med å synes godt om. Dette var spesielt knyttet til scope; når man får sende inn en self-variabel i alle metoder virker det som om man har hacket inn et objekt-system i et språk som egentlig ikke er objektorientert. Å i tillegg måtte eksplisitt deklarere alle globale variabler man ønsker å benytte i en metode kan forsvares – men det minte meg om PHP, og jeg likte det ikke.

Bruk av moduler/namespace gjør meg også litt forundret. Hvorfor i all hverden må jeg si datetime.datetime.now()? Det er mulig det kan gjøres anderledes, men jeg fant dette i mange kodeeksempler.

Alt i alt liker jeg løsningen min dårlig! Designet med ulike klasser førte til alt for mye plumbing-kode. Det er mange som skryter mye av Python, men etter å ha prøvd litt (og jeg må innrømme at det er veldig lite å basere slike uttalelser på) skjønner jeg ikke helt hva de snakker om. Det slår meg at de som sier dette sansynligvis aldri har forsøkt seg på Ruby, som jeg føler er både enklere og mere sofistikert på en gang.

Bonus: Et community-bidrag..

Jeg har mottatt endel bidrag fra utviklere som har laget sine løsninger på Ping Ring i diverse språk jeg ikke behersker selv. En av disse er en 17 år gammel Python-entusiast som kaller seg oddstr13. Han har laget en fullt fungerende implementasjon med flere utvidelser i forhold til spesifikasjonen. Oddstr13's Ping Ring sender bl.a. epost når en server går ned.

Her er et lite utdrag som viser noen av konfigurasjonsmulighetene:

334     %s [--listen=<port>] [--remote=<host>[:<port>]] [--timeout=<int>]
335    
336     --listen    Port to bind to.
337     --remote    Host to connect to.
338     --timeout   Time after last ping received before sending email.
339     --mail      Send mail on ping timeout. (Default) (Not yet implemented)
340     --nomail    Oposite of above.
341     --mailto    Address to mail. (Not yet implemented)
342     --mailfrom  Address mail apears to come from. (Not yet implemented)
343     --mailhost  Adress to smtp server. (Not yet implemented)
344    
345     (Not yet implemented) simply means that the cli argument dosn't work
346     the functionality is however there, but you have to edit the script.

Programmer er tilgjengelig i sin helhet her.

Denne implementasjonen er på neste 370 linjer – min var under 70. Mye av koden er infrastruktur for konfigureringen og behandling av kommandolinje-argumentene. Oddstr13 har ikke brukt klasser, men har splittet opp funksjonaliteten sånn passe bra i metoder med beskrivende navn. Det at han har brukt hashtabeller (eller maps eller dictionaries eller hva de nå heter i Python) gjør endel av koden litt "bråkete", og jeg tror jeg ville løst dette anderledes.

Men for all del, jeg er imponert og takknemlig for at han ville løse oppgaven min og spre litt Python-kunnskap. Takk skal du ha!

Flere community-bidrag, i til tider overraskende språk, presenteres snart…

Tidligere i serien: Introduksjon | C# | Ruby | Boo | Erlang | Clojure | Clojure m/Agenter.

Kildekoden fra denne blogposten er tilgjengelig på Github. Der står du fritt til å forgrene løsningen og gjøre egne modifikasjoner om du ønsker det (for å illustrere et poeng eller lignende). Som alt annet på bloggen er koden lisensiert under Creative Commons.


comments powered by Disqus