original in en: Leonardo Giordani
en to nl: Guus Snijders
Ik heb juist mijn diploma ontvangen van de Faculteit van Telecommunicatie Engineering in Politecnico in Milaan, ben geïnteresseerd in programmeren (voornamelijk Assembly en C/C++). En sinds 1999 werk ik bijna alleen maar met Linux/Unix.
Het is misschien een goed idee om ook enkele van de voorgaande artikelen in deze serie eerst te lezen:
We hadden al gezegd dat een protocol een set regels is die mensen of machines in staat stellen te praten, zelfs als ze anders zijn. Zo is Engels bijvoorbeeld een protocol, omdat het me in staat stelt te spreken tot mijn Indische lezers (die altijd erg geïnteresseerd zijn in wat ik schrijf). Een iets meer Linux-gerelateerd voorbeeld, is het hercompileren van je kernel (wees niet bang, het is niet zo moeilijk), waarbij je zeker de Networking sectie zult opmerken, waar je je kernel verschillende netwerk protocollen kunt laten ondersteunen, zoals TCP/IP.
Om een protocol te creëren, zullen we moeten beslissen wat voor soort applicatie we zullen ontwikkelen. Deze keer zullen we een eenvoudige telefoon switch simulator bouwen. Het main proces zal de telefoon switch zijn, en de 'zoon' processen zullen zich gedragen als gebruikers: we zullen gebruikers berichten naar elkaar laten sturen door de switch.
Het protocol zal drie verschillende situaties behelsen: de geboorte van een gebruiker (dwz de gebruiker bestaat en is verbonden), het normale werk van de gebruiker, en de dood van een gebruiker (hij is niet langer verbonden). Laten we spreken over deze drie situaties:
Als een gebruiker verbindt met het systeem, creëert hij zijn eigen berichtwachtrij (vergeet niet dat we het hebben over processen), de identifiers moeten naar de switch worden gestuurd om deze te laten weten hoe te communiceren met deze gebruiker. Hier heeft het de tijd om een aantal data structuren te creëren, indien nodig. Het ontvangt van de switch de indentifier van de wachtrij waar hij de berichten heen kan schrijven die door de switch verstuurd moeten worden naar andere gebruikers.
De gebruiker kan berichten versturen en ontvangen. Als hij een bericht ontvangt van een andere gebruiker, kunnen er twee situaties onstaan: de ontvanger is verbonden, of niet. We besluiten dat in beide gevallen een bevestiging moet worden verstuurd naar de zender, om deze te laten weten wat er gebeurt met zijn bericht. Dit vereist geen acties van de ontvanger zelf, de switch zou dit moeten doen.
Als een gebruiker de verbinding met het systeem verbreekt, zou hij de switch moeten informeren, maar verder zijn er geen acties nodig. De metacode om deze manier van werken te beschrijven, is als volgt:
/* Birth */ create_queue init send_alive send_queue_id get_switch_queue_id /* Work */ while(!leaving){ receive_all if(<send condition>){ send_message } if(<leave condition>){ leaving = 1 } } /* Death */ send_dead
Nu dienen we het gedrag van onze telefoon switch te bepalen: als een gebruiker verbindt, stuurt deze ons een bericht met de identifier van zijn bericht wachtrij; dus, dienen we deze op te slaan om berichten voor deze gebruiker af te leveren en te antwoorden met de identifier van een wachtrij waar hij zijn bericht kan laten die we naar andere gebruikers moeten sturen. Dan moeten we alle ontvangen berichten analyseren en controleren of de ontvangers aanwezig zijn: als de ontvanger verbonden is, moeten we het bericht versturen, als de ontvanger niet verbonden is, moeten we het bericht verwijderen; in beide gevallen moeten we de zender bevestigen. Als een gebruiker verdwijnt verwijderen we simpelweg de identifier van zijn wachtrij, zodat deze onbereikbaar wordt.
Weer, onze metacode implementatie is
while(1){ /* New user */ if (<birth of a user>){ get_queue_id send switch_queue_id } /* User dies */ if (<death of a user>){ remove_user } /* Messages delivering */ check_message if (<user alive>){ send_message ack_sender_ok } else{ ack_sender_error } }
Het eerste ding om te doen is een structuur te creëren voor ons bericht met het kernel prototype van msgbuf
typedef struct { int service; int sender; int receiver; int data; } messg_t; typedef struct { long mtype; /* Tipo del messaggio */ messg_t messaggio; } mymsgbuf_t;
Dit is iets algemeens dat we later kunnen uitbreiden: de zender en ontvanger velden bevatten een gebruikers id en het data veld bevat de eigenlijke data, terwijl het service veld wordt gebruikt om een service van de switch aan te vragen. We zouden ons bijvoorbeeld kunnen voorstellen twee services te hebben: een voor onmiddelijke en een voor vertraagde aflevering, in welk geval het data veld het aantal seconden vertraging zou kunnen transporteren. Dit is slechts een voorbeeld, maar laat ons zien dat het service veld ons vele mogelijkheden oplevert.
Nu kunnen we een aantal functies implementeren om onze data structuren te beheren, vooral om de velden van de berichten te zetten en te krijgen. Deze functies zijn allemaal min of meer gelijk, dus geef ik er hier maar twee, de andere zijn te vinden in de .h files
void set_sender(mymsgbuf_t * buf, int sender) { buf->message.sender = sender; } int get_sender(mymsgbuf_t * buf) { return(buf->message.sender); }
Het doel van deze functies is niet om de code te beperken (ze bestaan uit slechts 1 regel code): ze zijn er om ons hun bedoeling te herinneren en laten het protocol dichter bij menselijke taal komen, en dus eenvoudiger in gebruik.
Nu moeten we de functies schrijven om IPC keys te genereren, bericht wachtrijen te creëren en te verwijderen, berichten te vesturen en te ontvangen: het bouwen van een IPC key is simpel
key_t build_key(char c) { key_t key; key = ftok(".", c); return(key); }
Then the function to create a queue
int create_queue(key_t key) { int qid; if((qid = msgget(key, IPC_CREAT | 0660)) == -1){ perror("msgget"); exit(1); } return(qid); }
Zoals je kunt zien is fout beheer in dit geval erg simpel. De volgende code vernietigt een wachtrij
int remove_queue(int qid) { if(msgctl(qid, IPC_RMID, 0) == -1) { perror("msgctl"); exit(1); } return(0); }
En tenslotte de functies om berichten te versturen en te ontvangen: een bericht sturen betekend voor ons het schrijven ervan naar een bepaalde wachtrij, bijvoorbeeld degene die ons gegeven is door de switch.
int send_message(int qid, mymsgbuf_t *qbuf) { int result, lenght; lenght = sizeof(mymsgbuf_t) - sizeof(long); if ((result = msgsnd(qid, qbuf, lenght, 0)) == -1){ perror("msgsnd"); exit(1); } return(result); } int receive_message(int qid, long type, mymsgbuf_t *qbuf) { int result, length; length = sizeof(mymsgbuf_t) - sizeof(long); if((result = msgrcv(qid, (struct msgbuf *)qbuf, length, type, IPC_NOWAIT)) == -1){ if(errno == ENOMSG){ return(0); } else{ perror("msgrcv"); exit(1); } } return(result); }
Dat is alles. Je kunt de functies vinden in het bestand layer1.h: probeer eens een programma (bijvoorbeeld dat van het vorige artikel) te schrijven met behulp hiervan. In het volgende artikel zullen het hebben over laag 2 van het protocol en deze implementeren.
Zoals altijd kun je me commentaar, correcties en vragen sturen op mijn mail adres (leo.giordiani(at)libero.it) of via de Talkback pagina. Schrijf me alsjeblieft in Engels, Duits of Italiaans.