Skip to content

9. Programmeren TCP-IP

9.1. Algemeen

9.1.1. Internetprotocollen

Hier geven we een inleiding tot de communicatieprotocollen van het internet, ook wel de TCP/IP-protocolsuite (Transfer Control Protocol / Internet Protocol) genoemd, naar de twee belangrijkste protocollen. Het is raadzaam dat de lezer een algemeen begrip heeft van de werking van netwerken en met name van de protocollen TCP/IP, voordat hij zich bezighoudt met het bouwen van gedistribueerde applicaties.

De volgende tekst is een gedeeltelijke vertaling van een tekst uit het document „Lan Workplace for Dos – Administrator’s Guide” van NOVELL, een document uit het begin van de jaren 90.

-----------------------------------

Het algemene concept van het opzetten van een netwerk van heterogene computers is voortgekomen uit onderzoek uitgevoerd door het DARPA (Defense Advanced Research Projects Agency) in de Verenigde Staten. Het DARPA heeft de reeks protocollen ontwikkeld die bekend staat onder de naam TCP/IP, waarmee heterogene machines met elkaar kunnen communiceren. Deze protocollen zijn getest op een netwerk met de naam ARPAnet, een netwerk dat later het INTERNET-netwerk werd. De protocollen TCP/IP definiëren formaten en regels voor verzending en ontvangst die onafhankelijk zijn van de organisatie van de netwerken en de gebruikte apparatuur.

Het netwerk dat is ontworpen door het DARPA en wordt beheerd door de protocollen TCP/IP is een pakketgeschakeld netwerk. Een dergelijk netwerk verzendt informatie over het netwerk in kleine stukjes die pakketten worden genoemd. Als een computer bijvoorbeeld een groot bestand verzendt, wordt dit in kleine stukjes opgedeeld die over het netwerk worden verzonden om op de bestemming weer te worden samengevoegd. TCP/IP definieert het formaat van deze pakketten, namelijk:

  • herkomst van het pakket
  • bestemming
  • lengte
  • type

9.1.2. Het model OSI

De protocollen TCP/IP volgen grotendeels het open netwerkmodel genaamd OSI (Open Systems Interconnection Reference Model), gedefinieerd door de ISO (International Standards Organisation). Dit model beschrijft een ideaal netwerk waarin de communicatie tussen machines kan worden weergegeven door een model met zeven lagen:

Elke laag ontvangt diensten van de onderliggende laag en biedt haar eigen diensten aan de bovenliggende laag aan. Stel dat twee applicaties op verschillende machines A en B met elkaar willen communiceren: dat doen ze op het niveau van de Application-laag. Ze hoeven niet alle details van de werking van het netwerk te kennen: elke applicatie geeft de informatie die ze wil verzenden door aan de onderliggende laag: de laag Présentation. De applicatie hoeft dus alleen de regels voor de koppeling met de laag Présentation te kennen.

Zodra de informatie zich in de laag Présentation bevindt, wordt deze volgens andere regels doorgegeven aan de laag Session en zo verder, totdat de informatie op het fysieke medium terechtkomt en fysiek naar de bestemmingsmachine wordt verzonden. Daar ondergaat de informatie het omgekeerde proces van wat er op de verzendende machine is gebeurd.

Op elke laag stuurt het verzendproces, dat verantwoordelijk is voor het verzenden van de informatie, deze naar een ontvangstproces op de andere machine die tot dezelfde laag behoort. Dit gebeurt volgens bepaalde regels die het protocol van de laag worden genoemd. We krijgen dus het volgende uiteindelijke communicatieschema:

De rol van de verschillende lagen is als volgt:

Physique
Zorgt voor de overdracht van bits via een fysiek medium. In deze laag bevinden zich eindapparatuur voor gegevensverwerking (E.T.T.D), zoals terminals of computers, evenals apparatuur voor het afsluiten van datacircuits (E.T.C.D), zoals modulators/demodulators, multiplexers en concentrators. De aandachtspunten op dit niveau zijn:
. de keuze van de codering van de informatie (analoog of digitaal)
. de keuze van de overdrachtswijze (synchroon of asynchroon).
Liaison de données
Verbergt de fysieke kenmerken van de fysieke laag. Detecteert en corrigeert transmissiefouten.
Réseau
Beheert het pad dat de via het netwerk verzonden informatie moet volgen. Dit wordt de routage genoemd: het bepalen van de route die informatie moet volgen om bij de ontvanger aan te komen.
Transport
Maakt communicatie tussen twee applicaties mogelijk, terwijl de voorgaande lagen alleen communicatie tussen machines toelieten. Een dienst die door deze laag wordt geleverd, kan multiplexing zijn: de transportlaag kan één en dezelfde netwerkverbinding (van machine naar machine) gebruiken om informatie van meerdere applicaties te verzenden.
Session
In deze laag vinden we diensten waarmee een applicatie een werksessie op een externe machine kan openen en in stand houden.
Présentation
Deze laag heeft tot doel de weergave van gegevens op de verschillende machines te uniformiseren. Zo worden gegevens afkomstig van machine A door de Présentation-laag van machine A in een standaardformaat ‘verpakt’ voordat ze over het netwerk worden verzonden. Zodra de gegevens de laag Présentation van de ontvangende machine B bereiken – die ze dankzij hun standaardformaat herkent – worden ze op een andere manier verpakt, zodat de applicatie van machine B ze kan herkennen.
Application
Op dit niveau bevinden zich de toepassingen die doorgaans dicht bij de gebruiker staan, zoals e-mail of bestandsoverdracht.

9.1.3. Het model TCP/IP

Het model OSI is een ideaal model dat nog nooit is gerealiseerd. De reeks protocollen TCP/IP komt hier dicht bij in de volgende vorm:

Fysieke laag

In een lokaal netwerk wordt doorgaans gebruikgemaakt van Ethernet- of Token-Ring-technologie. We behandelen hier alleen de Ethernet-technologie.

Ethernet

Dit is de naam die wordt gegeven aan een technologie voor lokale, pakketgeschakelde netwerken die begin jaren zeventig bij PARC Xerox werd uitgevonden en in 1978 door Xerox, Intel en Digital Equipment werd gestandaardiseerd. Het netwerk bestaat fysiek uit een coaxkabel met een diameter van ongeveer 1,27 cm en een lengte van maximaal 500 m. Het kan worden uitgebreid met behulp van répéteurs, waarbij twee apparaten niet door meer dan twee repeaters van elkaar gescheiden mogen zijn. De kabel is passief: alle actieve componenten bevinden zich op de apparaten die op de kabel zijn aangesloten. Elk apparaat is via een netwerktoegangskaart met de kabel verbonden, die bestaat uit:

  • een zender (transceiver) die de aanwezigheid van signalen op de kabel detecteert en de analoge signalen omzet in digitale signalen en omgekeerd.
  • een koppelaar die de digitale signalen van de zender ontvangt en deze doorgeeft aan de computer voor verwerking, of omgekeerd.

De belangrijkste kenmerken van de Ethernet-technologie zijn:

  • Capaciteit van 10 megabit/seconde.
  • Bustopologie: alle apparaten zijn aangesloten op dezelfde kabel
  • Broadcast-netwerk – Een computer die gegevens verzendt, stuurt deze via de kabel met het adres van de ontvangende computer. Alle aangesloten computers ontvangen deze gegevens, maar alleen de computer waarvoor ze bestemd zijn, bewaart ze.
  • De toegangsmethode is als volgt: de zender die wil verzenden, luistert naar de kabel – hij detecteert dan of er al dan niet een draaggolf aanwezig is, wat zou betekenen dat er een transmissie aan de gang is. Dit is de CSMA-techniek (Carrier Sense Multiple Access). Als er geen draaggolf aanwezig is, kan een zender besluiten om op zijn beurt te zenden. Er kunnen meerdere zenders zijn die deze beslissing nemen. De uitgezonden signalen vermengen zich: men spreekt dan van een botsing. De zender detecteert deze situatie: terwijl hij naar de kabel zendt, luistert hij tegelijkertijd naar wat er daadwerkelijk over de kabel gaat. Als hij detecteert dat de informatie die over de kabel gaat niet dezelfde is als die hij heeft verzonden, concludeert hij dat er een botsing is en stopt hij met zenden. De andere zenders die aan het zenden waren, zullen hetzelfde doen. Elke zender hervat zijn uitzending na een willekeurige tijd die afhankelijk is van de betreffende zender. Deze techniek wordt CD (Collision Detect) genoemd. De toegangsmethode wordt daarom CSMA/CD genoemd.
  • een 48-bits adressering. Elke machine heeft een adres, hier het fysieke adres genoemd, dat is vastgelegd op de kaart die de machine met de kabel verbindt. Dit adres wordt het Ethernet-adres van de machine genoemd.

Netwerklaag

Op deze laag vinden we de protocollen IP, ICMP, ARP en RARP.

IP (Internet Protocol)
Verzendt pakketten tussen twee knooppunten in het netwerk
ICMP 
(Internet Control Message Protocol)
ICMP zorgt voor de communicatie tussen het protocolprogramma IP van de ene machine en dat van een andere machine. Het is dus een protocol voor het uitwisselen van berichten binnen het protocol IP zelf.
ARP
(Address Resolution Protocol)
zorgt voor de koppeling tussen het internetadres van de machine en het fysieke adres van de machine
RARP
(Reverse Address Resolution Protocol)
zet het fysieke adres van de machine om naar het internetadres van de machine

Transport-/sessielagen

In deze laag bevinden zich de volgende protocollen:

TCP (Transmission Control Protocol)
Zorgt voor een betrouwbare overdracht van informatie tussen twee clients
UDP (User Datagram Protocol)
Zorgt voor een onbetrouwbare overdracht van informatie tussen twee clients

Lagen: Toepassing/Presentatie/Sessie

Hier vindt u diverse protocollen:

TELNET
Terminalemulator waarmee machine A verbinding kan maken met machine B als terminal
FTP (File Transfer Protocol)
maakt bestandsoverdracht mogelijk
TFTP (Trivial File
Transfer Protocol)
maakt bestandsoverdracht mogelijk
SMTP (Simple Mail Transfer
protocol)
maakt het uitwisselen van berichten tussen netwerkgebruikers mogelijk
DNS (Domain Name System)
zet een computernaam om in het internetadres van de computer
XDR (eXternal Data 
Representation)
gemaakt door sun MicroSystems, specificeert een standaard, machine-onafhankelijke weergave van de gegevens
RPC(Remote Procedures Call)
ook gedefinieerd door Sun; het is een communicatieprotocol tussen externe applicaties, onafhankelijk van de transportlaag. Dit protocol is belangrijk: het ontlast de programmeur van de kennis van de details van de transportlaag en maakt applicaties draagbaar. Dit protocol is gebaseerd op het protocol XDR
NFS (Network File System)
, eveneens gedefinieerd door Sun. Dit protocol stelt een machine in staat om het bestandssysteem van een andere machine te „zien”. Het is gebaseerd op het voorgaande protocol RPC

9.1.4. Werking van internetprotocollen

Toepassingen die zijn ontwikkeld in de TCP/IP-omgeving maken doorgaans gebruik van meerdere protocollen uit deze omgeving. Een applicatieprogramma communiceert met de hoogste laag van de protocollen. Deze laag geeft de informatie door aan de laag eronder, en zo verder, totdat deze de fysieke drager bereikt. Daar wordt de informatie fysiek doorgestuurd naar de ontvangende machine, waar ze dezelfde lagen weer doorloopt, ditmaal in omgekeerde volgorde, totdat ze de ontvangende applicatie van de verzonden informatie bereikt. Het volgende schema toont het traject van de informatie:

Laten we een voorbeeld nemen: de applicatie FTP, gedefinieerd op het niveau van de laag Application, die bestandsoverdrachten tussen computers mogelijk maakt.

  • De applicatie levert een reeks bytes af die moet worden doorgegeven aan de laag transport.
  • De laag transport splitst deze reeks bytes op in segments en TCP, en voegt aan het begin van elk segment het nummer van dat segment toe. De segmenten worden doorgegeven aan de netwerklaag, die wordt bestuurd door het protocol IP.
  • De IP-laag maakt een pakket aan waarin het ontvangen segment TCP wordt ingekapseld. Aan het begin van dit pakket plaatst deze laag de internetadressen van de bron- en bestemmingscomputer. Ook bepaalt deze laag het fysieke adres van de bestemmingscomputer. Het geheel wordt doorgegeven aan de laag voor gegevensverbinding en fysieke verbinding, dat wil zeggen aan de netwerkkaart die de computer met het fysieke netwerk verbindt.
  • Daar wordt het pakket IP op zijn beurt ingekapseld in een fysiek frame en via de kabel naar de ontvanger verzonden.
  • Op de ontvangende machine doet de laag ‘Datalink & Fysieke verbinding’ het omgekeerde: deze ontkapselt het pakket IP uit het fysieke frame en geeft het door aan de laag IP.
  • De laag IP controleert of het pakket correct is: ze berekent een checksum op basis van de ontvangen bits (checksum), een checksum die ze terug moet vinden in de header van het pakket. Als dat niet het geval is, wordt het pakket afgewezen.
  • Als het pakket als correct wordt aangemerkt, ontkapselt de laag IP het segment TCP dat zich daarin bevindt en geeft het door aan de bovenliggende laag transport.
  • De laag transport – in ons voorbeeld de laag TCP – controleert het segmentnummer om de juiste volgorde van de segmenten te herstellen.
  • Ze berekent ook een controlesom voor het segment TCP. Als deze correct blijkt te zijn, stuurt de laag TCP een ontvangstbevestiging naar de bronmachine; anders wordt het segment TCP geweigerd.
  • De laag TCP hoeft nu alleen nog maar het gegevensgedeelte van het segment door te sturen naar de toepassing die deze gegevens ontvangt in de bovenliggende laag.

9.1.5. Adressering op het internet

Een noeud in een netwerk kan een computer zijn, een slimme printer, een bestandsserver, eigenlijk alles wat kan communiceren met behulp van de protocollen TCP/IP. Elk knooppunt heeft een fysiek adres waarvan het formaat afhankelijk is van het type netwerk. Op een Ethernet-netwerk wordt het fysieke adres gecodeerd in 6 bytes. Een adres in een X25-netwerk is een getal van 14 cijfers.

