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:
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). | |
Verbergt de fysieke kenmerken van de fysieke laag. Detecteert en corrigeert transmissiefouten. | |
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. | |
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. | |
In deze laag vinden we diensten waarmee een applicatie een werksessie op een externe machine kan openen en in stand houden. | |
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. | |
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.
Verzendt pakketten tussen twee knooppunten in het netwerk | |
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. | |
zorgt voor de koppeling tussen het internetadres van de machine en het fysieke adres van de machine | |
zet het fysieke adres van de machine om naar het internetadres van de machine |
Transport-/sessielagen
In deze laag bevinden zich de volgende protocollen:
Zorgt voor een betrouwbare overdracht van informatie tussen twee clients | |
Zorgt voor een onbetrouwbare overdracht van informatie tussen twee clients |
Lagen: Toepassing/Presentatie/Sessie
Hier vindt u diverse protocollen:
Terminalemulator waarmee machine A verbinding kan maken met machine B als terminal | |
maakt bestandsoverdracht mogelijk | |
maakt bestandsoverdracht mogelijk | |
maakt het uitwisselen van berichten tussen netwerkgebruikers mogelijk | |
zet een computernaam om in het internetadres van de computer | |
gemaakt door sun MicroSystems, specificeert een standaard, machine-onafhankelijke weergave van de gegevens | |
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 | |
, 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:

De meeste methoden van de klasse zijn statisch. Laten we eens kijken naar de methoden die voor ons van belang zijn:
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. | |
geeft een adres IPHostEntry terug op basis van een machinenaam. Er wordt een uitzondering gegenereerd als de machine name niet kan worden gevonden. | |
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:
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. | |
lijst met aliassen van een machine, die kan worden aangeduid met een hoofdnaam en aliassen | |
de naam van de machine, indien deze er een heeft |
Uit de klasse IPAddress onthouden we de volgende constructor, eigenschappen en methoden:

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:

De constructors, methoden en eigenschappen die voor ons van belang zijn, zijn de volgende:
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 | |
verbreekt de verbinding met de TCP-server | |
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:

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
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:
Om het antwoord van M2 te lezen, schrijf je:
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:

De constructors, methoden en eigenschappen die voor ons van belang zijn, zijn de volgende:
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. | |
accepteert het verzoek van een klant. Geeft als resultaat een object TcpClient dat is gekoppeld aan een andere poort, de zogenaamde servicepoort. | |
begint te luisteren naar verzoeken van klanten | |
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:
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:
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:
- een thread die de op het toetsenbord ingevoerde commando’s leest en deze naar de server verstuurt
- 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:
- 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):

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:
Pas nadat de lege regel is ontvangen, reageert de webserver. In het voorbeeld hebben we slechts één commando gebruikt:
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:
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
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:
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:

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:

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

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:
- 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
betekent dat de opgevraagde pagina van plaats is veranderd (dus van URL). De nieuwe URL wordt aangegeven door de Location:-header:
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.
- 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
- 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.
- 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:
- het object TcpClient, waarmee de client kan worden bereikt
- 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:
- berekening gehuwd (j/n) nbEnfants salaireAnnuel
- 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
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:
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
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:
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:















