Next Previous Contents

6. Annet

Til nå har vi fort tatt for oss det nødvendigste, og man kan forsåvidt løpe ut og skrive noen webapplikasjoner nå. Men HTML og en database utgjør ikke det mest spennende i verden, så la oss titte dypere ned i verktøykassa.

6.1 E-post

Hva er vel en webapplikasjon som ikke kan spamme brukerne sine?

  1| require 'net/smtp'
  2| til = 'kent_dahl@hotmail.com'  # Ikke /dev/null, men nært nok.
  3| fra = 'kentda@pvv.org'
  4| Net::SMTP.start('smtp.pvv.ntnu.no') do |smtp|
  5|   tekst = [ "To: #{til}\n", 
  6|     "Subject: En liten test e-post\n", "\n",
  7|     "Hei, hei, alle sammen. Har vi det bra dere?\n"
  8|   ]  
  9|   smtp.sendmail( tekst,  fra, [til] )
 10| end 

Vi angir mottakerne med en Array, slik at vi kan sende en e-post til flere om gangen. I dette tilfellet kunne vi sendt en String direkte, siden det bare var en.

Dersom man skal skrive en stor e-post og ikke ønsker å bruke så mye minne på å bygge den opp i en String eller Array før man sender, kan man benytte instansmetoden Net::SMTP#ready etter å ha startet koblingen. Den tar en blokk med et argument, et adapter-objekt som du kan skrive til fortløpende.

6.2 Tråder

Ruby har et lett lite trådsystem internt. Det kjører fullstendig inne i fortolkeren, benytter ikke plattformspesifikke tråder, kan ikke dra nytte av flere CPUer og dersom en tråd kaller en C-metode som blokker, kan det sulte ut alle de andre trådene også. Men dersom mye av prosesseringen du gjør skjer i Ruby, er de veldig nyttige.

  1| puts 'A1'
  2| traad = Thread.new do
  3|   puts 'B1'
  4|   sleep 2
  5|   puts 'B2'
  6| end
  7| puts 'A2'
  8| puts traad.join  # Vent på at tråden avslutter.
  9| # Utput blir: A1 B1 A2 B2

Merk at når hovedtråden (main) avslutter, drepes alle andre tråder. Man må kalle join eller lignende på trådene om man ønsker å vente på dem.

Ønsker man å returnere en verdi fra en tråd, kan man kalle value i stedet for join.

  1| pi_traad = Thread.new do
  2|   # Regn ut PI til ørten desimaler ...
  3|   puts 'Regne, regne, regne!'
  4|   sleep 5   # Vanlig student-innsats fra denne tråden...
  5|   Math::PI  # ... og tror du ikke den koker også!
  6| end
  7| puts 'Tråden går i bakgrunnen.'
  8| print 'PI er ', pi_traad.value  # Vent på returverdien fra tråden.

NB: Unntak som heves i tråder vil svinne hen i stillhet, med mindre vi eksplisitt venter på at tråden skal avslutte, eller Thread.abort_on_exception er satt til true.

Tråder som sandkasser

Tråder kan også brukes som sikre(re?) sandkasser. Husker du $SAFE-variabelen fra tidligere? Den er ikke en global variabel, men en tråd-lokal variabel. Hver tråd har sitt eget sikkerhetsnivå, og det kan vi benytte oss av for å kjøre "skumle" kodebiter i en noe tryggere "sandkasse".

  1| # Pakker sandkasselekingen i en metode slik at det 
  2| # ikke vil være (mange) variabler tilgjengelig i konteksten.
  3| def sandkasse_lek( kode )
  4|   sandkasse = Thread.new do
  5|     # "evil" eval kan være skummel, så la oss være paranoide.
  6|     $SAFE = 4   # Nivå 1-3 lar oss ikke bruke eval på  
  7|     eval kode   # besudlet data, men det gjør nivå 4!
  8|   end
  9|   print 'Koden din returnerte: ', sandkasse.value.inspect, "\n"
 10| end
 11| 
 12| begin
 13|   print 'Skriv inn vilkårlig Ruby-kode: '
 14|   kildekode = gets.chomp              # Brukerdata er tainted.
 15|   sandkasse_lek( kildekode )
 16| end while kildekode.size.nonzero?

Synkronisering av tråder

Med trådene følger det en håndfull verktøy for synkronisering av tråder; Thread.critical=, Mutex og ConditionVariable.

  1| require 'thread'
  2| $delt_teller = 0
  3| $mutex = Mutex.new
  4| 
  5| # Lag ti tråder som øker den delte telleren 
  6| # gradvis tjuefem ganger.
  7| traader = (1..10).collect do 
  8|   Thread.new do
  9|     25.times do |i|
 10|       $mutex.synchronize do                  ### Synkronisert
 11|         gammel_verdi = $delt_teller            # kodebit.
 12|         ny_verdi = gammel_verdi + 1            #
 13|         sleep 0 # Framtvinge trådproblematikk  #
 14|         $delt_teller = ny_verdi                #
 15|       end                                    ###
 16|     end
 17|   end 
 18| end
 19| 
 20| # Vent på alle trådene før vi skriver ut den endelige verdien.
 21| traader.each{|t| t.join}
 22| puts $delt_teller # Skal skrive ut "250"