Het internetadres van een knooppunt is een logisch adres: het is onafhankelijk van de gebruikte hardware en het netwerk. Het is een adres van 4 bytes dat zowel een lokaal netwerk als een knooppunt van dat netwerk identificeert. Het internetadres wordt gewoonlijk weergegeven in de vorm van 4 getallen, de waarden van de 4 bytes, gescheiden door een punt. Zo wordt het adres van de computer Lagaffe van de faculteit Wetenschappen in Angers weergegeven als 193.49.144.1 en dat van de computer Liny als 193.49.144.9. Hieruit volgt dat het internetadres van het lokale netwerk 193.49.144.0 is. Er kunnen maximaal 254 knooppunten op dit netwerk aanwezig zijn.

Omdat internetadressen of IP-adressen onafhankelijk zijn van het netwerk, kan een computer in netwerk A communiceren met een computer in netwerk B zonder zich zorgen te hoeven maken over het type netwerk waarop deze zich bevindt: het volstaat dat hij zijn IP-adres kent. Het IP-protocol van elk netwerk zorgt voor de conversie van het IP-adres naar het fysieke adres en vice versa.

De IP-adressen moeten allemaal verschillend zijn. In Frankrijk is het de INRIA die de IP-adressen toewijst. In feite kent deze instantie een adres toe aan uw lokale netwerk, bijvoorbeeld 193.49.144.0 voor het netwerk van de faculteit der wetenschappen van Angers. De beheerder van dit netwerk kan vervolgens de IP-adressen 193.49.144.1 tot en met 193.49.144.254 naar eigen inzicht toewijzen. Dit adres wordt doorgaans vastgelegd in een speciaal bestand op elke computer die op het netwerk is aangesloten.

9.1.5.1. De adresklassen IP

Een adres IP is een reeks van 4 bytes die vaak wordt weergegeven als I1.I2.I3.I4, en die in feite twee adressen bevat:

  • het netwerkadres
  • het adres van een knooppunt in dat netwerk

Afhankelijk van de grootte van deze twee velden worden IP-adressen onderverdeeld in drie klassen: klasse A, B en C.

Klasse A

Het adres IP: I1.I2.I3.I4 heeft de vorm R1.N1.N2.N3, waarbij

R1
het netwerkadres is
N1.N2.N3
het adres is van een computer in dat netwerk

Om precies te zijn, de vorm van een adres van klasse A, IP, is als volgt:

Het netwerkadres bestaat uit 7 bits en het knooppuntadres uit 24 bits. Er kunnen dus 127 klasse A-netwerken zijn, die elk maximaal 224 knooppunten bevatten.

Klasse B

Hier heeft het adres IP: I1.I2.I3.I4 de vorm R1.R2.N1.N2, waarbij

R1.R2
het adres van het netwerk is
N1.N2
het adres is van een computer in dit netwerk

Om precies te zijn, de vorm van een klasse B-adres IP is als volgt:

Zowel het netwerkadres als het knooppuntadres beslaat 2 bytes (precies 14 bits). Er kunnen dus 2¹⁴ klasse B-netwerken zijn, die elk maximaal 2¹⁶ knooppunten bevatten.

Klasse C

In deze klasse heeft het adres IP: I1.I2.I3.I4 de vorm R1.R2.R3.N1, waarbij

R1.R2.R3
het adres van het netwerk is
N1
het adres is van een computer in dit netwerk

Om precies te zijn, de vorm van een klasse C-adres IP is als volgt:

Het netwerkadres beslaat 3 bytes (minus 3 bits) en het knooppuntadres 1 byte. Er kunnen dus 221 klasse C-netwerken zijn met elk maximaal 256 knooppunten.

Aangezien het adres van de computer Lagaffe van de faculteit der wetenschappen van Angers 193.49.144.1 is, zien we dat de hoogste byte 193 is, dat wil zeggen in binair 11000001. Hieruit kunnen we afleiden dat het netwerk van klasse C is.

Gereserveerde adressen

  • Sommige adressen, zoals IP, zijn netwerkadressen in plaats van knooppuntadressen in het netwerk. Dit zijn de adressen waarbij het knooppuntadres op 0 is gezet. Zo is het adres 193.49.144.0 het adres IP van het netwerk van de Faculteit der Wetenschappen van Angers. Bijgevolg kan geen enkel knooppunt in een netwerk het adres nul hebben.
  • Wanneer in een adres IP het knooppuntadres uitsluitend uit enen bestaat, is er sprake van een broadcastadres: dit adres verwijst naar alle knooppunten in het netwerk.
  • In een klasse C-netwerk, dat theoretisch 2⁸ = 256 knooppunten toestaat, blijven er, als we de twee verboden adressen verwijderen, slechts 254 toegestane adressen over.

9.1.5.2. Conversieprotocollen: internetadres <--> fysiek adres

We hebben gezien dat bij het verzenden van gegevens van de ene machine naar de andere, deze bij het passeren van de IP-laag in pakketten werden ingekapseld. Deze hebben de volgende vorm:

Het pakket IP bevat dus de internetadressen van de bron- en bestemmingsmachines. Wanneer dit pakket wordt doorgegeven aan de laag die verantwoordelijk is voor het verzenden ervan over het fysieke netwerk, wordt er andere informatie aan toegevoegd om het fysieke frame te vormen dat uiteindelijk over het netwerk wordt verzonden. Het formaat van een frame op een Ethernet-netwerk is bijvoorbeeld als volgt:

In het uiteindelijke frame staan de fysieke adressen van de bron- en bestemmingscomputers. Hoe worden deze verkregen?

De verzendende machine, die het adres IP kent van de machine waarmee zij wil communiceren, verkrijgt het fysieke adres daarvan door gebruik te maken van een speciaal protocol genaamd ARP (Address Resolution Protocol).

  • Hij verstuurt een pakket van een speciaal type, een zogenaamd ARP-pakket, dat het adres IP bevat van de machine waarvan het fysieke adres wordt gezocht. Hij heeft er ook voor gezorgd dat zijn eigen adres IP en zijn fysieke adres in dit pakket zijn opgenomen.
  • Dit pakket wordt naar alle knooppunten in het netwerk verzonden.
  • Deze herkennen het speciale karakter van het pakket. Het knooppunt dat zijn adres IP in het pakket herkent, reageert door zijn fysieke adres naar de afzender van het pakket te sturen. Hoe kan het dat? Het heeft in het pakket de adressen IP en het fysieke adres van de afzender gevonden.
  • De afzender ontvangt dus het fysieke adres dat hij zocht. Hij slaat dit op in het geheugen om het later te kunnen gebruiken als er nog meer pakketten naar dezelfde ontvanger moeten worden verzonden.

Het IP-adres van een computer staat normaal gesproken in een van zijn bestanden, dat hij dus kan raadplegen om het te achterhalen. Dit adres kan worden gewijzigd: het volstaat om het bestand te bewerken. Het fysieke adres staat daarentegen in een geheugen van de netwerkkaart en kan niet worden gewijzigd.

Wanneer een beheerder zijn netwerk anders wil inrichten, kan het nodig zijn om de IP-adressen van alle knooppunten te wijzigen en dus de verschillende configuratiebestanden van de afzonderlijke knooppunten te bewerken. Dit kan omslachtig zijn en tot fouten leiden als er veel machines zijn. Een methode bestaat erin geen IP-adres aan de machines toe te wijzen: men voert dan een speciale code in het bestand in waarin de machine zijn IP-adres zou moeten vinden. Wanneer de machine ontdekt dat hij geen adres IP heeft, vraagt hij dit op via een protocol dat RARP (Reverse Address Resolution Protocol) wordt genoemd. Vervolgens verstuurt de machine via het netwerk een speciaal pakket, het zogenaamde RARP-pakket, vergelijkbaar met het voorgaande ARP-pakket, waarin het zijn fysieke adres vermeldt. Dit pakket wordt naar alle knooppunten gestuurd, die het vervolgens herkennen als een RARP-pakket. Een van deze knooppunten, de zogenaamde server RARP, beschikt over een bestand met de koppeling tussen het fysieke adres en het IP-adres van alle knooppunten. Hij antwoordt vervolgens aan de afzender van het pakket RARP door zijn IP-adres terug te sturen. Een beheerder die zijn netwerk opnieuw wil configureren, hoeft dus alleen maar het toewijzingsbestand van de server RARP te bewerken. Deze moet normaal gesproken een vast adres IP hebben, dat hij moet kunnen kennen zonder zelf het protocol RARP te hoeven gebruiken.

9.1.6. De zogenaamde IP-netwerklaag van het internet

Het protocol IP (Internet Protocol) definieert de vorm die de pakketten moeten aannemen en de manier waarop ze moeten worden verwerkt bij het verzenden of ontvangen ervan. Dit specifieke type pakket wordt een IP-datagram genoemd. We hebben dit al eerder besproken:

Belangrijk is dat het IP-datagram, naast de te verzenden gegevens, ook de internetadressen van de bron- en bestemmingscomputer bevat. Zo weet de bestemmingscomputer wie hem een bericht stuurt.

In tegenstelling tot een netwerkframe, waarvan de lengte wordt bepaald door de fysieke kenmerken van het netwerk waarover het wordt verzonden, wordt de lengte van het datagram IP door de software vastgesteld en zal deze dus op verschillende fysieke netwerken hetzelfde zijn. We hebben gezien dat het datagram IP, naarmate we van de netwerklaag naar de fysieke laag afdalen, in een fysiek frame wordt ingekapseld. We hebben het voorbeeld gegeven van het fysieke frame van een Ethernet-netwerk:

De fysieke frames worden van knooppunt naar knooppunt doorgestuurd naar hun bestemming, die zich mogelijk niet op hetzelfde fysieke netwerk bevindt als de verzendende machine. Het pakket IP kan dus achtereenvolgens in verschillende fysieke frames worden ingekapseld op de knooppunten die de verbinding vormen tussen twee netwerken van verschillende typen. Het is ook mogelijk dat het pakket IP te groot is om in een fysiek frame te worden ingekapseld. De software IP van het knooppunt waar dit probleem zich voordoet, splitst het pakket IP dan volgens precieze regels op in fragments, waarna elk deel afzonderlijk over het fysieke netwerk wordt verzonden. Ze worden pas op hun eindbestemming weer samengevoegd.

9.1.6.1. Routering

Routering is de methode waarmee de pakketten IP naar hun bestemming worden geleid. Er zijn twee methoden: directe routering en indirecte routering.

Directe routering

Directe routering betekent dat een pakket IP rechtstreeks van de afzender naar de ontvanger binnen hetzelfde netwerk wordt geleid:

  • De machine die een datagram IP verstuurt, beschikt over het adres IP van de ontvanger.
  • De machine verkrijgt het fysieke adres van de ontvanger via het protocol ARP of uit haar tabellen, indien dit adres al bekend is.
  • Hij verstuurt het pakket via het netwerk naar dit fysieke adres.

Indirecte routering

Indirecte routering verwijst naar het doorsturen van een pakket IP naar een bestemming die zich op een ander netwerk bevindt dan dat waartoe de afzender behoort. In dit geval verschillen de netwerkadresdelen van de adressen IP van de bron- en bestemmingsmachine. De bronmachine herkent dit. Vervolgens stuurt hij het pakket naar een speciaal knooppunt, een zogenaamde router (router). Dit knooppunt verbindt een lokaal netwerk met andere netwerken en het adres IP ervan is in zijn tabellen opgeslagen; dit adres is aanvankelijk verkregen uit een bestand, uit een permanent geheugen of via informatie die over het netwerk circuleert.

Een router is gekoppeld aan twee netwerken en heeft binnen deze twee netwerken een adres IP.

In ons bovenstaande voorbeeld:

  • Heeft netwerk nr. 1 het internetadres 193.49.144.0 en netwerk nr. 2 het adres 193.49.145.0.
  • Binnen netwerk nr. 1 heeft de router het adres 193.49.144.6 en binnen netwerk nr. 2 het adres 193.49.145.3.

De router heeft als taak het pakket IP dat hij ontvangt – en dat vervat zit in een fysiek frame dat typisch is voor netwerk nr. 1 – om te zetten in een fysiek frame dat over netwerk nr. 2 kan worden verzonden. Als het adres IP van de ontvanger van het pakket zich in netwerk nr. 2 bevindt, stuurt de router het pakket rechtstreeks naar hem; anders stuurt hij het naar een andere router, die netwerk nr. 2 met netwerk nr. 3 verbindt, enzovoort.

9.1.6.2. Fout- en controlemeldingen

Nog steeds in de netwerklaag, dus op hetzelfde niveau als het protocol IP, bestaat het protocol ICMP (Internet Control Message Protocol). Dit protocol dient om berichten te verzenden over de interne werking van het netwerk: defecte knooppunten, opstoppingen bij een router, enzovoort ... De ICMP-berichten worden ingekapseld in IP-pakketten en over het netwerk verzonden. De IP-lagen van de verschillende knooppunten ondernemen de juiste acties op basis van de ICMP-berichten die ze ontvangen. Zo krijgt een applicatie zelf deze netwerkgerelateerde problemen nooit te zien. Een knooppunt gebruikt de ICMP-informatie om zijn routeringstabellen bij te werken.

9.1.7. De transportlaag: de protocollen UDP en TCP

9.1.7.1. Het protocol UDP: User Datagram Protocol

Het protocol UDP maakt een onbetrouwbare gegevensuitwisseling tussen twee punten mogelijk, wat betekent dat de correcte bezorging van een pakket op de bestemming niet gegarandeerd is. De toepassing kan dit desgewenst zelf beheren, bijvoorbeeld door na het verzenden van een bericht te wachten op een ontvangstbevestiging alvorens het volgende bericht te verzenden.

Tot nu toe hebben we op netwerkniveau gesproken over IP-adressen van machines. Op één machine kunnen echter tegelijkertijd verschillende processen bestaan die allemaal met elkaar kunnen communiceren. Bij het verzenden van een bericht moet daarom niet alleen het adres IP van de ontvangende machine worden opgegeven, maar ook de „naam” van het ontvangende proces. Deze naam is in feite een nummer, het zogenaamde poortnummer. Sommige nummers zijn gereserveerd voor standaardtoepassingen: poort 69 voor de tftp-toepassing (trivial file transfer protocol) bijvoorbeeld. De pakketten die door het UDP-protocol worden verwerkt, worden ook wel datagrammen genoemd. Ze hebben de volgende vorm:

