Ping Ring Contribs
- Thursday, September 23rd, 2010
- Skriv en kommentar
Dette er del 9 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.
Samtidig som jeg startet denne artikkelserien utfordret jeg også folkene på Norsk Freakforum til å implementere den samme oppgaven i språk som jeg selv ikke behersket. Og flere var villige til å bidra. Denne episoden av Ping Ring tar for seg disse implementasjonene.
Programmeringsdelen av Freakforum er et sted hvor man kan gjøre alt fra å diskutere hvorvidt Python eller VB.NET er det beste språket å starte med for ferske utviklere (og det er i alle fall ikke Python), til å løse skoleoppgavene til folk. Jeg tar turen innom av og til, for det hender faktisk det dukker opp interessante ting. Det er mange der som ønsker å lære mer programmering, og da kan det være givende å hjelpe til litt innimellom.
BASIC
Det første bidraget jeg fikk var i PureBasic, en komersiell BASIC-variant som er tilgjengelig på Windows, Linux, MaC OS X og faktisk også Amiga. Løsningen ble laget av Kråkelefse, og er tilgjengelig fra min github repo. Her er et utdrag:
51 Procedure PingAlarmThread(ThreadVoid)
52 Shared FriendPort, NextPing, LastPing, AlarmDelay
53 Repeat
54 If LastPing And ElapsedMilliseconds()-LastPing > AlarmDelay
55 PrintN(“Weeep! I’m so lonely… No one pinged me for quite some time.”)
56 Ping(FriendPort)
57 LastPing = ElapsedMilliseconds()
58 EndIf
59 Delay(1000)
60 ForEver
61 EndProcedure
…
Størrelsesmessig (kodelinjer og bytes) er det en av de lengre implementasjonene, omtrent på nivå med C# og Erlang. Men BASIC skal jo være enkelt å forstå, og løsningen er ganske ryddig og fin – absolutt mye bedre enn Erlang-varianten på det området.
Assembly (galskap)
Neste bidrag er noe ganske anderledes, nemlig en implementasjon i FASM (flat assembler) for x86-64, skrevet av Akhkharu. Det begynte med en versjon på 324 linjer, men etter å ha laget noen “litt fikse macroer” fikk han ned antallet til 199. Her følger et lite utdrag som kanskje gir deg følelsen av hvordan det arter seg å skrive assembler-kode. Hele kilden finner du her.
106 surveillance:
107 mov qword [rsp], 5
108 mov qword [rsp + 8], 0
109 .conts: syscall SYS_NANOSLEEP, nocheck, rsp->rdi, rsp->rsi
110 cmp eax, -1
111 je .conts
112
113 syscall SYS_GETTIMEOFDAY, check, rsp->rsi, ~esi
114
115 mov rax, [rsp]
116 mov rdx, [lastPing]
117 add rdx, r14
118 cmp rax, rdx
119 jle surveillance
120
121 call write, timeout&>rdi
122 clone senderStack, sender
123 jmp surveillance
…
Akhkharu postet denne koden med den passende kommentaren: “… og det var nok masochisme for i år.”
PHP (er det mulig da?)
Og så til et bidrag jeg ikke trodde skulle komme. |d13m0b har gjort et forsøk på å implementere en Ping Ring i PHP. Han advarer om at koden kan inneholde feil, for den ble skrevet på vorspiel. Men den illustrerer uansett hvordan en dyktig utvikler kan ta et verktøy beregnet for en bestemt oppgave (som å slå inn spiker), og gjøre noe helt annet med den (som å pusse tennene).
Utvikleren forklarer:
Når man skal skrive dette programmet i php oppstår det et par små problemer; PHP har ikke threading. I alle fall ikke threading i den forstand man forbinder med “normale” språk.
Det man er nødt til da er å forke, og det som skjer er at prosessen spawner en ny prosess (som er nøyaktig samme scriptet), og man må sjekke PID for å bestemme hva som er child eller parent og deretter gjøre handling ut i fra dette. Igjennom php kan man aksessere kallet med pcntl_fork. Slik jeg har satt det opp vil det lages 3 prosesser. En for PingAlertProcesser og en for PingListener, og det “opprinnelige” scriptet er parent og holder øye med barna om de dør eller ikke.
Så man har fått “threading” av veien, og så støter man imidlertid på en aldri så liten utfordring til. Hvordan får man delt variabler mellom scriptene? Man kan selvfølgelig serialisere og dumpe til en fil som alle scriptene har tilgang til, men dette er ikke akkurat noen optimal løsning.
Heldigvis har php noe matnyttig man kan bruke; Semaphore. Dette er en wrapper til SysV sine IPC-kall. Vi sitter altså med en modul som kan håndtere minnet mellom uavhengige prosesser (getLastPing og setLastPing).”
|d13m0b har på en måte nesten gjennoppfunnet Erlang-løsningen i PHP :) Den fullstendige kildekoden er på 217 linjer, og er tilgjengelig her. Her er en smakebit fra semafor-koden:
172 function hasReceivedPing()
173 {
174 global $handle;
175 global $buffer;
176
177 sem_acquire( $handle );
178 $hasReceivedPing = shm_get_var($buffer, 2 );
179 sem_release( $handle );
180
181 return $hasReceivedPing;
182 }
183
184 function setReceivedPing( $value )
185 {
186 global $handle;
187 global $buffer;
188
189 sem_acquire( $handle );
190 shm_put_var( $buffer, 2, $value );
191 sem_release( $handle );
192 }
…
Bidragsyteren avslutter med følgende kommentar om å implementere Ping Ring eller lignende slik han har gjort:
“Ikke gjør det i php, og ikke gjør det i fylla.”
Haskell
Jeg fikk også se en implementasjon i Haskell, og den er tilgjengelig i sin helhet her. Koden inneholder endel unicode, men jeg gjør et forsøk på å rendrere et lite utdrag (krysser fingrene):
68 pingSurveillance ∷ State → IO ()
69 pingSurveillance s@State {timeout, sendPing, lastPing} = do
70 threadDelay 5000000
71 t ← readMVar lastPing
72 now ← getClockTimeMS
73 when (now - t > timeout * 10^6) $ do
74 putStrLn $ “ALARM! It has been “ ⊕ show ((now - t) `div` 10^6)
75 ⊕ ” seconds since last ping”
76 putMVar sendPing ()
77 pingSurveillance s
…
Akhkharu, som leverte denne løsningen også, innrømmet at han ikke så noen fordel med å implementere Ping Ring i Haskell. Koden er ifølge tallene like lite kompakt som Erlang-versjonen.
I tillegg til de implementasjonene dere har fått sett smakebiter av her inneholder Ping Ring contrib-repositorien på Github også tre ulike Python-implementasjoner. Ta en titt, og se om du blir inspirert til å implementere din egen ring i det språket du liker best. Eller kanskje du skal prøve deg på et språk du aldri har gjort noe i før? Hvorfor ikke?!!
Tidligere i serien: Introduksjon | C# | Ruby | Boo | Erlang | Clojure | Clojure m/Agenter | Python.
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.
Kategorier: NNUG / community, Polyglot.
RSS feed for kommentarene.
Tilbaketråkk.