Det er flere synkroniseringsverktøy tilgjengelig, slik som Queue, SizedQueue, Synchronizer og Monitor for å nevne noen.

6.3 Distribuert Ruby - druby

Distribuert Ruby (druby a.k.a. DRb) lar oss kommunisere og samhandle enkelt med andre Ruby-programmer over nettet, ikke ulikt RMI, CORBA ( rinn) eller XML-RPC ( xmlrpc4r) og lignende.

Et lite eksempel hvor vi deler ut et tjenerobjekt over nettet. Serveren:

  1| require 'drb'
  2| class Tjener
  3|   def si_hei
  4|     puts 'Hei!'
  5|     return 'Hei fra tjeneren!'
  6|   end
  7| end
  8| tjener = Tjener.new
  9| DRb.start_service( 'druby://localhost:4242', tjener )
 10| DRb.thread.join

... hvor vi må vente på at druby sin tråd skal avslutte, slik at serveren ikke stopper med en gang.
Dernest kobler vi oss opp med en klient:

  1| require 'drb'
  2| DRb.start_service
  3| tjener = DRbObject.new( nil, 'druby://localhost:4242' )
  4| svar = tjener.si_hei
  5| print "Fikk fra tjener: '", svar, "'.\n"

Merk at vi angir nil som første argument til DRbObject-konstruktøren, siden vi ikke har et ordentlig objekt på "vår" side.

Verdioverføring og referanseoverføring

Oversending av parametre og returnverdier i distribuerte systemer deles i to hovedkategorier: verdioverføring (pass-by-value) og referanseoverføring (pass-by-reference).

Standard i druby er at objekter sendes ved verdioverføring. Objektene serialiseres (se Marshal-modulen), sendes over nettet og en kopi opprettes på mottakerens side. Dersom objektet ikke kan serialiseres/marshalles, fanges unntaket og det sendes over en referanse. Mottakeren ender da opp med en DRbObject-instans som videresender metodekall over nettet.

druby og videospiller/tvslave-eksemplet


  1| require 'drb'
  2| $hvor = 'i stua'
  3| class Videospiller  
  4|   # Gjør slik at Videospiller ikke kan serialiseres.
  5|   include DRbUndumped 
  6|   def start
  7|     puts "Videospiller: 'Press play on tape #{$hvor}.'"
  8|   end
  9| 
 10|   class Kontroll 
 11|     def initialize( spiller )
 12|       @spiller = spiller
 13|     end    
 14| 
 15|     def hvor_er_du?
 16|       puts "Kontroll: 'Jeg er #{$hvor}.'"
 17|     end
 18|     def start
 19|       @spiller.start
 20|     end
 21|   end # Kontroll
 22| 
 23| end # Videospiller
 24| 
 25| class Tjener 
 26|   def initialize
 27|     @spiller = Videospiller.new
 28|   end
 29|   def ny_fjernkontroll     # pass-by-value
 30|     Videospiller::Kontroll.new( DRbObject.new( @spiller ) )  
 31|   end
 32|   def ny_kontroll          # pass-by-reference
 33|     Videospiller::Kontroll.new( @spiller )
 34|   end
 35|   def hent_streng   # pass-by-value
 36|     'En streng, en streng, mitt kongerike for en streng.'
 37|   end
 38|   def hent_standard_utput  # pass-by-reference da IO er en av
 39|     $stdout                # klassene som ikke kan serialiseres.
 40|   end
 41| end
 42| 
 43| if __FILE__ == $0 then # Start tjeneren med våre utvidelser, og
 44|   load 'drb_server.rb' # gjenbruker koden fra forrige eksempel.
 45| end



  1| require 'drb_videospiller' # Trenger definisjonen til Kontroller
  2| $hvor = 'på soverommet'
  3| DRb.start_service
  4| vcr_tjener = DRbObject.new( nil, 'druby://localhost:4242' )
  5| 
  6| tjener_utput = vcr_tjener.hent_standard_utput 
  7| puts_begge_steder = proc do |tekst|
  8|   puts tekst
  9|   tjener_utput.puts tekst
 10| end
 11| 
 12| puts_begge_steder.call('--Fjernkontrollen kan vi ta med inn på soverommet')
 13| fjernkontroll = vcr_tjener.ny_fjernkontroll
 14| p fjernkontroll
 15| fjernkontroll.hvor_er_du? #=> Kontroll: 'Jeg er på soverommet.'
 16| fjernkontroll.start       #=> Videospiller: 'Press play on tape i stua.'
 17| 
 18| puts_begge_steder.call('--Mens kontrollen som er tett knyttet til ' +
 19|                        "videospilleren\n--må forbli i stua.")
 20| # Så vi får bare en referanse til, og ikke en kopi av, kontrollen.
 21| kontroll = vcr_tjener.ny_kontroll
 22| p kontroll
 23| kontroll.hvor_er_du?  #=> Kontroll: 'Jeg er i stua.'
 24| kontroll.start        #=> Videospiller: 'Press play on tape i stua.'

Apache-prosesser og druby-server

Dette kan benyttes for å flytte logikk i fra Apache-prosessene og over i en samlet druby-prosess.

Arkitektur hvor logikken separeres fra mod_ruby-prosessene.


Next Previous Contents