Deze datagrammen worden ingekapseld in IP-pakketten en vervolgens in fysieke frames.

9.1.7.2. Het protocol TCP: Transfer Control Protocol

Voor veilige communicatie volstaat het protocol UDP niet: de applicatieontwikkelaar moet zelf een protocol ontwikkelen waarmee hij kan controleren of de pakketten correct worden doorgestuurd.

Het protocol TCP (Transfer Control Protocol) voorkomt deze problemen. De kenmerken ervan zijn als volgt:

  • Het proces dat gegevens wil verzenden, brengt eerst een verbinding tot stand met het proces dat de informatie zal ontvangen. Deze verbinding wordt tot stand gebracht tussen een poort van de verzendende machine en een poort van de ontvangende machine. Tussen beide poorten ontstaat zo een virtueel pad dat uitsluitend is gereserveerd voor de twee processen die de verbinding tot stand hebben gebracht.
  • Alle pakketten die door het bronproces worden verzonden, volgen dit virtuele pad en komen aan in de volgorde waarin ze zijn verzonden, wat niet gegarandeerd was in het protocol UDP, aangezien de pakketten verschillende paden konden volgen.
  • De verzonden informatie heeft een continu karakter. Het verzendende proces verstuurt informatie in zijn eigen tempo. Deze informatie wordt niet noodzakelijkerwijs onmiddellijk verzonden: het protocol TCP wacht tot er voldoende informatie is verzameld om deze te verzenden. De informatie wordt opgeslagen in een structuur die het TCP-segment wordt genoemd. Zodra dit segment gevuld is, wordt het doorgestuurd naar de IP-laag, waar het wordt ingekapseld in een IP-pakket.
  • Elk segment dat via het protocol TCP wordt verzonden, krijgt een nummer. Het ontvangende protocol TCP controleert of het de segmenten in de juiste volgorde ontvangt. Voor elk correct ontvangen segment stuurt het een ontvangstbevestiging naar de afzender.
  • Zodra de afzender deze ontvangstbevestiging ontvangt, geeft hij dit door aan het verzendende proces. Dit proces kan dus vaststellen dat een segment goed is aangekomen, wat met het protocol UDP niet mogelijk was.
  • Als het protocol TCP, dat een segment heeft verzonden, na verloop van tijd geen ontvangstbevestiging ontvangt, verzendt het het betreffende segment opnieuw, waardoor de kwaliteit van de dienstverlening bij de doorgifte van de informatie wordt gewaarborgd.
  • De virtuele verbinding die tussen de twee communicerende processen tot stand wordt gebracht, is full-duplex: dit betekent dat de informatie in beide richtingen kan worden verzonden. Zo kan het bestemmingsproces ontvangstbevestigingen versturen terwijl het bronproces doorgaat met het verzenden van informatie. Hierdoor kan bijvoorbeeld het bronprotocol TCP meerdere segmenten verzenden zonder op een ontvangstbevestiging te wachten. Als het na verloop van tijd vaststelt dat het geen ontvangstbevestiging heeft ontvangen voor een bepaald segment met nummer n, zal het de verzending van de segmenten vanaf dat punt hervatten.

9.1.8. De applicatielaag

Bovenop de protocollen UDP en TCP bestaan diverse standaardprotocollen:

TELNET

Dit protocol stelt een gebruiker van machine A in het netwerk in staat verbinding te maken met machine B (vaak de hostmachine genoemd). TELNET emuleert op machine A een zogenaamde universele terminal. De gebruiker gedraagt zich dus alsof hij beschikt over een terminal die is aangesloten op machine B. Telnet maakt gebruik van het protocol TCP.

FTP: (File Transfer Protocol)

Dit protocol maakt de uitwisseling van bestanden tussen twee machines op afstand mogelijk, evenals bestandsbewerkingen zoals het aanmaken van mappen. Het is gebaseerd op het protocol TCP.

TFTP: (Trivial File Transfer Control)

Dit protocol is een variant van FTP. Het is gebaseerd op het protocol UDP en is minder geavanceerd dan FTP.

DNS: (Domain Name System)

Wanneer een gebruiker bestanden wil uitwisselen met een computer op afstand, bijvoorbeeld via FTP, moet hij het internetadres van die computer kennen. Om bijvoorbeeld FTP uit te voeren op de computer Lagaffe van de universiteit van Angers, zou men FTP als volgt moeten starten: FTP 193.49.144.1

Hiervoor is een lijst nodig waarin de machines worden gekoppeld aan de adressen <--> IP. Waarschijnlijk zouden de machines in deze lijst worden aangeduid met symbolische namen zoals:

machine DPX2/320 van de universiteit van Angers

Sun-computer van de ISERPA in Angers

Het is duidelijk dat het prettiger zou zijn om een machine aan te duiden met een naam in plaats van met zijn adres IP. Dit roept echter het probleem op van de uniekheid van de naam: er zijn miljoenen onderling verbonden machines. Men zou zich kunnen voorstellen dat een centrale instantie de namen toekent. Dat zou ongetwijfeld nogal omslachtig zijn. Het beheer van de namen is in feite verdeeld over domeinen. Elk domein wordt beheerd door een doorgaans zeer lichte organisatie die volledige vrijheid heeft bij de keuze van de namen van computers. Zo behoren de computers in Frankrijk tot het domein fr, dat wordt beheerd door het Inria in Parijs. Om de zaken nog verder te vereenvoudigen, wordt het beheer nog verder gedecentraliseerd: er worden domeinen gecreëerd binnen het domein fr. Zo behoort de universiteit van Angers tot het domein univ-Angers. De dienst die dit domein beheert, heeft alle vrijheid om de computers in het netwerk van de Universiteit van Angers een naam te geven. Voorlopig is dit domein nog niet onderverdeeld. Maar bij een grote universiteit met veel computers in het netwerk zou dat wel kunnen gebeuren.

De computer DPX2/320 van de Universiteit van Angers is Lagaffe genoemd, terwijl een PC en een 486DX50 de naam liny hebben gekregen. Hoe kunnen deze machines van buitenaf worden aangeduid? Door de hiërarchie van de domeinen waartoe ze behoren te vermelden. De volledige naam van de machine Lagaffe is dus:

Lagaffe.univ-Angers.fr

Binnen de domeinen kunnen relatieve namen worden gebruikt. Binnen het domein fr en buiten het domein univ-Angers kan de machine Lagaffe dus worden aangeduid met

Lagaffe.univ-Angers

Ten slotte kan de machine binnen het domein univ-Angers eenvoudig worden aangeduid met

Lagaffe

Een toepassing kan dus naar een machine verwijzen via haar naam. Uiteindelijk moet het internetadres van deze machine echter toch worden verkregen. Hoe gebeurt dit? Stel dat we vanaf machine A willen communiceren met machine B.

  • Als machine B tot hetzelfde domein behoort als machine A, zal het adres IP waarschijnlijk in een bestand op machine A te vinden zijn.
  • Zo niet, dan vindt computer A in een ander bestand – of in hetzelfde bestand als hiervoor – een lijst met enkele naamservers en hun adressen IP. Een naamserver heeft als taak de koppeling te leggen tussen een computernaam en het bijbehorende adres IP. Computer A stuurt een speciaal verzoek naar de eerste naamserver in zijn lijst, het zogenaamde DNS-verzoek, waarin dus de naam van de gezochte computer is opgenomen. Als de geraadpleegde server deze naam in zijn tabellen heeft staan, stuurt hij het bijbehorende adres IP naar computer A. Zo niet, dan zal de server zelf in zijn bestanden een lijst vinden met naamservers die hij kan raadplegen. Dat zal hij dan doen. Zo wordt een aantal naamservers geraadpleegd, niet op willekeurige wijze, maar op een manier die het aantal verzoeken tot een minimum beperkt. Als de machine uiteindelijk wordt gevonden, komt het antwoord terug naar machine A.

XDR: (eXternal gegevensweergave)

Dit protocol, ontwikkeld door Sun MicroSystems, specificeert een standaard, machine-onafhankelijke weergave van gegevens.

RPC: (Remote Procedure Call)

Dit protocol, eveneens gedefinieerd door Sun, is een communicatieprotocol tussen externe applicaties, onafhankelijk van de transportlaag. Dit protocol is belangrijk: het ontlast de programmeur van de noodzaak om de details van de transportlaag te kennen en maakt applicaties draagbaar. Dit protocol is gebaseerd op het protocol XDR

NFS: Network File System

Dit protocol, eveneens gedefinieerd door Sun, stelt een computer in staat om het bestandssysteem van een andere computer te „zien”. Het is gebaseerd op het voorgaande protocol RPC.

9.1.9. Conclusie

In deze inleiding hebben we enkele hoofdlijnen van de internetprotocollen uiteengezet. Om je verder in dit onderwerp te verdiepen, kun je het uitstekende boek van Douglas Comer lezen:

Titel
TCP/IP: Architectuur, protocollen, toepassingen.
Auteur
Douglas COMER
Uitgever
InterEditions

9.2. Beheer van netwerkadressen

Een computer op het internet wordt op unieke wijze gedefinieerd door een IP-adres (Internet Protocol) in de vorm I1.I2.I3.I4, waarbij In een getal tussen 1 en 254 is. De computer kan ook worden gedefinieerd door een eveneens unieke naam. Deze naam is niet verplicht, aangezien applicaties uiteindelijk altijd de IP-adressen van de apparaten gebruiken. Ze zijn bedoeld om het leven van de gebruikers te vergemakkelijken. Zo is het met een browser gemakkelijker om het adres URL http://www.ibm.com op te vragen dan het adres URL http://129.42.17.99, hoewel beide methoden mogelijk zijn. De koppeling tussen het adres IP en nomMachine wordt verzorgd door een gedistribueerde internetdienst die DNS (Domain Name System) wordt genoemd. Het .NET-platform biedt de klasse Dns om internetadressen te beheren:

Image

De meeste methoden van de klasse zijn statisch. Laten we eens kijken naar de methoden die voor ons van belang zijn:

Overloads Public Shared Function
 GetHostByAddress(ByVal address As
 String) As IPHostEntry
Geeft een adres IPHostEntry terug op basis van een adres IP in de vorm "I1.I2.I3.I4". Genereert een uitzondering als de machine address niet kan worden gevonden.
Public Shared Function
 GetHostByName(ByVal hostName As
 String) As IPHostEntry
geeft een adres IPHostEntry terug op basis van een machinenaam. Er wordt een uitzondering gegenereerd als de machine name niet kan worden gevonden.
Public Shared Function 
GetHostName() As String
geeft de naam van de machine waarop het programma dat deze instructie uitvoert, draait

Netwerkadressen van het type IPHostEntry hebben de volgende vorm:

De eigenschappen die voor ons van belang zijn:

Public Property AddressList 
As IPAddress ()
Lijst met IP-adressen van een machine. Als een IP-adres verwijst naar één en slechts één fysieke machine, kan een fysieke machine dan meerdere IP-adressen hebben? Dit is het geval als de machine meerdere netwerkkaarten heeft die verbinding maken met verschillende netwerken.
Public Property Aliases 
As String ()
lijst met aliassen van een machine, die kan worden aangeduid met een hoofdnaam en aliassen
Public Property HostName 
As String
de naam van de machine, indien deze er een heeft

Uit de klasse IPAddress onthouden we de volgende constructor, eigenschappen en methoden:

Image

Een object [IPAddress] kan met de methode ToString() worden omgezet in de tekenreeks I1.I2.I3.I4. Omgekeerd kan men een object IPAddress verkrijgen uit een tekenreeks I1.I2.I3.I4 met de statische methode IPAddress.Parse("I1.I2.I3.I4"). Laten we eens kijken naar het volgende programma dat de naam van de machine weergeeft waarop het wordt uitgevoerd en vervolgens interactief de overeenkomsten tussen het adres IP en de machinenaam weergeeft:

dos>address1
Machine Locale=tahe

Machine recherchée (fin pour arrêter) : istia.univ-angers.fr
Machine : istia.univ-angers.fr
Adresses IP : 193.49.146.171

Machine recherchée (fin pour arrêter) : 193.49.146.171
Machine : istia.istia.univ-angers.fr
Adresses IP : 193.49.146.171
Alias : 171.146.49.193.in-addr.arpa

Machine recherchée (fin pour arrêter) : www.ibm.com
Machine : www.ibm.com
Adresses IP : 129.42.17.99,129.42.18.99,129.42.19.99,129.42.16.99

Machine recherchée (fin pour arrêter) : 129.42.17.99
Machine : www.ibm.com
Adresses IP : 129.42.17.99

Machine recherchée (fin pour arrêter) : x.y.z
Impossible de trouver la machine [x.y.z]

Machine recherchée (fin pour arrêter) : localhost
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : 127.0.0.1
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : tahe
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : fin

Het programma is als volgt:


' opties
Option Explicit On 
Option Strict On

' naamruimten
Imports System
Imports System.Net
Imports System.Text.RegularExpressions

' testmodule
Public Module adresses

    Sub Main()
        ' geeft de naam van de lokale machine weer
        ' en geeft vervolgens interactief informatie over de netwerkcomputers
        ' geïdentificeerd door een naam of een adres IP
        ' lokale machine
        Dim localHost As String = Dns.GetHostName()
        Console.Out.WriteLine(("Machine Locale=" + localHost))

        ' interactieve vraag-en-antwoord
        Dim machine As String
        Dim adresseMachine As IPHostEntry
        While True
            ' de naam van de gezochte machine invoeren
            Console.Out.Write("Machine recherchée (fin pour arrêter) : ")
            machine = Console.In.ReadLine().Trim().ToLower()
            ' klaar?
            If machine = "fin" Then
                Exit While
            End If

            ' adres I1.I2.I3.I4 of computernaam?
            Dim isIPV4 As Boolean = Regex.IsMatch(machine, "^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*$")
            ' uitzonderingsafhandeling
            Try
                If isIPV4 Then
                    adresseMachine = Dns.GetHostByAddress(machine)
                Else
                    adresseMachine = Dns.GetHostByName(machine)
                End If
                ' de naam
                Console.Out.WriteLine(("Machine : " + adresseMachine.HostName))
                ' de adressen IP
                Console.Out.Write(("Adresses IP : " + adresseMachine.AddressList(0).ToString))
                Dim i As Integer
                For i = 1 To adresseMachine.AddressList.Length - 1
                    Console.Out.Write(("," + adresseMachine.AddressList(i).ToString))
                Next i
                Console.Out.WriteLine()
                ' de aliassen
                If adresseMachine.Aliases.Length <> 0 Then
                    Console.Out.Write(("Alias : " + adresseMachine.Aliases(0)))
                    For i = 1 To adresseMachine.Aliases.Length - 1
                        Console.Out.Write(("," + adresseMachine.Aliases(i)))
                    Next i
                    Console.Out.WriteLine()
                End If
            Catch
                ' de machine bestaat niet
                Console.Out.WriteLine("Impossible de trouver la machine [" + machine + "]")
            End Try
        End While
    End Sub
