Ping Ring del 3: Ruby
- September 7th, 2010
- Permalink
- 1 kommentar
Dette er del 2 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.
Neste språk ut er Ruby. Denne implementasjonen er logisk sett identisk med C#-varianten fra del 2. TCP-logikken er ørlite enklere (jeg trenger ingen StreamReader/Writer), og Ruby’s kodeblokker gjør tråd-logikken mere elegant å sette opp enn med C#’s lambda-uttrykk pluss et kall til start(). Men i bunn og grunn er det liten forskjell i hvor enkelt det er å løse oppgaven.
Hovedforskjellen er syntaksen. Ruby’s dynamiske typing gjør at vi unngår endel av hva jeg vil kalle støy. Syntaksen føles mere “komprimert” i Ruby, og det ser man igjen i bl.a. færre linjer kode. Min Ruby-variant “klokker seg inn” på 59 linjer – mot C# sine 90.
2
3 class RingServer
4 def initialize
5 @self_port = ARGV.shift
6 @other_port = ARGV.shift
7 @max_delay = ARGV.shift.to_i
8 @should_send_startup_ping = ARGV.shift == “true“
9 @last_ping_time = Time.now
10 puts “** Ruby Ring Server (#{@self_port})“
11 end
12 def start
13 send_delayed_ping if @should_send_startup_ping
14 start_missing_ping_alert_thread
15 start_ping_listener_thread
16 Thread.list.each { |t| t.join unless t == Thread.main }
17 end
18 def send_delayed_ping
19 Thread.new do
20 sleep 1
21 begin
22 client = TCPSocket.new(‘127.0.0.1‘, @other_port)
23 client.print “Ping from #{@self_port}“
24 rescue
25 puts “*** Failed sending ping: #{$!}“
26 else
27 client.close
28 end
29 end
30 end
31 def start_missing_ping_alert_thread
32 Thread.new do
33 while true
34 sleep 5
35 ping_delay = Time.now – @last_ping_time
36 if ping_delay > @max_delay
37 puts “*** ALERT, RING BROKEN! No ping in #{ping_delay} seconds.“
38 send_delayed_ping # try to wake up ring
39 end
40 end
41 end
42 end
43 def start_ping_listener_thread
44 Thread.new do
45 listener = TCPServer.new(‘127.0.0.1‘, @self_port)
46 while session = listener.accept
47 process_incoming_ping session.gets
48 session.close
49 end
50 end
51 end
52 def process_incoming_ping message
53 @last_ping_time = Time.now
54 puts “Received #{message}“
55 send_delayed_ping
56 end
57 end
58
59 RingServer.new.start
Det vil være riktig av deg å påpeke at jeg ikke bruker blanke linjer på samme måte i denne koden som jeg gjorde i C#, og at det derfor er naturlig at det blir færre linjer. Men ta en titt nedenfor, hvor jeg har satt opp de to implementasjonene side om side. Her har Ruby-varianten fått seg endel linjeskift, slik at metodene som gjør det samme starter på samme linjenummer.

Det er ikke tvil om hvilken versjon jeg synes er mest elegant. Men elegansen ligger altså i at C#-syntaksen har for mye støy og staffasje, ikke at det var spesielt mye enklere å implemetere i Ruby.
Jeg må derimot få påpeke at Ruby gir meg større fleksibilitet i hvordan jeg ønsker å organisere koden. Jeg valgte å bruke den samme strukturen som C#-varianten denne gangen; én klasse med seks metoder som løste helt konkrete behov. Jeg kunne derimot for eksempel ha valgt en klasse-løs implementasjon – uten å miste noe lesbarhet av betydning. Ruby egner seg etter mening veldig godt til “små skripts” som dette, hvor kravet til “innpakning” (encapsulation) ikke er så stort.
Alt i alt: Ikke en veldig stor forskjell, men Ruby trekker det lengste strådet! I neste del vil du få se programmet i et språk som har enda finere og mere kompakt syntaks, så følg med..
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.


For å starte jobber i egne tråder bruker jeg Thread-klassen sin konstruktør som kan ta imot en lambda-funksjon, for så å kalle Start() på tråden. Lambda’en for lytteren og alerteren inneholder uendelige løkker (while true). I RingServer.Start() kaller jeg til slutt Join() på en av trådene slik at programmet blir stående og vente på at tråden skal bli ferdig, noen den selvfølgelig aldri gjør.
Ping-sending:
I går lanserte jeg versjon 0.1 av et lite open source bibliotek jeg har valgt å kalle QuickBencher. Dere finner det på ![CropperCapture[39] CropperCapture[39]](http://blog.kjempekjekt.com/wp-content/uploads/2009/10/CropperCapture39.png)

Snookiepoof er et lite C# prosjekt jeg har jobbet med de siste ukene, hvor jeg eksperimenterer med ting som OO-design patterns og clean code. Jeg planlegger å skrive endel blogposter fremover basert på erfaringer med dette prosjektet.