September 23rd, 2010 at 9:51 am
Her er et forsøk på å lage den i C# med .net 4 pluss Reactive Extensions (System.Linq.Observable)
Har bare såvidt startet å se på RX så det kan sikkert gjøres mye bedre.
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; public class RingServerRX { private DateTimeOffset _lastPing = DateTimeOffset.Now; public TimeSpan MaxDelay { get; set; } public int OtherPort { get; set; } public int ThisPort { get; set; } public static void Main(string[] args) { Console.WriteLine(\"** C# Ring Server ({0})\", args[0]); var cleanupHandle = new RingServerRX // Parse arguments and start server.. { ThisPort = int.Parse(args[0]), OtherPort = int.Parse(args[1]), MaxDelay = new TimeSpan(0, 0, int.Parse(args[2])) }.Start(bool.Parse(args[3])); Console.ReadLine(); cleanupHandle.Dispose(); } public IDisposable Start(bool sendStartupPing) { if (sendStartupPing) Task.Factory.StartNew(DelaySendPing); var tcpListener = new TcpListener(IPAddress.Parse(\"127.0.0.1\"), ThisPort); tcpListener.Start(); var asyncGetTcpClient = Observable.FromAsyncPattern(tcpListener.BeginAcceptTcpClient, tcpListener.EndAcceptTcpClient); var tcpClientObservable = Observable.Defer(asyncGetTcpClient).DoWhile(() => true); return tcpClientObservable.Select(tcpClient => { using (tcpClient) using (var streamReader = new StreamReader(tcpClient.GetStream())) return streamReader.ReadToEnd(); } ).Timestamp().BufferWithTimeOrCount(TimeSpan.FromSeconds(1), 1).Subscribe(ProcessIncomingPings); } private void DelaySendPing() { try { Thread.Sleep(1000); using (var tcpClient = new TcpClient(\"127.0.0.1\", OtherPort)) using (var streamWriter = new StreamWriter(tcpClient.GetStream())) streamWriter.Write(\"PING sendt from {0}\", ThisPort); } catch (Exception ex) { Console.WriteLine(\"*** Failed sending ping: {0}\", ex.Message); } } private void ProcessIncomingPings(IList> pingList) { if (pingList.Any()) { foreach (var item in pingList) Console.WriteLine(\"Received {0} on thread {1} at {2}\", item.Value, Thread.CurrentThread.ManagedThreadId, item.Timestamp); this._lastPing = pingList.Max(_ => _.Timestamp); Task.Factory.StartNew(DelaySendPing); } var timeSinceLast = DateTimeOffset.Now - this._lastPing; if (timeSinceLast > timeSinceLast) return; Console.WriteLine(”*** ALERT, RING BROKEN! No ping in {0} seconds”, timeSinceLast.TotalSeconds); Task.Factory.StartNew(DelaySendPing); } }September 23rd, 2010 at 11:27 am
Var da veldig til nedrakking av Python…
September 23rd, 2010 at 11:41 am
Jostein: Jeg tror rett og slett jeg ble litt skuffet da jeg endelig forsøkte meg på noe i Python.., så ta det med en klype salt ;)
September 23rd, 2010 at 12:13 pm
Torbjørn, for en fantastisk serie :)
Jeg skal ikke si på meg at jeg har skjønt hver implementasjon, men konseptet med å *faktisk* prøve det ut i forskjellige språk er sinnsykt kult. Contrib’ene er vel bevis på at andre er enige :)
Tøft gjort!
January 2nd, 2011 at 11:39 pm
[...] Ping RingDette var et lite prosjekt hvor jeg ville se hvordan ulike programmeirngsspråk håndterte å løse den samme oppgaven.IntroduksjonDel 2: C#Del 3: RubyDel 4: BooDel 5: ErlangDel 6: ClojureDel 7: Clojure m/agenterDel 8: PythonContribs [...]