End Module

9.3. Programmering TCP-IP

9.3.1. Algemeen

Laten we eens kijken naar de communicatie tussen twee op afstand gelegen machines A en B:

Wanneer een toepassing AppA op machine A wil communiceren met een toepassing AppB op machine B via het internet, moet deze verschillende gegevens kennen:

  • het adres IP of de naam van machine B
  • het poortnummer waarmee de applicatie AppB werkt. Computer B kan namelijk talrijke applicaties ondersteunen die via het internet werken. Wanneer deze computer informatie uit het netwerk ontvangt, moet hij weten voor welke applicatie deze informatie bestemd is. De applicaties op machine B hebben toegang tot het netwerk via poorten, ook wel communicatiepoorten genoemd. Deze informatie is opgenomen in het pakket dat door machine B wordt ontvangen, zodat het aan de juiste applicatie kan worden afgeleverd.
  • De communicatieprotocollen die door machine B worden begrepen. In onze studie zullen we uitsluitend de protocollen TCP-IP gebruiken.
  • het dialoogprotocol dat door de toepassing AppB wordt geaccepteerd. Machine A en machine B gaan namelijk met elkaar ‘praten’. Wat ze gaan zeggen, wordt ingekapseld in de protocollen TCP-IP. Wanneer echter aan het einde van de keten de applicatie AppB de door de applicatie AppA verzonden informatie ontvangt, moet zij deze kunnen interpreteren. Dit is vergelijkbaar met de situatie waarin twee personen, A en B, via de telefoon communiceren: hun dialoog wordt via de telefoon overgebracht. De spraak wordt door telefoon A gecodeerd in de vorm van signalen, via telefoonlijnen getransporteerd en komt aan bij telefoon B om daar te worden gedecodeerd. Persoon B hoort dan spraak. Hier komt het begrip dialoogprotocol om de hoek kijken: als A Frans spreekt en B deze taal niet begrijpt, kunnen A en B geen zinvol gesprek voeren.

Daarom moeten de twee communicerende applicaties het eens zijn over het type dialoog dat ze gaan hanteren. De dialoog met een dienst ftp is bijvoorbeeld niet dezelfde als die met een dienst pop: deze twee diensten accepteren niet dezelfde opdrachten. Ze hebben een verschillend dialoogprotocol.

9.3.2. De kenmerken van het protocol TCP

We zullen hier alleen netwerkcommunicatie behandelen die gebruikmaakt van het transportprotocol TCP. Laten we hier nog even de kenmerken ervan in herinnering brengen:

  • Het proces dat gegevens wil verzenden, brengt eerst een verbinding tot stand met het proces dat de informatie zal ontvangen. Deze verbinding wordt tot stand gebracht tussen een poort van de verzendende machine en een poort van de ontvangende machine. Tussen beide poorten ontstaat zo een virtueel pad dat uitsluitend is gereserveerd voor de twee processen die de verbinding tot stand hebben gebracht.
  • Alle pakketten die door het bronproces worden verzonden, volgen dit virtuele pad en komen aan in de volgorde waarin ze zijn verzonden
  • De verzonden informatie heeft een continu karakter. Het verzendende proces verstuurt informatie in zijn eigen tempo. Deze informatie wordt niet noodzakelijkerwijs onmiddellijk verzonden: het protocol TCP wacht tot er voldoende informatie is verzameld om deze te verzenden. De informatie wordt opgeslagen in een structuur die het segment TCP wordt genoemd. Zodra dit segment gevuld is, wordt het doorgestuurd naar de laag IP, waar het wordt ingekapseld in een pakket IP.
  • Elk segment dat via het protocol TCP wordt verzonden, krijgt een nummer. Het ontvangende protocol TCP controleert of het de segmenten in de juiste volgorde ontvangt. Voor elk correct ontvangen segment stuurt het een ontvangstbevestiging naar de afzender.
  • Zodra de afzender deze ontvangstbevestiging ontvangt, geeft hij dit door aan het verzendende proces. Dit proces weet dan dat een segment goed is aangekomen.
  • Als het protocol TCP, dat een segment heeft verzonden, na verloop van tijd geen ontvangstbevestiging ontvangt, verzendt het het betreffende segment opnieuw, waardoor de kwaliteit van de dienst voor het doorsturen van informatie wordt gewaarborgd.
  • De virtuele verbinding die tussen de twee communicerende processen tot stand is gebracht, is full-duplex: dit betekent dat de informatie in beide richtingen kan worden verzonden. Zo kan het bestemmingsproces ontvangstbevestigingen versturen terwijl het bronproces doorgaat met het verzenden van informatie. Hierdoor kan bijvoorbeeld het bronprotocol TCP meerdere segmenten verzenden zonder op een ontvangstbevestiging te wachten. Als het na verloop van tijd vaststelt dat het geen ontvangstbevestiging heeft ontvangen voor een bepaald segment met nummer n, zal het de verzending van de segmenten vanaf dat punt hervatten.

9.3.3. De client-serverrelatie

Vaak is de communicatie via internet asymmetrisch: machine A initieert een verbinding om een dienst aan te vragen bij machine B; hij geeft aan dat hij een verbinding wil openen met de dienst SB1 van machine B. Deze accepteert of weigert het verzoek. Als machine B de verbinding accepteert, kan machine A haar verzoeken naar de dienst SB1 sturen. Deze verzoeken moeten voldoen aan het communicatieprotocol dat door de dienst SB1 wordt ondersteund. Zo ontstaat een vraag-antwoorddialoog tussen machine A, die we de clientmachine noemen, en machine B, die we de servermachine noemen. Een van beide partners zal de verbinding verbreken.

9.3.4. Architectuur van een client

De architectuur van een netwerkprogramma dat gebruikmaakt van de diensten van een serverapplicatie ziet er als volgt uit:

ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
    tant que ce n'est pas fini
        préparer une demande
        l'émettre vers la machine B
        attendre et récupérer la réponse
        la traiter
    fin tant que
finsi

9.3.5. Architectuur van een server

De architectuur van een programma dat diensten aanbiedt, ziet er als volgt uit:

ouvrir le service sur la machine locale
tant que le service est ouvert
        se mettre à l'écoute des demandes de connexion sur un port dit port d'écoute
lorsqu'il y a une demande, la faire traiter par une autre tâche sur un autre port dit port de service
fin tant que

Het serverprogramma behandelt het eerste verbindingsverzoek van een klant anders dan zijn latere verzoeken om een dienst te verkrijgen. Het programma levert de dienst zelf niet. Als het dat wel zou doen, zou het gedurende de looptijd van de dienst niet meer luisteren naar verbindingsverzoeken en zouden klanten dan niet bediend worden. Het gaat daarom anders te werk: zodra een verbindingsverzoek wordt ontvangen op de luisterpoort en vervolgens wordt geaccepteerd, maakt de server een taak aan die verantwoordelijk is voor het leveren van de door de client gevraagde dienst. Deze dienst wordt geleverd op een andere poort van de server, de zogenaamde servicepoort. Zo kunnen meerdere clients tegelijkertijd worden bediend. Een servicetaken heeft de volgende structuur:

tant que le service n'a pas été rendu totalement
        attendre une demande sur le port de service
        lorsqu'il y en a une, élaborer la réponse
        transmettre la réponse via le port de service
fin tant que
     libérer le port de service

9.3.6. De klasse TcpClient

De klasse TcpClient is de juiste klasse om de klant van een dienst TCP weer te geven. Deze is als volgt gedefinieerd:

Image

De constructors, methoden en eigenschappen die voor ons van belang zijn, zijn de volgende:

Public Sub New(ByVal hostname
 As String,ByVal port As Integer)
maakt een TCP-verbinding met de server die draait op de opgegeven poort (port) van de opgegeven machine (hostname). Bijvoorbeeld: new TcpClient("istia.univ-angers.fr", 80) om verbinding te maken met poort 80 van de machine istia.univ-angers.fr
Public Sub Close()
verbreekt de verbinding met de TCP-server
Public Function GetStream()
 As NetworkStream
verkrijgt een NetworkStream-stream voor lezen en schrijven naar de server. Deze stream maakt de communicatie tussen client en server mogelijk.

9.3.7. De klasse NetworkStream

De klasse NetworkStream vertegenwoordigt de netwerkstroom tussen de client en de server. De klasse is als volgt gedefinieerd:

Image

De klasse NetworkStream is afgeleid van de klasse Stream. Veel client-servertoepassingen wisselen tekstregels uit die eindigen met de regeleinde-tekens "\r\n". Daarom is het interessant om de objecten StreamReader en StreamWriter te gebruiken om deze regels in de netwerkstroom te lezen en te schrijven. Wanneer twee machines met elkaar communiceren, bevindt zich aan elk uiteinde van de verbinding een object TcpClient. De methode GetStream van dit object biedt toegang tot de netwerkstroom (NetworkStream) die de twee machines met elkaar verbindt. Als een machine M1 dus een verbinding heeft tot stand gebracht met een machine M2 met behulp van een object TcpClient client1 waarmee ze tekstregels uitwisselen, dan kan deze machine zijn lees- en schrijfstromen als volgt aanmaken:

Dim in1 as StreamReader=new StreamReader(client1.GetStream())
Dim out1 as StreamWriter=new StreamWriter(client1.GetStream())
out1.AutoFlush=true

De instructie

out1.AutoFlush=true

betekent dat de schrijfstroom van client1 niet via een tussenbuffer verloopt, maar rechtstreeks naar het netwerk gaat. Dit is een belangrijk punt. Over het algemeen verwacht client1 een antwoord wanneer het een tekstregel naar zijn partner verstuurt. Dit antwoord zal nooit komen als de regel in werkelijkheid op de machine M1 in de buffer is opgeslagen en nooit is verzonden. Om een tekstregel naar de machine M2 te verzenden, schrijft men:

client1.WriteLine("un texte")

Om het antwoord van M2 te lezen, schrijf je:

Dim réponse as String=client1.ReadLine()

9.3.8. Basisarchitectuur van een internetclient

We beschikken nu over de elementen om de basisarchitectuur van een internetclient te beschrijven:


    Dim client As TcpClient = Nothing     ' le client
    Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
    Dim OUT As StreamWriter = Nothing     ' le flux d'écriture du client
    Dim demande As String = Nothing         ' demande du client
    Dim réponse As String = Nothing         ' réponse du serveur

    Try
      ' er wordt verbinding gemaakt met de dienst die op poort P van machine M draait
      client = New TcpClient(nomServeur, port)

      ' de in- en uitgangsstromen van de client worden aangemaakt TCP
      [IN] = New StreamReader(client.GetStream())
      OUT = New StreamWriter(client.GetStream())
      OUT.AutoFlush = True

      ' verzoek-antwoordlus
      While True
        ' het verzoek wordt voorbereid
        demande = ...
        ' de aanvraag wordt naar de server verzonden
        OUT.WriteLine(demande)
        ' het antwoord van de server wordt gelezen
        réponse = [IN].ReadLine()
        ' het antwoord wordt verwerkt
        ...
            End While
            ' klaar
            client.Close()
        Catch ex As Exception
      ' de uitzondering wordt afgehandeld
...
        End Try

9.3.9. De klasse TcpListener

De klasse TcpListener is de geschikte klasse om een service TCP weer te geven. Deze is als volgt gedefinieerd:

Image

De constructors, methoden en eigenschappen die voor ons van belang zijn, zijn de volgende:

Public Sub New(ByVal localaddr 
As IPAddress,ByVal port As Integer)
maakt een service aan, TCP, die wacht (listen) op verzoeken van clients op een als parameter doorgegeven poort (port), de zogenaamde luisterpoort van de lokale machine met adres IP localadr.
Public Function AcceptTcpClient() 
As TcpClient
accepteert het verzoek van een klant. Geeft als resultaat een object TcpClient dat is gekoppeld aan een andere poort, de zogenaamde servicepoort.
Public Sub Start()
begint te luisteren naar verzoeken van klanten
Public Sub Stop()
stopt met het luisteren naar verzoeken van klanten

9.3.10. Basisstructuur van een internetserver

Uit hetgeen hierboven is besproken, kunnen we de basisstructuur van een server afleiden:


    ' we starten de luisterservice
    Dim ecoute As TcpListener = Nothing
    Dim port As Integer = ...
    Try
      ' de dienst wordt aangemaakt
      ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
      ' de service wordt gestart
      ecoute.Start()
      ' servicelus
      Dim liaisonClient As TcpClient = Nothing
            While not fini
                ' wachten op een client
                liaisonClient = ecoute.AcceptTcpClient()
                ' de service wordt door een andere taak overgenomen
                Dim tache As Thread = New Thread(New ThreadStart(AddressOf [méthode]))
                tache.Start()
            End While
        Catch ex As Exception
            ' de fout wordt gemeld
....
        End Try
        ' einde van de dienst
        ecoute.Stop()

De klasse Service is een thread die er als volgt uit zou kunnen zien:


Public Class Service

    Private liaisonClient As TcpClient    ' liaison avec le client
    Private [IN] As StreamReader    ' flux d'entrée
    Private OUT As StreamWriter    ' flux de sortie

    ' constructor
    Public Sub New(ByVal liaisonClient As TcpClient, ...)
        Me.liaisonClient = liaisonClient
        ...
    End Sub

    ' run-methode
    Public Sub Run()
        ' geeft de service terug aan de client
        Try
            ' invoerstroom
            [IN] = New StreamReader(liaisonClient.GetStream())
            ' uitvoerstroom
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            ' lus voor het lezen van verzoeken en het schrijven van antwoorden
            Dim demande As String = Nothing
            Dim reponse As String = Nothing
            demande = [IN].ReadLine
            While Not (demande Is Nothing)
                ' het verzoek wordt verwerkt
                ...
                ' het antwoord wordt verzonden
                reponse = "[" + demande + "]"
                OUT.WriteLine(reponse)
                ' volgend verzoek
                demande = [IN].ReadLine
            End While
            ' verbinding beëindigd
            liaisonClient.Close()
        Catch e As Exception
            ...
        End Try
        ' einde van de dienst
    End Sub

