Opgave project ``POP3D''

Structuur van computer programma's II

Dirk Vermeir

Departement Informatica, Faculteit Wetenschappen
Vrije Universiteit Brussel

$Date: 2010/07/09 12:36:19 $


Inhoudsopgave

Achtergrond
Client-server
Multi-threading
Het Post Office Protocol
Eisen
Functionele eisen
Niet-functionele eisen
Hints
Indienen
Vragen

Samenvatting

Dit is de opgave voor het examen van het vak ``Structuur van Computerprogramma's II'' voor de 2de zittijd van het academiejaar 2009-20010.

Achtergrond

Client-server

Het programma maakt gebruik van de normale internet protocollen en standaarden: TCP/IP, DNS. Zeer summier:

  • Een machine op het net heeft een numeriek adres, meestal voorgesteld als een rij van 4 bytes. Bvb. is 134.184.65.2 het internet adres van tinf2.

  • D.m.v. de DNS service kunnen een of meerdere namen geassocieerd worden met een internet adres. Bvb. is tinf2.vub.ac.be de officiële naam van de machine met adres 134.184.65.2. Zulke namen zijn hiërarchisch gestructureerd (de machine ``tinf2'' behoort tot het domein ``vub.ac.be'' dat zelf weer een deeldomein is van ``ac.be'', hetwelk een deel is van het ``top'' domein ``be''. Men kan bvb. d.m.v. het ``nslookup'' programma nagaan wat het adres is dat hoort bij een naam (of omgekeerd).

  • Een machine op het net heeft een aantal poorten waarlangs een verbinding kan gemaakt worden met een poort van een andere machine. Sommige poorten worden, bij conventie, gebruikt voor bepaalde diensten. Bvb. wordt de poort nummer 80 veelal gebruikt door web servers. Wanneer bvb. mozilla (of konqueror, of netscape) vanop de machine 134.184.65.2 (tinf2) een verbinding wenst te maken naar de webserver op elvas.vub.ac.be (adres 134.18.65.31), dan wordt er een verbinding geopend tussen een poort, bvb 8026, op 134.184.65.2 en poort 80 op 134.18.65.31. D.m.v. het tcp protocol kunnen dan data uitgewisseld worden tussen de toepassingen aan beide kanten van de verbinding.

Voor het netwerk aspect kan gebruik gemaakt worden van het dvnet pakket dat o.a. een klasse Socket (voor clients) en SocketServer (voor servers) definieert. Omdat Socket afgeleid is van iostream is het sturen en ontvangen van gegevens via een netwerkverbinding heel makkelijk.

De map client-server-demo bevat een volledig uitgewerkt voorbeeld van een ``echo server'', dit is een server die elke lijn gestuurd door een client terugstuurt, na de tekst te hebben omgezet naar hoofdletters.

Multi-threading

Omdat een typische server toepassing verschillende sessies/clients tegelijkertijd moet kunnen behandelen is het aangewezen om deze parallelle activiteiten te implementeren via verschillende ``threads'' [1].

Voor deze toepassing volstaat het een thread te beschouwen als een proces dat zijn address space deelt met een ander proces (dit is trouwens min of meer de huidige implementatie van threads in linux). De uitvoering van een thread wordt verder geregeld zoals voor een proces; een multi-threaded programma kan dus verschillende activiteiten gelijktijdig uitvoeren. Omdat threads dezelfde address space delen is er natuurlijk een probleem om gemeenschappelijke data structuren te beschermen tegen gelijktijdige toegang. Een simpele oplossing gebruikt daarvoor zgn. ``monitors'' , dat zijn stukken code die slechts door één thread tegelijk kunnen uitgevoerd worden.

Voor ``threads'' en ``monitors'' zijn klassen beschikbaar in de dvthread bibliotheek (Java ondersteunt gelijkaardige klassen). De documentatie voor dvthread bevat ook een programma dat het gebruik van threads en monitors illustreert om concurrente toegang tot een gedeelde buffer te regelen voor verschillende ``reader'' en ``writer'' threads.

Indien een programma een onbekend aantal threads dient op te starten is het nuttig om de nieuwe (sinds versie 0.4.5) constructor functie van Dv::Thread::Thread te gebruiken: indien met de constructor de bool parameter true wordt meegegeven, dan zorgt de dvthread bibliotheek ervoor dat het nieuw aangemaakte Dv::Thread::Thread object automatisch wordt deleted wanneer de corresponderende thread klaar is. Indien geen verdere informatie over de exit status van een thread moet opgevraagd worden, dan spaart het gebruik van deze optie een extra ``manager'' thread klasse uit die anders nodig zou zijn om andere threads te vernietigen, eens ze klaar zijn.

In het onderstaand voorbeeld wordt een klasse Session afgeleid van Dv::Thread::Thread zodanig dat elk Session object op de hierboven beschreven manier vernietigd wordt.

Voorbeeld 1. Een thread klasse afgeleid van Dv::Thread::Thread

     class Session: public Dv::Thread::Thread {
     public:
       /** Een Session kan alleen met deze "factory" functie gemaakt
        * worden. Op die manier wordt vermeden dat per abuus
        * een Session op de stack wordt gemaakt, wat desastreuze
        * gevolgen zou hebben.
        */
       static Session* make(..) { return new Session(..); }
       /** Destructor. */
       virtual ~Session(..) { .. }
       ..
     private:
       /** De echte constructor is private, zie hoger.
        * Het meegeven van de optionele parameter "true" aan de
        * Dv::Thread::Thread constructor zorgt ervoor dat het
        * dvthread pakket zelf een delete zal uitvoeren eens
        * de aangemaakte Thread klaar is.
        */
       Session(..): Dv::Thread::Thread(true) { .. }
       ..
     };
     


Een typische server main loop ziet er dan uit zoals in de onderstaande code.

    while (true) {
      .. // Accept a new connection.
      ..
      (Session::make(..))->start(); // Launch new session thread.
      ..
      }
    

Het Post Office Protocol

E-mail berichten komen typisch aan op een server machine waar ze worden bewaard tot een gebruiker ze ophaalt. Dit laatste gebeurt dikwijls vanop een client machine waarop geen e-mail server beschikbaar is. Het ``post office protocol'' (POP) kan gebruikt worden om de conversatie tussen zo'n client en server machine te regelen.

Pop3 is een eenvoudig protocol dat een UA (user agent) programma toelaat om, vanop een client machine, e-mail berichten van een server af te halen en, in beperkte mate, te manipuleren (op de server). Het POP3 protocol is zeer eenvoudig en niet erg krachtig. Dikwijls zal daarom het IMAP protocol, dat meer mogelijkheden biedt om berichten op de server te behandelen, bvb. te filteren, gebruikt worden.

De specificatie van het POP3 protocol kan gevonden worden in het RFC (Request For Comments) document RFC1939.

Eisen

Functionele eisen

Schrijf[2] een POP3 server programma pop3d dat de meest elementaire POP3 bevelen ondersteund, te weten: USER, PASS, STAT, QUIT, RETR, DELE, LIST, NOOP, UIDL, TOP.

Het starten van de server gebeurt d.m.v. het volgende bevel.

pop3d configuration-file

waarbij de inhoud van het configuratiebestand een aantal parameters vastlegt, zoals getoond in het voorbeeld.

Voorbeeld 2. Voorbeeld pop3d configuratiebestand

     # sample dvpop3d configuration file
     # port: portnumber on which server will be listening for connections
     port=9999
     # top: top directory
     top=/home/dvermeir/pop3d/
     # logfile: where log messages are written to
     logfile=dvpop3d.log
     # timeout is in seconds
     timeout=600
     


Een lijn in het configuratiebestand is dus ofwel een commentaarlijn, die begint met het teken '#', ofwel een definite van de vorm naam=waarde waarbij naam en waarde één van de betekenissen uit de onderstaande tabel hebben.

Tabel 1. Configuratie-elementen

NaamWaarde
portnummer van de poort gebruikt door de server
logfilename van het bestand voor logboodschappen
topname van de map waarin de usermappen staan
timeoutaantal seconden dat de server wacht op client input, indien er gedurende timeout seconden geen input was moet de server de verbinding verbreken


Opmerkingen:

  • De server gebruikt top als de naam van een map waaronder zich weer gebruikersmappen bevinden, één per gebruiker. Als bvb top verwijst naar /home/fred/pop3 en lisa is een gebruiker, dan zullen de berichten voor lisa zich bevinden in de map /home/fred/pop3/lisa. Een bericht komt overigens overeen met een file. Een tar bestand met een aantal berichten die als test kunnen gebruikt worden is hier te vinden.
  • Voor de implementatie van het UIDL bevel kan de relatieve naam van het bestand gebruikt worden als identificatie van het bericht. Dus, als /home/fred/pop3/lisa/9999 de bestandsnaam is van een bericht dan kan 9999 als identificatie (voor het UIDL bevel) gebruikt worden.
  • Het USER bevel zal nagaan of de aangegeven gebruiker bestaat, m.a.w. of er een map bestaat met die naam onder top. Voor dit project mag het PASS bevel altijd lukken (op voorwaarde dat al zeker is dat de gebruiker bestaat).
  • Om te testen of de server goed werkt kan men een UA progamma, bvb. mozilla instellen zodat de eigen pop server gebruikt wordt (vergeet niet het correcte poortnummer op te geven, de standaard poort voor POP3 is immers niet beschikbaar voor gewone gebruikers).

Niet-functionele eisen

  • Het programma moet volledig en uitsluitend in C++ geschreven worden.
  • Het programma moet gebaseerd zijn op een object-georiënteerd ontwerp, volgens de richtlijnen in Hoofdstuk 11 van het handboek. Functies die langer zijn dan 30 lijnen zullen met argwaan worden bekeken.
  • Het programma moet correct werken op wilma .
  • Enkel de C++ (of C) standaard library en de dvutil, dvnet en dvthread bibliotheken mogen gebruikt worden. In dvutil bevinden zich diverse klassen (bvb. DateFile, logstream) en functies (bvb. make_daemon) die vermoedelijk in dit project nuttig kunnen zijn.
  • Eigen implementaties van containers zijn niet toegestaan, enkel standaard library containers zoals map, set, vector, mogen gebruikt worden.
  • De source (C++) bestanden dienen de suffix ``.cc'', ``.C'' of ``.cpp'' te gebruiken. De ``header files'' met declaraties dienen de suffix ``.h'' te gebruiken.
  • Alle source bestanden, samen met een Makefile, dienen zich in een map stru2.tar te bevinden. De volgende reeks van bevelen zal resulteren in een correct uitvoerbaar programma ``pop3d'':
                    mkdir test
                    cd test
                    tar xvf ../stru2.tar
    		make clean
    		rm -f pop3d *.o
    		make all
    		
  • Het programma moet robuust zijn. Dit wil zeggen dat het ook een redelijke actie moet ondernemen indien de input extreem of foutief is.

Hints

  • Het locken van een maildrop kan geimplementeerd worden door bij te houden welke threads met welke maildrop (user map) bezig zijn. Vergeet niet die datastructuur te beschermen d.m.v. een (klasse afgeleid van) Dv::Thread::Monitor.
  • De file messages.tar.gz bevat een directory messages met 57 correcte e-mail berichten. Dit is nuttig om een test configuratie op te zetten:
    	 tar zxf $HOME/messages.tar.gz 
    	 mkdir top
    	 mkdir top/lisa
    	 cp $HOME/messages/* top/lisa
    	 mkdir top/fred
    	 cp $HOME/messages/* top/fred
    	 
  • Een proefimplementatie (na het lezen van RFC1939) duurde ongeveer 5 uur. Voor testen en documenteren was nog 2 uur nodig.
  • Om het configuratiebestand te parsen is Dv::Props nuttig.
  • Eenvoudig testen kan met telnet.
    	 telnet localhost 9999
    	 +OK pop3d
    	 USER fred
    	 ...
    	 
    Voor een degelijke test dient men een UA progamma, bvb. mozilla in te stellen zodat de zelfgemaakte pop server gebruikt wordt (vergeet niet het correcte poortnummer op te geven, de standaard poort voor POP3 is immers niet beschikbaar voor gewone gebruikers).

Indienen

De uiterste indiendatum (stuur tar bestand -- zie hoger -- via e-mail naar ) is zondag 15/8/2010, 23:59. Een nette afdruk van een document dat uw ontwerp (hoe en waarom) kort beschrijft moet vóór die datum op het secretariaat van de vakgroep afgegeven worden. Later volgt dan een (individuele) mondeling verdediging.

Vragen



[1] Idem dito voor de server die naar verschillende sockets tegelijk moet kunnen luisteren.

[2] Functionele eisen leggen vast wat het programma moet doen. Niet-functionele eisen leggen vast aan welke andere voorwaarden het programma moet voldoen, bvb. onder welk operating system het moet werken, welke programmeertalen of bibliotheken mogen gebruikt worden, etc.