9.4. Voorbeelden

9.4.1. Echo-server

We gaan een echo-server schrijven die vanuit een venster met de naam DOS wordt gestart met het commando:

serveurEcho port

De server draait op de poort die als parameter wordt doorgegeven. Hij stuurt de aanvraag die de client hem heeft gestuurd gewoon terug naar de client. Het programma ziet er als volgt uit:


' opties
Option Explicit On 
Option Strict On

' naamruimten
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

' oproep: serveurEcho poort
' echo-server
' stuurt de regel die de client heeft verzonden terug naar de client

Public Class serveurEcho
  Private Shared syntaxe As String = "Syntaxe : serveurEcho port"

  ' hoofdprogramma
  Public Shared Sub Main(ByVal args() As String)

    ' is er een argument
    If args.Length <> 1 Then
      erreur(syntaxe, 1)
    End If
    ' dit argument moet een geheel getal >0 zijn
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(0))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If
    ' de luisterservice wordt aangemaakt
    Dim ecoute As TcpListener = Nothing
    Dim nbClients As Integer = 0 ' nbre de clients traités
    Try
      ' de service wordt aangemaakt
      ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
      ' de service wordt gestart
      ecoute.Start()
      ' vervolg
      Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & port))
      Console.Out.WriteLine(ecoute.LocalEndpoint)

      ' servicelus
      Dim liaisonClient As TcpClient = Nothing
            While True
                ' oneindige lus – wordt gestopt met Ctrl-C
                ' wacht op een client
                liaisonClient = ecoute.AcceptTcpClient()

                ' de service wordt verzorgd door een andere taak
                nbClients += 1
                Dim tache As Thread = New Thread(New ThreadStart(AddressOf New traiteClientEcho(liaisonClient, nbClients).Run))
                tache.Start()
            End While
            ' we gaan weer luisteren naar verzoeken
        Catch ex As Exception
            ' de fout wordt gemeld
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
        ' einde van de dienst
        ecoute.Stop()
    End Sub

    ' fouten weergeven
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' fout weergeven
        System.Console.Error.WriteLine(msg)
        ' afsluiten met fout
        Environment.Exit(exitCode)
    End Sub
End Class

' -------------------------------------------------------
' verleent service aan een klant van de echo-server
Public Class traiteClientEcho

    Private liaisonClient As TcpClient    ' liaison avec le client
    Private numClient As Integer    ' n° de client
    Private [IN] As StreamReader    ' flux d'entrée
    Private OUT As StreamWriter    ' flux de sortie

    ' constructor
    Public Sub New(ByVal liaisonClient As TcpClient, ByVal numClient As Integer)
        Me.liaisonClient = liaisonClient
        Me.numClient = numClient
    End Sub

    ' run-methode
    Public Sub Run()
        ' verricht de dienst voor de client
        Console.Out.WriteLine(("Début de service au client " & numClient))
        Try
            ' invoerstroom
            [IN] = New StreamReader(liaisonClient.GetStream())
            ' uitvoerstroom
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            ' lus voor het lezen van verzoeken en het schrijven van antwoorden
            Dim demande As String = Nothing
            Dim reponse As String = Nothing
            demande = [IN].ReadLine
            While Not (demande Is Nothing)
                ' monitoring
                Console.Out.WriteLine(("Client " & numClient & " : " & demande))
                ' de dienst stopt wanneer de klant een einde-van-bestand-markering verstuurt
                reponse = "[" + demande + "]"
                OUT.WriteLine(reponse)
                ' de service stopt wanneer de client "einde" verstuurt
                If demande.Trim().ToLower() = "fin" Then
                    Exit While
                End If
                ' volgend verzoek
                demande = [IN].ReadLine
            End While
            ' verbinding beëindigd
            liaisonClient.Close()
        Catch e As Exception
            erreur("Erreur lors de la fermeture de la liaison client (" + e.ToString + ")", 2)
        End Try
        ' einde van de dienst
        Console.Out.WriteLine(("Fin de service au client " & numClient))
    End Sub

    ' foutmeldingen weergeven
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' fout weergeven
        System.Console.Error.WriteLine(msg)
        ' afsluiten met fout
        Environment.Exit(exitCode)
    End Sub
End Class

De structuur van de server komt overeen met de algemene architectuur van TCP-servers.

9.4.2. Een client voor de echo-server

We schrijven nu een client voor de vorige server. Deze wordt als volgt aangeroepen:

clientEcho nomServeur port

Hij maakt verbinding met de machine nomServeur op poort port en stuurt vervolgens tekstregels naar de server, die deze als echo terugstuurt.


' opties
Option Explicit On 
Option Strict On

' naamruimten
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

Public Class clientEcho

  ' maakt verbinding met een echo-server
  ' elke regel die via het toetsenbord wordt ingevoerd, wordt vervolgens als echo teruggegeven
  Public Shared Sub Main(ByVal args() As String)
    ' syntaxis
    Const syntaxe As String = "pg machine port"

    ' aantal argumenten
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' noteer de naam van de server
    Dim nomServeur As String = args(0)

    ' de poort moet een geheel getal >0 zijn
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(1))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If

        ' we kunnen aan de slag
    Dim client As TcpClient = Nothing ' le client
    Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
    Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
    Dim demande As String = Nothing ' demande du client
    Dim réponse As String = Nothing ' réponse du serveur
    Try
      ' we maken verbinding met de dienst die draait op poort P van machine M
      client = New TcpClient(nomServeur, port)

      ' de in- en uitgangsstromen van de client TCP worden aangemaakt
      [IN] = New StreamReader(client.GetStream())
      OUT = New StreamWriter(client.GetStream())
      OUT.AutoFlush = True

      ' verzoek-antwoordlus
      While True
        ' het verzoek komt van het toetsenbord
        Console.Out.Write("demande (fin pour arrêter) : ")
        demande = Console.In.ReadLine()
        ' deze wordt naar de server verzonden
        OUT.WriteLine(demande)
        ' het antwoord van de server wordt gelezen
        réponse = [IN].ReadLine()
        ' het antwoord wordt verwerkt
        Console.Out.WriteLine(("Réponse : " + réponse))
        ' klaar?
        If demande.Trim().ToLower() = "fin" Then
          Exit While
                End If
            End While
            ' het is klaar
            client.Close()
        Catch ex As Exception
      ' de uitzondering wordt afgehandeld
      erreur(ex.Message, 3)
        End Try
    End Sub

    ' fouten weergeven
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' fout weergeven
        System.Console.Error.WriteLine(msg)
        ' afgesloten met fout
        Environment.Exit(exitCode)
    End Sub
End Class

De structuur van deze client komt overeen met de algemene architectuur van de tcp.Voici-clients. De resultaten zijn verkregen in de volgende configuratie:

  • de server wordt gestart op poort 100 in een DOS-venster
  • op dezelfde machine worden twee clients gestart in twee andere DOS-vensters

In het venster van client 1 zien we de volgende resultaten:

dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne1
Réponse : [ligne1]
demande (fin pour arrêter) : ligne1B
Réponse : [ligne1B]
demande (fin pour arrêter) : ligne1C
Réponse : [ligne1C]
demande (fin pour arrêter) : fin
Réponse : [fin]

In die van klant 2:

dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne2A
Réponse : [ligne2A]
demande (fin pour arrêter) : ligne2B
Réponse : [ligne2B]
demande (fin pour arrêter) : fin
Réponse : [fin]

In die van de server:

dos>serveurEcho 100
Serveur d'écho lancé sur le port 100
0.0.0.0:100
Début de service au client 1
Client 1 : ligne1
Début de service au client 2
Client 2 : ligne2A
Client 2 : ligne2B
Client 1 : ligne1B
Client 1 : ligne1C
Client 2 : fin
Fin de service au client 2
Client 1 : fin
Fin de service au client 1
^C

Opvallend is dat de server inderdaad in staat was om twee clients tegelijkertijd te bedienen.

9.4.3. Een generieke client TCP

Veel diensten die in de beginjaren van het internet zijn ontstaan, werken volgens het eerder besproken echo-servermodel: de communicatie tussen client en server verloopt via de uitwisseling van tekstregels. We gaan een generieke TCP-client schrijven die als volgt wordt gestart: cltgen server poort

Deze client TCP maakt verbinding met poort port van de server serveur. Vervolgens maakt hij twee threads aan:

  1. een thread die de op het toetsenbord ingevoerde commando’s leest en deze naar de server verstuurt
  2. een thread die de antwoorden van de server leest en deze op het scherm weergeeft

Waarom twee threads, terwijl dat in de vorige toepassing niet nodig was? In die toepassing was het communicatieprotocol bekend: de client stuurde één regel en de server antwoordde met één regel. Elke dienst heeft zijn eigen specifieke protocol en er doen zich ook de volgende situaties voor:

  • de client moet meerdere regels tekst verzenden voordat hij een antwoord krijgt
  • het antwoord van een server kan uit meerdere regels tekst bestaan

Daarom is de lus waarbij één regel naar de server wordt verzonden en vervolgens één regel van de server wordt ontvangen, niet altijd geschikt. We gaan daarom twee afzonderlijke lussen maken:

  • een lus voor het lezen van de op het toetsenbord ingevoerde opdrachten die naar de server moeten worden verzonden. De gebruiker geeft het einde van de opdrachten aan met het trefwoord fin.
  • een lus voor het ontvangen en weergeven van de antwoorden van de server. Dit wordt een oneindige lus die alleen wordt onderbroken wanneer de netwerkverbinding door de server wordt verbroken of wanneer de gebruiker via het toetsenbord het commando fin invoert.

Om deze twee lussen van elkaar te scheiden, hebben we twee onafhankelijke threads nodig. Laten we een uitvoervoorbeeld bekijken waarin onze generieke TCP-client verbinding maakt met een dienst SMTP (SendMail Transfer Protocol). Deze dienst is verantwoordelijk voor het doorsturen van e-mail naar de ontvangers. Hij draait op poort 25 en maakt gebruik van een communicatieprotocol waarbij tekstregels worden uitgewisseld.

dos>cltgen istia.univ-angers.fr 25
Commandes :
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
help
<-- 502 5.3.0 Sendmail 8.11.6 -- HELP not implemented
mail from: machin@univ-angers.fr
<-- 250 2.1.0 machin@univ-angers.fr... Sender ok
rcpt to: serge.tahe@istia.univ-angers.fr
<-- 250 2.1.5 serge.tahe@istia.univ-angers.fr... Recipient ok
data
<-- 354 Enter mail, end with "." on a line by itself
Subject: test

ligne1
ligne2
ligne3
.
<-- 250 2.0.0 g4D6bks25951 Message accepted for delivery
quit
<-- 221 2.0.0 istia.univ-angers.fr closing connection
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

Laten we deze client-server-uitwisselingen eens bekijken:

  • de service SMTP verstuurt een welkomstbericht wanneer een client verbinding met hem maakt:
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
  • sommige services hebben een commando help dat informatie geeft over de commando's die met de service kunnen worden gebruikt. Hier is dat niet het geval. De commando's SMTP die in het voorbeeld worden gebruikt, zijn de volgende:
    • mail from: expéditeur, om het e-mailadres van de afzender van het bericht aan te geven
    • rcpt to: destinataire, om het e-mailadres van de ontvanger van het bericht aan te geven. Als er meerdere ontvangers zijn, wordt het commando rcpt to: zo vaak als nodig is herhaald voor elke ontvanger.
    • data, waarmee aan de server SMTP wordt aangegeven dat het bericht wordt verzonden. Zoals aangegeven in het antwoord van de server, bestaat dit uit een reeks regels die eindigt met een regel die uitsluitend uit een punt bestaat. Een bericht kan headers bevatten die door een lege regel van de hoofdtekst worden gescheiden. In ons voorbeeld hebben we een onderwerp opgegeven met het trefwoord Subject:
  • zodra het bericht is verzonden, kan men de server laten weten dat men klaar is met het commando quit. De server verbreekt dan de verbinding réseau.Le; de lees-thread kan deze gebeurtenis detecteren en stoppen.
  • De gebruiker typt vervolgens ‘fin’ op het toetsenbord om ook de thread die de ingevoerde opdrachten leest te stoppen.

Als we de ontvangen e-mail controleren, zien we het volgende (Outlook):

Image

Merk op dat de service SMTP niet kan detecteren of een afzender geldig is of niet. Daarom kan men het veld from van een bericht nooit vertrouwen. In dit geval bestond de afzender machin@univ-angers.fr niet. Met deze generieke TCP-client kunnen we het communicatieprotocol van internetdiensten achterhalen en op basis daarvan gespecialiseerde klassen bouwen voor clients van deze diensten. Laten we het communicatieprotocol van de dienst POP (Post Office Protocol) onderzoeken, waarmee e-mails die op een server zijn opgeslagen, kunnen worden opgehaald. Dit protocol werkt op poort 110.

dos>cltgen istia.univ-angers.fr 110
Commandes :
<-- +OK Qpopper (version 4.0.3) at istia.univ-angers.fr starting.
help
<-- -ERR Unknown command: "help".
user st
<-- +OK Password required for st.
pass monpassword
<-- +OK st has 157 visible messages (0 hidden) in 11755927 octets.
list
<-- +OK 157 visible messages (11755927 octets)
<-- 1 892847
<-- 2 171661
...
<-- 156 2843
<-- 157 2796
<-- .
retr 157
<-- +OK 2796 octets
<-- Received: from lagaffe.univ-angers.fr (lagaffe.univ-angers.fr [193.49.144.1])
<--     by istia.univ-angers.fr (8.11.6/8.9.3) with ESMTP id g4D6wZs26600;
<--     Mon, 13 May 2002 08:58:35 +0200
<-- Received: from jaume ([193.49.146.242])
<--     by lagaffe.univ-angers.fr (8.11.1/8.11.2/GeO20000215) with SMTP id g4D6wSd37691;
<--     Mon, 13 May 2002 08:58:28 +0200 (CEST)
...
<-- ------------------------------------------------------------------------
<-- NOC-RENATER2                  Tl.  : 0800 77 47 95
<-- Fax : (+33) 01 40 78 64 00 ,  Email : noc-r2@cssi.renater.fr
<-- ------------------------------------------------------------------------
<--
<-- .
quit
<-- +OK Pop server at istia.univ-angers.fr signing off.
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

De belangrijkste commando's zijn de volgende:

  • user login, waarbij je je login opgeeft op de machine waarop je e-mails staan
  • pass password, waarbij je het wachtwoord opgeeft dat bij de vorige gebruikersnaam hoort
  • list, om de lijst met berichten te krijgen in de vorm van nummer en grootte in bytes
  • retr i, om bericht nr. i te lezen
  • quit, om de dialoog te beëindigen.

Laten we nu eens kijken naar het communicatieprotocol tussen een client en een webserver, die doorgaans op poort 80 draait:

dos>cltgen istia.univ-angers.fr 80
Commandes :
GET /index.html HTTP/1.0

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
<--
<-- <head>
<-- <meta http-equiv="Content-Type"
<-- content="text/html; charset=iso-8859-1">
<-- <meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<-- <title>Bienvenue a l'ISTIA - Universite d'Angers</title>
<-- </head>
....
<-- face="Verdana"> - Dernire mise  jour le <b>10 janvier 2002</b></font></p>
<-- </body>
<-- </html>
<--
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

Een webclient stuurt zijn opdrachten naar de server volgens het volgende schema:

commande1
commande2
...
commanden
[ligne vide]

Pas nadat de lege regel is ontvangen, reageert de webserver. In het voorbeeld hebben we slechts één commando gebruikt:

GET /index.html HTTP/1.0

waarmee de server wordt gevraagd om URL /index.html en wordt aangegeven dat er wordt gewerkt met het protocol HTTP versie 1.0. De meest recente versie van dit protocol is 1.1. Het voorbeeld laat zien dat de server heeft gereageerd door de inhoud van het bestand index.html terug te sturen en vervolgens de verbinding heeft verbroken, aangezien we zien dat de thread voor het lezen van de antwoorden is beëindigd. Voordat de inhoud van het bestand index.html werd verzonden, heeft de webserver een reeks headers verzonden, afgesloten met een lege regel:

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>

De regel <html> is de eerste regel van het bestand /index.html. Het bovenstaande wordt de headers HTTP (HyperText Transfer Protocol) genoemd. We gaan hier niet in detail op deze headers ingaan, maar onthoud dat onze generieke client hier toegang toe biedt, wat nuttig kan zijn om ze te begrijpen. De eerste regel bijvoorbeeld:

<-- HTTP/1.1 200 OK

geeft aan dat de benaderde webserver het protocol HTTP/1.1 ondersteunt en dat hij het gevraagde bestand inderdaad heeft gevonden (200 OK), waarbij 200 een HTTP-antwoordcode is. De regels

<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html

geven aan de client door dat hij 11251 bytes zal ontvangen die de tekst HTML (HyperText Markup Language) vertegenwoordigen en dat de verbinding na afloop van de verzending zal worden verbroken. We hebben hier dus te maken met een zeer handige TCP-client. In feite bestaat deze client al op de machines, waar hij telnet heet, maar het was interessant om hem zelf te schrijven. Het programma van de generieke TCP-client is als volgt:


' naamruimten
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

' de klasse
Public Class clientTcpGénérique
   
    
   ' ontvangt als parameter de kenmerken van een service in de vorm
   ' serverpoort
   ' maakt verbinding met de dienst
   ' maakt een thread aan om op het toetsenbord ingevoerde opdrachten te lezen
   ' deze worden naar de server verzonden
   ' maakt een thread aan om de antwoorden van de server te lezen
   ' deze worden op het scherm weergegeven
   ' het geheel wordt afgesloten met het commando 'fin' dat via het toetsenbord wordt ingevoerd
  Public Shared Sub Main(ByVal args() As String)

    ' syntaxis
    Const syntaxe As String = "pg serveur port"

    ' aantal argumenten
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' de naam van de server wordt genoteerd
    Dim serveur As String = args(0)

    ' de poort moet een geheel getal >0 zijn
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(1))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If
    Dim client As TcpClient = Nothing
    ' er kunnen problemen optreden
    Try
      ' er wordt verbinding gemaakt met de dienst
      client = New TcpClient(serveur, port)
    Catch ex As Exception
      ' fout
      Console.Error.WriteLine(("Impossible de se connecter au service (" & serveur & "," & port & "), erreur : " & ex.Message))
      ' einde
      Return
        End Try
        ' de lees- en schrijfthreads worden aangemaakt
        Dim thReceive As New Thread(New ThreadStart(AddressOf New clientReceive(client).Run))
        Dim thSend As New Thread(New ThreadStart(AddressOf New clientSend(client).Run))

        ' de twee threads worden gestart
        thSend.Start()
        thReceive.Start()

        ' einde van de hoofdthread
        Return
    End Sub

    ' fouten weergeven
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' fout weergeven
        System.Console.Error.WriteLine(msg)
        ' afsluiten met fout
        Environment.Exit(exitCode)
    End Sub
End Class

Public Class clientSend
    ' klasse die verantwoordelijk is voor het lezen van via het toetsenbord ingevoerde opdrachten
    ' en deze via een aan de constructor doorgegeven TCP-client naar een server te verzenden
    Private client As TcpClient    ' le client tcp

    ' constructor
    Public Sub New(ByVal client As TcpClient)
        ' de TCP-client wordt opgegeven
        Me.client = client
    End Sub

    ' Run-methode van de thread
    Public Sub Run()

        ' lokale gegevens
        Dim OUT As StreamWriter = Nothing        ' flux d'écriture réseau
        Dim commande As String = Nothing        ' commande lue au clavier
        ' foutafhandeling
        Try
            ' aanmaken van de netwerkschrijfstroom
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
            ' lus voor het invoeren en verzenden van opdrachten
            Console.Out.WriteLine("Commandes : ")
            While True
                ' lezen van via het toetsenbord ingevoerde opdracht
                commande = Console.In.ReadLine().Trim()
                ' klaar?
                If commande.ToLower() = "fin" Then
                    Exit While
                End If
                ' opdracht naar de server verzenden
                OUT.WriteLine(commande)
            End While
        Catch ex As Exception
            ' fout
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' einde - de streams worden gesloten
        Try
            OUT.Close()
            client.Close()
        Catch
        End Try
        ' het einde van de thread wordt gemeld
        Console.Out.WriteLine("[fin du thread d'envoi des commandes au serveur]")
    End Sub
End Class


Public Class clientReceive
    ' klasse die de tekstregels bestemd voor een 
    ' TCP-client die aan de constructor is doorgegeven
    Private client As TcpClient    ' le client tcp

    ' constructor
    Public Sub New(ByVal client As TcpClient)
        ' de TCP-client wordt genoteerd
        Me.client = client
    End Sub

    'constructor
    ' Run-methode van de thread
    Public Sub Run()

        ' lokale gegevens
        Dim [IN] As StreamReader = Nothing        ' flux lecture réseau
        Dim réponse As String = Nothing        ' réponse serveur
        ' foutbeheer
        Try
            ' aanmaken van de netwerkleesstroom
            [IN] = New StreamReader(client.GetStream())
            ' lus voor het lezen van tekstregels uit de stream IN
            While True
                ' netwerkstream lezen
                réponse = [IN].ReadLine()
                ' stream gesloten?
                If réponse Is Nothing Then
                    Exit While
                End If
                ' weergave
                Console.Out.WriteLine(("<-- " + réponse))
            End While
        Catch ex As Exception
            ' fout
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' einde – de streams worden gesloten
        Try
            [IN].Close()
            client.Close()
        Catch
        End Try
        ' het einde van de thread wordt gemeld
        Console.Out.WriteLine("[fin du thread de lecture des réponses du serveur]")
    End Sub
End Class

9.4.4. Een generieke TCP-server

Nu richten we ons op een server

  • die de door zijn clients verzonden commando’s op het scherm weergeeft
  • en als antwoord de tekstregels terugstuurt die door een gebruiker via het toetsenbord zijn ingevoerd. Het is dus deze laatste die als server fungeert.

Het programma wordt gestart met: srvgen portEcoute, waarbij portEcoute de poort is waarop de clients verbinding moeten maken. De dienstverlening aan de client wordt verzorgd door twee threads:

  • een thread die zich uitsluitend bezighoudt met het lezen van de door de klant verzonden tekstregels
  • een thread die zich uitsluitend bezighoudt met het lezen van de antwoorden die de gebruiker via het toetsenbord invoert. Deze thread geeft met het commando „fin” aan dat de verbinding met de client wordt beëindigd.

De server maakt twee threads per klant aan. Als er n klanten zijn, zullen er 2n threads tegelijkertijd actief zijn. De server stopt zelf nooit, tenzij de gebruiker op het toetsenbord op Ctrl-C drukt. Laten we enkele voorbeelden bekijken.

De server wordt gestart op poort 100 en we gebruiken de generieke client om met de server te communiceren. Het venster van de client ziet er als volgt uit:

dos>cltgen localhost 100
Commandes :
commande 1 du client 1
<-- réponse 1 au client 1
commande 2 du client 1
<-- réponse 2 au client 1
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]

De regels die beginnen met <-- zijn de regels die van de server naar de client worden verzonden, de andere zijn die van de client naar de server. Het servervenster ziet er als volgt uit:

dos>srvgen 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]

De regels die beginnen met <-- zijn de regels die van de client naar de server zijn verzonden. De regels N: zijn de regels die van de server naar client nr. N zijn verzonden. De bovenstaande server is nog steeds actief, terwijl client 1 is beëindigd. We starten een tweede client voor dezelfde server:

dos>cltgen localhost 100
Commandes :
commande 3 du client 2
<-- réponse 3 au client 2
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]

Het servervenster ziet er dan als volgt uit:

dos>srvgen 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- commande 3 du client 2
réponse 3 au client 2
2 : [fin du Thread de lecture des demandes du client 2]
fin
[fin du Thread de lecture des réponses du serveur au client 2]
^C

Laten we nu een webserver simuleren door onze generieke server op poort 88 te starten:

dos>srvgen 88
Serveur générique lancé sur le port 88

Laten we nu een browser openen en de pagina URL http://localhost:88/exemple.html opvragen. De browser maakt vervolgens verbinding met poort 88 van de machine localhost en vraagt vervolgens de pagina /exemple.html op:

Image

Laten we nu eens kijken naar het venster van onze server:

dos>srvgen 88
Serveur générique lancé sur le port 88
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

Zo ontdekken we de headers HTTP die door de browser worden verzonden. Hierdoor kunnen we het protocol HTTP beetje bij beetje ontrafelen. In een eerder voorbeeld hadden we een webclient gemaakt die slechts één commando verstuurde: GET. Dat was toen voldoende. Hier zien we dat de browser nog andere informatie naar de server stuurt. Deze informatie is bedoeld om de server te laten weten met welk type client hij te maken heeft. We zien ook dat de headers HTTP eindigen met een lege regel. Laten we een antwoord voor onze client opstellen. De gebruiker achter het toetsenbord is hier de echte server en kan met de hand een antwoord opstellen. Laten we ons het antwoord van een webserver in een eerder voorbeeld nog eens in herinnering brengen:

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>

Laten we eens proberen een soortgelijk antwoord te geven:

...
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
2 : HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
2 : <html>
2 :   <head><title>Serveur generique</title></head>
2 :   <body>
2 :     <center>
2 :       <h2>Reponse du serveur generique</h2>
2 :     </center>
2 :    </body>
2 : </html>
2 : fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du Thread de lecture des demandes du client 2]
[fin du Thread de lecture des réponses du serveur au client 2]

Regels die beginnen met 2: worden vanaf de server naar client nr. 2 verzonden. Het commando fin verbreekt de verbinding tussen de server en de client. In ons antwoord hebben we ons beperkt tot de volgende HTTP-headers:

HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :

We geven de bestandsgrootte van het bestand dat we gaan verzenden (Content-Length) niet aan, maar we vermelden alleen dat we de verbinding zullen verbreken (Connection: close) nadat dit bestand is verzonden. Dit is voldoende voor de browser. Zodra de verbinding wordt verbroken, weet de browser dat het antwoord van de server is voltooid en geeft hij de pagina HTML weer die naar hem is verzonden. Deze pagina ziet er als volgt uit:

2 : <html>
2 :   <head><title>Serveur generique</title></head>
2 :   <body>
2 :     <center>
2 :       <h2>Reponse du serveur generique</h2>
2 :     </center>
2 :    </body>
2 : </html>

De gebruiker verbreekt vervolgens de verbinding met de client door het commando fin in te voeren. De browser weet dan dat het antwoord van de server is voltooid en kan dit vervolgens weergeven:

Image

Als we hierboven Affichage/Source invoeren om te zien wat de browser heeft ontvangen, krijgen we:

Image

dat wil zeggen precies wat we vanaf de generieke server hebben verzonden. De code van de generieke server TCP is als volgt:


' naamruimten
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

Public Class serveurTcpGénérique

    ' hoofdprogramma
    Public Shared Sub Main(ByVal args() As String)

        ' ontvangt de luisterpoort voor verzoeken van klanten
        ' maakt een thread aan om de verzoeken van de client te lezen
        ' deze worden op het scherm weergegeven
        ' maakt een thread aan om op het toetsenbord ingevoerde opdrachten te lezen
        ' deze worden als antwoord naar de klant verzonden
        ' het geheel wordt afgesloten met het commando 'fin' dat via het toetsenbord wordt ingevoerd

        Const syntaxe As String = "Syntaxe : pg port"

        ' is er een argument
        If args.Length <> 1 Then
            erreur(syntaxe, 1)
        End If
        ' dit argument moet een geheel getal >0 zijn
        Dim port As Integer = 0
        Dim erreurPort As Boolean = False
        Dim E As Exception = Nothing
        Try
            port = Integer.Parse(args(0))
        Catch ex As Exception
            E = ex
            erreurPort = True
        End Try
        erreurPort = erreurPort Or port <= 0
        If erreurPort Then
            erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
        End If
        ' we maken de luisterservice aan
        Dim ecoute As TcpListener = Nothing
        Dim nbClients As Integer = 0     ' nbre de clients traités
        Try
            ' de service wordt aangemaakt
            ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
            ' de service wordt gestart
            ecoute.Start()
            ' opvolging
            Console.Out.WriteLine(("Serveur générique lancé sur le port " & port))

            ' lus voor het bedienen van klanten
            Dim client As TcpClient = Nothing
            While True        ' boucle infinie - sera arrêtée par Ctrl-C
                ' wachten op een klant
                client = ecoute.AcceptTcpClient()

                ' de service wordt uitgevoerd door afzonderlijke threads
                nbClients += 1
                ' thread voor het lezen van verzoeken van klanten
                Dim thReceive As New Thread(New ThreadStart(AddressOf New serveurReceive(client, nbClients).Run))
                ' thread voor het lezen van antwoorden die de gebruiker via het toetsenbord invoert
                Dim thSend As New Thread(New ThreadStart(AddressOf New serveurSend(client, nbClients).Run))

                ' de uitvoering van beide threads wordt gestart
                thSend.Start()
                thReceive.Start()
            End While
            ' er wordt teruggekeerd naar het afwachten van verzoeken
        Catch ex As Exception
            ' de fout wordt gemeld
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
    End Sub

    ' fouten weergeven
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' fout weergeven
        System.Console.Error.WriteLine(msg)
        ' afsluiten met fout
        Environment.Exit(exitCode)
    End Sub
End Class


Public Class serveurSend
    ' klasse die verantwoordelijk is voor het lezen van via het toetsenbord ingevoerde antwoorden
    ' en deze via een aan de constructor doorgegeven TCP-client naar een client te verzenden
    Private client As TcpClient    ' le client tcp
    Private numClient As Integer    ' n° de client

    ' constructor
    Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
        ' de TCP-client wordt geregistreerd
        Me.client = client
        ' en het nummer ervan
        Me.numClient = numClient
    End Sub

    ' Run-methode van de thread
    Public Sub Run()

        ' lokale gegevens
        Dim OUT As StreamWriter = Nothing        ' flux d'écriture réseau
        Dim réponse As String = Nothing        ' réponse lue au clavier
        ' tracking
        Console.Out.WriteLine(("Thread de lecture des réponses du serveur au client " & numClient & " lancé"))
        ' foutbeheer
        Try
            ' aanmaken van de netwerk-schrijfstroom
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
            ' lus voor het invoeren en verzenden van opdrachten
            While True
                ' klantidentificatie
                Console.Out.Write((numClient & " : "))
                ' antwoord ingevoerd via het toetsenbord
                réponse = Console.In.ReadLine().Trim()
                ' klaar?
                If réponse.ToLower() = "fin" Then
                    Exit While
                End If
                ' antwoord naar de server verzenden
                OUT.WriteLine(réponse)
            End While
            ' volgend antwoord
        Catch ex As Exception
            ' fout
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' einde - streams worden gesloten
        Try
            OUT.Close()
            client.Close()
        Catch
        End Try
        ' het einde van de thread wordt gemeld
        Console.Out.WriteLine(("[fin du Thread de lecture des réponses du serveur au client " & numClient & "]"))
    End Sub
End Class

Public Class serveurReceive
    ' klasse die verantwoordelijk is voor het lezen van de naar de server verzonden tekstregels 
    ' via een TCP-client die aan de constructor wordt doorgegeven
    Private client As TcpClient     ' le client tcp
    Private numClient As Integer    ' n° de client

    ' constructor
    Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
        ' de TCP-client wordt genoteerd
        Me.client = client
        ' en het bijbehorende nummer
        Me.numClient = numClient
    End Sub

    ' Run-methode van de thread
    Public Sub Run()
        ' lokale gegevens
        Dim [IN] As StreamReader = Nothing        ' flux lecture réseau
        Dim réponse As String = Nothing        ' réponse serveur
        ' tracking
        Console.Out.WriteLine(("Thread de lecture des demandes du client " & numClient & " lancé"))
        ' foutbeheer
        Try
            ' aanmaken van de netwerkleesstroom
            [IN] = New StreamReader(client.GetStream())
            ' lus voor het lezen van tekstregels uit de stream IN
            While True
                ' netwerkstream lezen
                réponse = [IN].ReadLine()
                ' stream gesloten?
                If réponse Is Nothing Then
                    Exit While
                End If
                ' weergave
                Console.Out.WriteLine(("<-- " + réponse))
            End While
        Catch ex As Exception
            ' fout
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' einde – de streams worden gesloten
        Try
            [IN].Close()
            client.Close()
        Catch
        End Try
        ' het einde van de thread wordt gemeld
        Console.Out.WriteLine(("[fin du Thread de lecture des demandes du client " & numClient & "]"))
    End Sub
End Class

9.4.5. Een webclient

In het vorige voorbeeld hebben we enkele van de HTTP-headers gezien die een browser verstuurde:

<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

We gaan een webclient schrijven waaraan we een URL als parameter doorgeven en die de door de server verzonden tekst op het scherm weergeeft. We gaan ervan uit dat de server het protocol HTTP 1.1 ondersteunt. Van de bovenstaande headers gebruiken we alleen de volgende:

<-- GET /exemple.html HTTP/1.1
<-- Host: localhost:88
<-- Connection: close
  • De eerste header geeft aan welke pagina we willen
  • de tweede welke server we benaderen
  • de derde dat we willen dat de server de verbinding verbreekt nadat hij ons heeft geantwoord.

Als we hierboven GET vervangen door HEAD, zal de server ons alleen de headers HTTP sturen en niet de pagina HTML.

Onze webclient wordt als volgt aangeroepen: clientweb URL cmd, waarbij URL degewenste URL is en cmd een van de twee trefwoorden GET of HEAD om aan te geven of we alleen de headers (HEAD) willen of ook de inhoud van de pagina (GET). Laten we een eerste voorbeeld bekijken. We starten de server IIS en vervolgens de webclient op dezelfde machine:

dos>clientweb http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:23:37 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/
Cache-control: private

Het antwoord

HTTP/1.1 302 Object moved

betekent dat de opgevraagde pagina van plaats is veranderd (dus van URL). De nieuwe URL wordt aangegeven door de Location:-header:

Location: /IISSamples/Default/welcome.htm

Als we GET gebruiken in plaats van HEAD in de aanroep naar de webclient:

dos>clientweb http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:33:36 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=IMFNCCMDAKPNNGMGMFIHENFE; path=/
Cache-control: private

<head><title>L'objet a changé d'emplacement</title></head>
<body><h1>L'objet a changé d'emplacement</h1>Cet objet peut être trouvé <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>

We krijgen hetzelfde resultaat als met HEAD, met daarnaast de hoofdtekst van de pagina HTML. Het programma is als volgt:


' naamruimten
Imports System
Imports System.Net.Sockets
Imports System.IO


Public Class clientWeb1

    ' vraagt om een URL
    ' geeft de inhoud ervan weer op het scherm
    Public Shared Sub Main(ByVal args() As String)
        ' syntaxis
        Const syntaxe As String = "pg URI GET/HEAD"

        ' aantal argumenten
        If args.Length <> 2 Then
            erreur(syntaxe, 1)
        End If
        ' de aangevraagde URI wordt genoteerd
        Dim URIstring As String = args(0)
        Dim commande As String = args(1).ToUpper()

        ' controle op de geldigheid van de URI
        Dim uri As Uri = Nothing
        Try
            uri = New Uri(URIstring)
        Catch ex As Exception
            ' URI is onjuist
            erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
        End Try
        ' controle van de bestelling
        If commande <> "GET" And commande <> "HEAD" Then
            ' onjuiste bestelling
            erreur("Le second paramètre doit être GET ou HEAD", 3)
        End If

        ' we kunnen aan de slag
        Dim client As TcpClient = Nothing        ' le client
        Dim [IN] As StreamReader = Nothing        ' le flux de lecture du client
        Dim OUT As StreamWriter = Nothing        ' le flux d'écriture du client
        Dim réponse As String = Nothing        ' réponse du serveur
        Try
            ' verbinding maken met de server
            client = New TcpClient(uri.Host, uri.Port)

            ' de in- en uitgaande stromen van de klant worden aangemaakt TCP
            [IN] = New StreamReader(client.GetStream())
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True

            ' URL wordt opgevraagd - verzending van de headers HTTP
            OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
            OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
            OUT.WriteLine("Connection: close")
            OUT.WriteLine()
            ' het antwoord wordt gelezen
            réponse = [IN].ReadLine()
            While Not (réponse Is Nothing)
                ' het antwoord wordt verwerkt
                Console.Out.WriteLine(réponse)
                ' het antwoord wordt gelezen
                réponse = [IN].ReadLine()
            End While
            ' het is klaar
            client.Close()
        Catch e As Exception
            ' de uitzondering wordt afgehandeld
            erreur(e.Message, 4)
        End Try
    End Sub

    ' fouten weergeven
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' fout weergeven
        System.Console.Error.WriteLine(msg)
        ' afsluiten met fout
        Environment.Exit(exitCode)
    End Sub
End Class

Het enige nieuwe in dit programma is het gebruik van de klasse Uri. Het programma ontvangt een URL (Uniform Resource Locator) of URI (Uniform Resource Identifier) in de vorm http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... Met de klasse Uri kunnen we de tekenreeks van de URL opsplitsen in de verschillende onderdelen. Er wordt een Uri-object geconstrueerd op basis van de tekenreeks URIstring die als parameter is ontvangen:


        ' geldigheid van URI controleren
        Dim uri As Uri = Nothing
        Try
            uri = New Uri(URIstring)
        Catch ex As Exception
            ' URI onjuist
            erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
        End Try

Als de als parameter ontvangen tekenreeks URI geen geldige URI is (ontbrekend protocol, ontbrekende server, ...), wordt er een uitzondering gegenereerd. Zo kunnen we de geldigheid van de ontvangen parameter controleren. Zodra het Uri-object is opgebouwd, hebben we toegang tot de verschillende elementen van deze Uri. Als het object uri uit de vorige code dus is opgebouwd op basis van de tekenreeks http://serveur:port/cheminPageHTML?param1=val1;param2=val2;..., dan hebben we:

uri.Host=serveur, uri.Port=port, uri.Path=cheminPageHTML, uri.Query=param1=val1;param2=val2;..., uri.pathAndQuery= cheminPageHTML?param1=val1;param2=val2;..., uri.Scheme=http.

9.4.6. Webclient die omleidingen afhandelt

De vorige webclient verwerkt geen eventuele omleiding van de URL die hij heeft aangevraagd. De volgende client verwerkt deze wel.

  1. hij leest de eerste regel van de headers HTTP die door de server zijn verzonden om te controleren of daarin de tekenreeks 302 Object moved voorkomt, die duidt op een omleiding
  2. hij leest de volgende headers. Als er een omleiding is, zoekt hij naar de regel „Location: url“ die de nieuwe URL van de opgevraagde pagina aangeeft en noteert hij deze URL.
  3. het geeft de rest van het antwoord van de server weer. Als er een omleiding is, worden stap 1 tot en met 3 herhaald met de nieuwe URL. Het programma accepteert niet meer dan één omleiding. Deze limiet wordt bepaald door een constante die kan worden aangepast.

Hier volgt een voorbeeld:

dos>clientweb2 http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 11:38:55 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=PDGNCCMDNCAOFDMPHCJNPBAI; path=/
Cache-control: private

<head><title>L'objet a chang d'emplacement</title></head>
<body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>

<--Redirection vers l'URL http://localhost:80/IISSamples/Default/welcome.htm-->

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Connection: close
Date: Mon, 13 May 2002 11:38:55 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 16 Feb 1998 21:16:22 GMT
ETag: "0174e21203bbd1:978"
Content-Length: 4781

<html>

<head>
<title>Bienvenue dans le Serveur Web personnel</title>
</head>
....
</body>
</html>

Het programma is als volgt:


' naamruimten
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic

' webclientklasse
Public Class clientWeb
   
  ' vraagt een URL op en geeft de inhoud ervan weer op het scherm
  Public Shared Sub Main(ByVal args() As String)
    ' syntaxis
    Const syntaxe As String = "pg URI GET/HEAD"

    ' aantal argumenten
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' de aangevraagde URI wordt genoteerd
    Dim URIstring As String = args(0)
    Dim commande As String = args(1).ToUpper()

    ' controle op de geldigheid van de URI
    Dim uri As Uri = Nothing
    Try
      uri = New Uri(URIstring)
    Catch ex As Exception
      ' URI is onjuist
      erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
    End Try 'catch
    ' controle van de bestelling
    If commande <> "GET" And commande <> "HEAD" Then
      ' onjuiste bestelling
      erreur("Le second paramètre doit être GET ou HEAD", 3)
    End If

    ' we kunnen aan de slag
    Dim client As TcpClient = Nothing ' le client
    Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
    Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
    Dim réponse As String = Nothing ' réponse du serveur
    Const nbRedirsMax As Integer = 1 ' pas plus d'une redirection acceptée
    Dim nbRedirs As Integer = 0 ' nombre de redirections en cours
    Dim premièreLigne As String ' 1ère ligne de la réponse
    Dim redir As Boolean = False ' indique s'il y a redirection ou non
    Dim locationString As String = "" ' la chaîne URI d'une éventuelle redirection
    ' reguliere expressie om een omleidings-URL te vinden
    Dim location As New Regex("^Location: (.+?)$") '

        ' foutafhandeling
    Try
      ' er kunnen meerdere URL'en worden opgevraagd als er omleidingen zijn
      While nbRedirs <= nbRedirsMax
        ' verbinding maken met de server
        client = New TcpClient(uri.Host, uri.Port)

        ' de in- en uitgaande stromen van de client TCP worden aangemaakt
        [IN] = New StreamReader(client.GetStream())
        OUT = New StreamWriter(client.GetStream())
        OUT.AutoFlush = True

        ' de headers HTTP worden verzonden om de URL op te vragen
        OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
        OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
        OUT.WriteLine("Connection: close")
        OUT.WriteLine()

        ' de eerste regel van het antwoord wordt gelezen
        premièreLigne = [IN].ReadLine()
        ' schermuitvoer
        Console.Out.WriteLine(premièreLigne)

        ' omleiding?
        If Regex.IsMatch(premièreLigne, "302 Object moved$") Then
          ' er is een omleiding
          redir = True
          nbRedirs += 1
                End If

                ' de volgende HTTP-headers totdat de lege regel wordt gevonden die het einde van de headers aangeeft
                Dim locationFound As Boolean = False
                réponse = [IN].ReadLine()
                While réponse <> ""
                    ' het antwoord wordt weergegeven
                    Console.Out.WriteLine(réponse)
                    ' als er een omleiding is, wordt de Location-header gezocht
                    If redir And Not locationFound Then
                        ' de regel wordt vergeleken met de relationele uitdrukking location
                        Dim résultat As Match = location.Match(réponse)
                        If résultat.Success Then
                            ' als deze is gevonden, noteer dan de omleidingscode URL
                            locationString = résultat.Groups(1).Value
                            ' er wordt genoteerd dat er iets is gevonden
                            locationFound = True
                        End If
                    End If
                    ' volgende regel
                    réponse = [IN].ReadLine()
                End While

                ' volgende regels van het antwoord
                Console.Out.WriteLine(réponse)
                réponse = [IN].ReadLine()
                While Not (réponse Is Nothing)
                    ' het antwoord wordt weergegeven
                    Console.Out.WriteLine(réponse)
                    ' volgende regel
                    réponse = [IN].ReadLine()
                End While

                ' de verbinding wordt verbroken
                client.Close()
                ' zijn we klaar?
                If Not locationFound Or nbRedirs > nbRedirsMax Then
                    Exit While
                End If

                ' er moet een omleiding worden uitgevoerd – we stellen de nieuwe URI samen
                URIstring = uri.Scheme + "://" & uri.Host & ":" & uri.Port & locationString
                uri = New Uri(URIstring)
                ' opvolging
                Console.Out.WriteLine((ControlChars.Lf + "<--Redirection vers l'URL " + URIstring + "-->" + ControlChars.Lf))
            End While
        Catch e As Exception
      ' de uitzondering wordt afgehandeld
      erreur(e.Message, 4)
        End Try
    End Sub

    ' fouten weergeven
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' fout weergeven
        System.Console.Error.WriteLine(msg)
        ' afbreken met fout
        Environment.Exit(exitCode)
    End Sub
End Class

9.4.7. Server voor belastingberekening

We pakken de oefening IMPOTS weer op, die al in verschillende vormen is behandeld. Laten we de laatste versie nog eens bekijken. Er is een belastingklasse aangemaakt. De attributen daarvan zijn drie tabellen met getallen:


Public Class impôt
    ' de gegevens die nodig zijn voor de berekening van de belasting
    ' zijn afkomstig van een externe bron
    Private limites(), coeffR(), coeffN() as double

De klasse heeft twee constructors:

  • een constructor waaraan de drie gegevensarrays worden doorgegeven die nodig zijn voor de berekening van de belasting
     // fabrikant 1
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
         ' initialiseert de drie limiettabellen, coeffR, coeffN, op basis van
         ' de parameters die aan de constructor zijn doorgegeven
  • een fabrikant aan wie de naam DSN uit een database ODBC wordt doorgegeven

    ' constructor 2
    Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        ' initialiseert de drie limietarrays, coeffR, coeffN, op basis van
        ' de inhoud van de tabel Timpots uit de database ODBC DSNimpots
        ' colLimites, colCoeffR, colCoeffN zijn de drie kolommen van deze tabel
        ' kan een uitzondering veroorzaken

Er was een testprogramma geschreven:

dos>vbc /r:impots.dll testimpots.vb

dos>test mysql-impots timpots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 200000
impôt=33388 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 3 200000
impôt=16400 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 300000
impôt=50082 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 200000
impôt=22506 F

Hier bevonden het testprogramma en het object impôt zich op dezelfde machine. We zijn van plan om het testprogramma en het object impôt op verschillende machines te plaatsen. We krijgen dan een client-server-toepassing waarbij het externe object impôt als server fungeert. De nieuwe klasse heet ServeurImpots en is afgeleid van de klasse impôt:


Public Class ServeurImpots
    Inherits impôt

    ' attributen
    Private portEcoute As Integer    ' le port d'écoute des demandes clients
    Private actif As Boolean    ' état du serveur

    ' constructor
    Public Sub New(ByVal portEcoute As Integer, ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        MyBase.New(DSNimpots, Timpots, colLimites, colCoeffR, colCoeffN)
        ' let op de luisterpoort
        Me.portEcoute = portEcoute
        ' momenteel inactief
        actif = False
        ' maakt een thread aan en start deze om de via het toetsenbord ingevoerde opdrachten te lezen
        ' de server wordt beheerd op basis van deze opdrachten
        Dim threadLecture As Thread = New Thread(New ThreadStart(AddressOf admin))
        threadLecture.Start()
    End Sub

De enige nieuwe parameter in de constructor is de poort waarop naar verzoeken van clients wordt geluisterd. De overige parameters worden rechtstreeks doorgegeven aan de basisklasse impôt. De belasting-server wordt aangestuurd door commando’s die via het toetsenbord worden ingevoerd. Daarom maken we een thread aan om deze commando’s te lezen. Er zijn twee mogelijke commando’s: start om de service te starten en stop om deze definitief te stoppen. De methode admin die deze commando’s afhandelt, is als volgt:


    Public Sub admin()
        ' leest de via het toetsenbord ingevoerde beheeropdrachten voor de server
        ' in een eindeloze lus
        Dim commande As String = Nothing
        While True
            ' prompt
            Console.Out.Write("Serveur d'impôts>")
            ' commando lezen
            commande = Console.In.ReadLine().Trim().ToLower()
            ' commando uitvoeren
            If commande = "start" Then
                ' actief?
                If actif Then
                    'fout
                    Console.Out.WriteLine("Le serveur est déjà actif")
                Else
                    ' de luisterservice wordt gestart
                    Dim threadEcoute As Thread = New Thread(New ThreadStart(AddressOf ecoute))
                    threadEcoute.Start()
                End If
            Else
                If commande = "stop" Then
                    ' einde van alle uitvoeringsthreads
                    Environment.Exit(0)
                Else
                    ' fout
                    Console.Out.WriteLine("Commande incorrecte. Utilisez (start,stop)")
                End If
            End If
        End While
    End Sub

Als het via het toetsenbord ingevoerde commando start is, wordt een thread gestart die luistert naar verzoeken van clients. Als het ingevoerde commando stop is, worden alle threads gestopt. De luisterthread voert de methode ecoute uit:


    Public Sub ecoute()
        ' thread voor het afluisteren van verzoeken van clients
        ' de luisterservice wordt aangemaakt
        Dim ecoute As TcpListener = Nothing
        Try
            ' de service wordt aangemaakt
            ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), portEcoute)
            ' de service wordt gestart
            ecoute.Start()
            ' vervolg
            Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & portEcoute))

            ' servicelus
            Dim liaisonClient As TcpClient = Nothing
            While True            ' boucle infinie
                ' wachten op een client
                liaisonClient = ecoute.AcceptTcpClient()
                ' de service wordt door een andere taak verzorgd
                Dim threadClient As Thread = New Thread(New ThreadStart(AddressOf New traiteClientImpots(liaisonClient, Me).Run))
                threadClient.Start()
            End While
            ' we gaan weer luisteren naar verzoeken
        Catch ex As Exception
            ' de fout wordt gemeld
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
    End Sub

    ' fouten weergeven
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' fout weergeven
        System.Console.Error.WriteLine(msg)
        ' afsluiten met fout
        Environment.Exit(exitCode)
    End Sub

Er is een klassieke TCP-server die luistert op poort portEcoute. Verzoeken van clients worden verwerkt door de methode Run van een object waaraan twee parameters worden doorgegeven:

  1. het object TcpClient, waarmee de client kan worden bereikt
  2. het object impôt this, dat toegang geeft tot de methode this.calculer voor het berekenen van de belasting.

' -------------------------------------------------------
' verzorgt de dienstverlening aan een klant van de belastingserver
Public Class traiteClientImpots

    Private liaisonClient As TcpClient    ' liaison avec le client
    Private [IN] As StreamReader    ' flux d'entrée
    Private OUT As StreamWriter    ' flux de sortie
    Private objImpôt As impôt     ' objet Impôt

    ' constructor
    Public Sub New(ByVal liaisonClient As TcpClient, ByVal objImpôt As impôt)
        Me.liaisonClient = liaisonClient
        Me.objImpôt = objImpôt
    End Sub

De methode Run verwerkt de aanvragen van klanten. Deze kunnen twee vormen aannemen:

  1. berekening gehuwd (j/n) nbEnfants salaireAnnuel
  2. financiële berekeningen

Vorm 1 maakt de berekening van een belasting mogelijk, vorm 2 verbreekt de verbinding tussen client en server.


    ' Run-methode
    Public Sub Run()
        ' levert de dienst aan de client
        Try
            ' invoerstroom
            [IN] = New StreamReader(liaisonClient.GetStream())
            ' uitvoerstroom
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            ' een welkomstbericht naar de klant sturen
            OUT.WriteLine("Bienvenue sur le serveur d'impôts")

            ' lus voor het lezen van verzoeken en het schrijven van antwoorden
            Dim demande As String = Nothing
            Dim champs As String() = Nothing            ' les éléments de la demande
            Dim commande As String = Nothing            ' la commande du client : calcul ou fincalculs
            demande = [IN].ReadLine()
            While Not (demande Is Nothing)
                ' het verzoek wordt opgesplitst in velden
                champs = Regex.Split(demande.Trim().ToLower(), "\s+")
                ' twee verzoeken geaccepteerd: berekening en einde berekeningen
                commande = champs(0)
                Dim erreur As Boolean = False
                If commande <> "calcul" And commande <> "fincalculs" Then
                    ' fout bij de klant
                    OUT.WriteLine("Commande incorrecte. Utilisez (calcul,fincalculs).")
                End If
                If commande = "calcul" Then
                    calculerImpôt(champs)
                End If
                If commande = "fincalculs" Then
                    ' afscheidsbericht aan de klant
                    OUT.WriteLine("Au revoir...")
                    ' vrijgeven van bronnen
                    Try
                        OUT.Close()
                        [IN].Close()
                        liaisonClient.Close()
                    Catch
                    End Try
                    ' einde
                    Return
                End If
                ' nieuw verzoek
                demande = [IN].ReadLine()
            End While
        Catch e As Exception
            erreur("L'erreur suivante s'est produite (" + e.ToString + ")", 2)
        End Try
    End Sub

De belastingberekening wordt uitgevoerd door de methode calculerImpôt, die als parameter de tabel met velden van het verzoek van de klant ontvangt. De geldigheid van het verzoek wordt gecontroleerd en eventueel wordt de belasting berekend en teruggestuurd naar de klant.


    ' belastingberekening
    Public Sub calculerImpôt(ByVal champs() As String)
        ' verwerkt de aanvraag: berekening gehuwden nbEnfants salaireAnnuel
        ' opgesplitst in velden in de veldtabel
        Dim marié As String = Nothing
        Dim nbEnfants As Integer = 0
        Dim salaireAnnuel As Integer = 0

        ' geldigheid van de argumenten
        Try
            ' er zijn minimaal 4 velden nodig
            If champs.Length <> 4 Then
                Throw New Exception
            End If
            ' getrouwd
            marié = champs(1)
            If marié <> "o" And marié <> "n" Then
                Throw New Exception
            End If
            ' kinderen
            nbEnfants = Integer.Parse(champs(2))
            ' salaris
            salaireAnnuel = Integer.Parse(champs(3))
        Catch
            OUT.WriteLine(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel")
            ' klaar
            Exit Sub
        End Try
        ' de belasting kan worden berekend
        Dim impot As Long = objImpôt.calculer(marié = "o", nbEnfants, salaireAnnuel)
        ' het antwoord wordt naar de klant verzonden
        OUT.WriteLine(impot.ToString)
    End Sub

    ' foutmeldingen weergeven
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' fout weergeven
        System.Console.Error.WriteLine(msg)
        ' afbreken met fout
        Environment.Exit(exitCode)
    End Sub

Deze klasse wordt gecompileerd door

dos>vbc /r:impots.dll /r:system.dll /t:library srvimpots.vb

waarbij impots.dll de code van de klasse impôt bevat. Een testprogramma zou er als volgt uit kunnen zien:


' naamruimten
Imports System
Imports System.IO
Imports Microsoft.VisualBasic

Public Class testServeurImpots
    Public Shared syntaxe As String = "Syntaxe : pg port dsnImpots Timpots colLimites colCoeffR colCoeffN"

    ' hoofdprogramma
    Public Shared Sub Main(ByVal args() As String)

        ' er zijn 6 argumenten nodig
        If args.Length <> 6 Then
            erreur(syntaxe, 1)
        End If
        ' de poort moet een geheel getal >0 zijn
        Dim port As Integer = 0
        Dim erreurPort As Boolean = False
        Dim E As Exception = Nothing
        Try
            port = Integer.Parse(args(0))
        Catch ex As Exception
            E = ex
            erreurPort = True
        End Try
        erreurPort = erreurPort Or port <= 0
        If erreurPort Then
            erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
        End If
        ' de belasting-server wordt aangemaakt
        Try
            Dim srvimots As ServeurImpots = New ServeurImpots(port, args(1), args(2), args(3), args(4), args(5))
        Catch ex As Exception
            'fout
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
    End Sub

    ' foutmeldingen weergeven
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' foutmelding
        System.Console.Error.WriteLine(msg)
        ' afgebroken met fout
        Environment.Exit(exitCode)
    End Sub
End Class

We geven aan het testprogramma de gegevens door die nodig zijn voor het aanmaken van een object ServeurImpots en op basis daarvan maakt het dit object aan. Dit testprogramma wordt gecompileerd door:

dos>vbc /r:srvimpots.dll /r:impots.dll testimpots.vb

Hier volgt een eerste test:

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop

De regel

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn

maakt een object ServeurImpots aan dat nog niet luistert naar verzoeken van clients. Het is het via het toetsenbord ingevoerde commando start dat dit luisteren activeert. Het commando stop stopt de server. Laten we nu een client gebruiken. We gebruiken de eerder aangemaakte generieke client. De server wordt gestart:

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124

De generieke client wordt in een ander DOS-venster gestart:

dos> clttcpgenerique localhost 124Commandes :
<-- Bienvenue sur le serveur d'impôts

We zien dat de client het welkomstbericht van de server correct heeft ontvangen. We sturen nog enkele commando's:

x
<-- Commande incorrecte. Utilisez (calcul,fincalculs).
calcul
<--  syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel
calcul o 2 200000
<-- 22506
calcul n 2 200000
<-- 33388
fincalculs
<-- Au revoir...
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

We gaan terug naar het servervenster om de server te stoppen:

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop