Skip to content

8. Programmering TCP-IP

8.1. Algemeen

8.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 de 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

8.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:

Image

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 toepassing geeft de informatie die ze wil verzenden door aan de onderliggende laag: de laag Présentation. De toepassing 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 doorgestuurd naar de laag Session, enzovoort, 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:

Image

De rol van de verschillende lagen is als volgt:

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

8.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:

Image

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 van 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

Image

  • 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, afhankelijk 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 op de kaart staat die de machine met de kabel verbindt. Dit adres wordt het Ethernet-adres van de machine genoemd.

Netwerklaag

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

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

Transport-/sessielagen

In deze laag bevinden zich de volgende protocollen:

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

Toepassings-/presentatie-/sessielagen

Hier vinden we diverse protocollen:

TELNET
Terminalemulator waarmee machine A verbinding kan maken met machine B als terminal
FTP (File Transfer Protocol)
maakt bestandsoverdracht mogelijk
TFTP (Trivial File Transfer Protocol)
maakt bestandsoverdracht mogelijk
SMTP (Simple Mail Transfer protocol)
maakt het uitwisselen van berichten tussen netwerkgebruikers mogelijk
DNS (Domain Name System)
zet een computernaam om in het internetadres van de computer
XDR (eXternal Data Representation)
gemaakt door Sun MicroSystems, specificeert een standaard, machine-onafhankelijke weergave van de gegevens
RPC(Remote Procedures Call)
ook gedefinieerd door Sun; het is een communicatieprotocol tussen externe applicaties, onafhankelijk van de transportlaag. Dit protocol is belangrijk: het ontlast de programmeur van de 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)
, 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

8.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:

Image

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.
  • Deze laag 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.

8.1.5. Adresseringsproblemen 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 TCP/IP-protocollen. 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 IP-adres <--> fysiek adres, in beide richtingen.

De IP-adressen moeten allemaal verschillend zijn. Officiële instanties zijn belast met de toewijzing ervan. In feite wijzen deze instanties een adres toe aan lokale netwerken, bijvoorbeeld 193.49.144.0 voor het netwerk van de faculteit der wetenschappen van Angers. De beheerder van dit netwerk kan vervolgens de adressen IP 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 machine die op het netwerk is aangesloten.

8.1.5.1. De adresklassen IP

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

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

Afhankelijk van de grootte van deze twee velden worden de 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:

Image

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:

Image

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:

Image

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 1-en bestaat, is er sprake van een broadcast-adres: 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.

8.1.5.2. De 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 laag IP in pakketten werden ingekapseld. Deze hebben de volgende vorm:

Image

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:

Image

In het uiteindelijke frame staan de fysieke adressen van de bron- en bestemmingscomputer. 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 daarentegen staat opgeslagen in het 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 verschillende knooppunten te bewerken. Dit kan vervelend zijn en tot fouten leiden als er veel computers 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 haar IP-adres zou moeten vinden. Wanneer de machine ontdekt dat hij geen IP-adres 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 opneemt. 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 fysiek adres <--> IP-adres van alle knooppunten. Vervolgens antwoordt hij 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.

8.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:

Image

Het belangrijkste 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:

Image

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.

8.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 verwijst naar het rechtstreeks doorsturen van een pakket IP van de afzender naar de ontvanger binnen hetzelfde netwerk:

  • 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 van de afzender. In dit geval verschillen de netwerkadresdelen van de adressen IP van de bron- en bestemmingscomputer. De broncomputer 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.

Image

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.

8.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.

8.1.7. De transportlaag: de protocollen UDP en TCP

8.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. Bepaalde 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:

Image

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

8.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 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 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 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 vanaf dat punt de verzending van de segmenten hervatten.

8.1.8. De applicatielaag

Bovenop de protocollen UDP en TCP bestaan er diverse standaardprotocollen:

TELNET

Met dit protocol kan een gebruiker van machine A in het netwerk verbinding 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, bijvoorbeeld 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 Universiteit van Angers met adres ISERPA

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 applicatie kan dus naar een machine verwijzen via haar naam. Uiteindelijk moet het internetadres van die machine echter toch worden verkregen. Hoe gebeurt dat? 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, een zogenaamd 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 Data Representation)

Dit protocol, ontwikkeld door Sun MicroSystems, specificeert een standaard, machine-onafhankelijke gegevensweergave.

RPC: (Remote Procedure Call)

Ook gedefinieerd door Sun; dit 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 machine in staat om het bestandssysteem van een andere machine te „zien”. Het is gebaseerd op het voorgaande protocol RPC.

8.1.9. Conclusie

In deze inleiding hebben we enkele hoofdlijnen van de internetprotocollen uiteengezet. Voor wie zich verder in dit onderwerp wil verdiepen, is het uitstekende boek van Douglas Comer een aanrader:

Titel: TCP/IP: Architectuur, protocollen, toepassingen.

Auteur: Douglas COMER

Uitgever: InterEditions

8.2. Beheer van netwerkadressen in Java

8.2.1. Definitie

Elke computer op het internet wordt geïdentificeerd door een uniek adres of een unieke naam. Deze twee entiteiten worden in Java beheerd door de klasse InetAddress, waarvan hier enkele methoden worden weergegeven:

byte [] getAddress()
geeft de 4 bytes van het adres IP van de huidige instantie InetAddress
String getHostAddress()
geeft het adres IP van de huidige instantie InetAddress
String getHostName()
geeft de internetnaam van de huidige instantie InetAddress
String toString()
geeft de adresidentiteit IP/internetnaam van de huidige instantie InetAddress
InetAddress getByName(String Host)
maakt de instantie InetAddress aan van de machine die wordt aangeduid door Host. Genereert een uitzondering als Host onbekend is. Host kan de internetnaam van een machine zijn of het adres IP in de vorm I1.I2.I3.I4
InetAddress getLocalHost()
maakt de instantie InetAddress aan van de machine waarop het programma met deze instructie wordt uitgevoerd.

8.2.2. Enkele voorbeelden

8.2.2.1. De lokale machine identificeren


import java.net.*;

public class localhost{
  public static void main (String arg[]){
    try{
      InetAddress adresse=InetAddress.getLocalHost();
    byte[] IP=adresse.getAddress();
    System.out.print("IP=");
    int i;
    for(i=0;i<IP.length-1;i++) System.out.print(IP[i]+".");
    System.out.println(IP[i]);
      System.out.println("adresse="+adresse.getHostAddress());
    System.out.println("nom="+adresse.getHostName());
    System.out.println("identité="+adresse);
    } catch (UnknownHostException e){
      System.out.println ("Erreur getLocalHost : "+e);
    }// einde try
  }// einde main
}// einde class

De resultaten van de uitvoering zijn als volgt:

IP=127.0.0.1
adresse=127.0.0.1
nom=tahe
identité=tahe/127.0.0.1

Elke machine heeft een intern adres IP, namelijk 127.0.0.1. Wanneer een programma dit netwerkadres gebruikt, maakt het gebruik van de machine waarop het draait. Het voordeel van dit adres is dat er geen netwerkkaart voor nodig is. Men kan dus netwerkprogramma's testen zonder verbonden te zijn met een netwerk. Een andere manier om de lokale machine aan te duiden is door de naam „localhost“ te gebruiken.

8.2.2.2. Een willekeurige computer identificeren


import java.net.*;

public class getbyname{
  public static void main (String arg[]){
    String nomMachine;
    // het argument wordt opgehaald
    if(arg.length==0) 
      nomMachine="localhost";
    else nomMachine=arg[0];
    // we proberen het adres van de machine te verkrijgen
    try{
      InetAddress adresse=InetAddress.getByName(nomMachine);
      System.out.println("IP : "+  adresse.getHostAddress());
      System.out.println("nom : "+ adresse.getHostName());
      System.out.println("identité : "+ adresse);
    } catch (UnknownHostException e){
      System.out.println ("Erreur getByName : "+e);
    }// einde try
  }// einde main
}// einde class

Met de Java-aanroep **getbyname** krijgt men de volgende resultaten:

IP : 127.0.0.1
nom : localhost
identité : localhost/127.0.0.1

Met de Java-aanroep **getbyname shiva.istia.univ-angers.fr** krijgt men:

IP : 193.52.43.5
nom : shiva.istia.univ-angers.fr
identité : shiva.istia.univ-angers.fr/193.52.43.5

Met de Java-aanroep getbyname www.ibm.com krijgt men:

IP : 204.146.18.33
nom : www.ibm.com
identité : www.ibm.com/204.146.18.33

8.3. Communicaties TCP-IP

8.3.1. Algemeen

Image

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 gesprek wordt via de telefoon overgebracht. De spraak wordt door telefoon A in de vorm van signalen gecodeerd, via telefoonlijnen overgebracht en komt bij telefoon B aan om daar te worden gedecodeerd. Persoon B hoort dan gesproken woorden. Hier komt het begrip dialoogprotocol om de hoek kijken: als A Frans spreekt en B deze taal niet begrijpt, kunnen A en B geen zinvolle dialoog voeren.

Daarom moeten de twee communicerende applicaties het eens zijn over het type dialoog dat ze gaan hanteren. Zo is de dialoog met een dienst ftp bijvoorbeeld niet dezelfde als met een dienst pop: deze twee diensten accepteren niet dezelfde commando’s. Ze hebben een verschillend communicatieprotocol.

8.3.2. De kenmerken van het protocol TCP

We zullen hier alleen netwerkcommunicatie behandelen die gebruikmaakt van het transportprotocol TCP. Laten we hier nogmaals de kenmerken van dit protocol 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 TCP-segment 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, is genummerd. 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 vanaf dat punt de verzending van de segmenten hervatten.

8.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 accepteert, kan machine A zijn verzoeken naar de dienst SB1 sturen. Deze verzoeken moeten voldoen aan het communicatieprotocol dat door de dienst SB1 wordt begrepen. 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.

8.3.4. Architectuur van een client

De architectuur van een netwerkprogramma dat gebruikmaakt van de diensten van een servertoepassing 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

8.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

8.3.6. De Socket-klasse

8.3.6.1. Définition

De basisfunctie die wordt gebruikt door programma's die via het internet communiceren, is de socket. Dit Engelse woord betekent 'stopcontact'. Het wordt hier uitgebreid tot 'netwerkaansluiting'. Om informatie via het internet te kunnen verzenden en ontvangen, heeft een applicatie een netwerkaansluiting nodig, een socket. Dit hulpmiddel is oorspronkelijk ontwikkeld in de Unix-versies van de Universiteit van Berkeley. Het is sindsdien overgezet naar alle Unix-systemen en naar de Windows-wereld. Het bestaat ook op Java-virtuele machines in twee vormen: de klasse Socket voor clienttoepassingen en de klasse ServerSocket voor servertoepassingen. Hier lichten we enkele constructors en methoden van de klasse Socket toe:

public Socket(String host, int port)
opent een externe verbinding met poort port van de machine host
public int getLocalPort()
geeft het nummer van de lokale poort weer die door de socket wordt gebruikt
  
public int getPort()
geeft het nummer van de externe poort weer waarmee de socket is verbonden
  
public InetAddress getLocalAdress()
geeft het lokale adres weer waaraan de socket is gekoppeld
  
public InetAddress getInetAdress()
geeft het adres InetAddress van de externe verbinding weer waaraan de socket is gekoppeld
  
public InputStream getInputStream()
maakt een invoerstroom aan waarmee de door de externe partner verzonden gegevens kunnen worden gelezen
  
public OutputStream getOutputStream()
levert een uitvoerstroom waarmee gegevens naar de externe partner kunnen worden verzonden
  
public void shutdownInput()
sluit de invoerstroom van de socket
  
public void shutdownOutput()
sluit de uitgaande stream van de socket
  
public void close()
sluit de socket en de bijbehorende I/O-stromen
  
public String toString()
geeft een tekenreeks terug die de socket "vertegenwoordigt"
 

8.3.6.2. Een verbinding openen met een server

We hebben gezien dat machine A twee gegevens nodig heeft om een verbinding te openen met een service op machine B:

  • het adres IP of de naam van machine B
  • het poortnummer waarop de gewenste dienst actief is

De constructor

    public Socket(String  host, int  port);

maakt een socket aan en verbindt deze met machine host op poort port. Deze constructor genereert in verschillende gevallen een uitzondering:

  • verkeerd adres
  • verkeerde poort
  • verzoek afgewezen

We moeten deze uitzondering afhandelen:


    Socket  sClient=null;
    try{
        sClient=new Socket(host,port);
    } catch(Exception e){
        // de verbinding is mislukt - de fout wordt afgehandeld
        ….
    }

Als de verbindingsaanvraag slaagt, krijgt de client lokaal een poort toegewezen om met machine B te communiceren. Zodra de verbinding tot stand is gebracht, kan deze poort worden achterhaald met de methode:

public int getLocalPort();

Als de verbinding tot stand komt, hebben we gezien dat de server de dienst laat verzorgen door een andere taak die op een zogenaamde servicepoort draait. Dit poortnummer kan worden achterhaald met de methode:

public int getPort();

8.3.6.3. Informatie via het netwerk verzenden

Met de volgende methode kan een schrijfstroom naar de socket en dus naar het netwerk worden verkregen:

public OutputStream getOutputStream();

Alles wat via deze stream wordt verzonden, wordt ontvangen op de servicepoort van de server. Veel applicaties hebben een dialoog in de vorm van tekstregels die eindigen met een regeleinde. Daarom is de methode println in dergelijke gevallen erg handig. We zetten de uitgangsstroom OutputStream dan om in de stroom PrintWriter, die de methode println heeft. Het schrijven kan een uitzondering genereren.

8.3.6.4. Informatie van het netwerk lezen

Je kunt een leesstroom van de informatie die binnenkomt via de socket verkrijgen met de methode:

public InputStream getInputStream();

Alles wat in deze stream wordt gelezen, is afkomstig van de servicepoort van de server. Voor toepassingen met een dialoog in de vorm van tekstregels die eindigen met een regeleinde, is het raadzaam de methode readLine te gebruiken. Hiervoor zetten we de invoerstroom InputStream om in de stroom BufferedReader, die de methode readLine() bevat. Het lezen kan een uitzondering genereren.

8.3.6.5. De verbinding afsluiten

Dit gebeurt met de methode:

public void close();

De methode kan een uitzondering genereren. De gebruikte bronnen, met name de netwerkpoort, worden vrijgegeven.

8.3.6.6. De architectuur van de client

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


    Socket  sClient=null;
    try{
            // er wordt verbinding gemaakt met de dienst die draait op poort P van machine M
        sClient=new Socket(M,P);

        // de in- en uitgangsstromen van de clientsocket worden aangemaakt
        BufferedReader in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
        PrintWriter out=new PrintWriter(sClient.getOutputStream(),true);

        // verzoek-antwoordlus
        boolean  fini=false;
        String demande;
        String réponse;
        while (! fini){
            // het verzoek wordt voorbereid
            demande=…
            // het verzoek wordt verzonden
            out.println(demande);
            // het antwoord wordt gelezen
            réponse=in.readLine();
            // het antwoord wordt verwerkt

        }
        // het is klaar
        sClient.close();
    } catch(Exception e){
        // de uitzondering wordt afgehandeld
        ….
    }

Om het voorbeeld niet te ingewikkeld te maken, hebben we geen poging gedaan om de verschillende soorten uitzonderingen af te handelen die worden gegenereerd door de constructor Socket of de methoden readline, getInputStream, getOutputStream en close. Alles is samengevoegd tot één enkele uitzondering.

8.3.7. De klasse ServerSocket

8.3.7.1. Définition

Deze klasse is bedoeld voor het beheer van sockets aan de serverzijde. Hier lichten we enkele constructors en methoden van deze klasse toe:

public ServerSocket(int port)
maakt een luistersocket aan op poort port
public ServerSocket(int port, int count)
hetzelfde, maar stelt in count de grootte van de wachtrij in, c.a.d. het maximale aantal clientverbindingen dat in de wachtrij wordt geplaatst als de server bezet is wanneer de clientverbinding binnenkomt.
public int getLocalPort()
geeft het nummer van de luisterpoort weer die door de socket wordt gebruikt
public InetAddress getInetAdress()
geeft het lokale adres InetAddress weer waaraan de socket is gekoppeld
public Socket accept()
zet de server in de wachtstand voor een verbinding (blokkerende bewerking). Bij het tot stand komen van een verbinding met een client, retourneert het een socket waarmee de client wordt bediend.
public void close()
sluit de socket en de bijbehorende I/O-stromen
public String toString()
retourneert een tekenreeks die de socket „vertegenwoordigt“
public void close()
sluit de servicesocket en maakt de bijbehorende bronnen vrij

8.3.7.2. De service openen

Dit gebeurt met de volgende twee constructors:

public ServerSocket(int  port);    
public ServerSocket(int  port, int  count);

port is de luisterpoort van de service: de poort waar clients hun verbindingsverzoeken naar sturen. count is de maximale grootte van de wachtrij van de service (standaard 50), waarin de verbindingsverzoeken van clients worden opgeslagen waarop de server nog niet heeft gereageerd. Wanneer de wachtrij vol is, worden binnenkomende verbindingsverzoeken afgewezen. Beide constructies genereren een uitzondering.

8.3.7.3. Een verbindingsverzoek accepteren

Wanneer een client een verbindingsverzoek indient op de luisterpoort van de service, accepteert deze het verzoek met de methode:

    public Socket accept();

Deze methode retourneert een instantie van Socket: dit is de servicesocket, via welke de service wordt geleverd, meestal door een andere taak. De methode kan een uitzondering genereren.

8.3.7.4. Lezen/schrijven via de servicesocket

Aangezien de servicesocket een instantie is van de klasse Socket, verwijzen we naar de voorgaande paragrafen waarin dit onderwerp is behandeld.

8.3.7.5. De klant identificeren

Zodra de servicesocket is verkregen, kan de klant worden geïdentificeerd met de methode

    public InetAddress getInetAddress()

uit de klasse Socket. Hiermee krijgt men toegang tot het adres IP en de naam van de klant.

8.3.7.6. De service afsluiten

Dit gebeurt met de methode

    public void close();

van de klasse ServerSocket. Hierdoor worden de bezette bronnen vrijgegeven, met name de luisterpoort. De methode kan een uitzondering genereren.

8.3.7.7. Basisstructuur van een server

Op basis van het bovenstaande kunnen we de basisstructuur van een server als volgt weergeven:


SocketServer sEcoute=null;
try{
    // de service wordt geopend
    int portEcoute=…
    int maxConnexions=…
    sEcoute=new ServerSocket(portEcoute,maxConnexions);

    // verwerking van inlogverzoeken
    boolean fini=false;
    Socket sService=null;
    while( ! fini){
        // in behandeling nemen en goedkeuren van een aanvraag
        sService=sEcoute.accept();

        // de dienst wordt uitgevoerd door een andere taak waaraan de dienstsocket wordt doorgegeven
        new Service(sService).start();

        // er wordt opnieuw gewacht op verbindingsverzoeken
    }
    // het is klaar – de service wordt afgesloten
    sEcoute.close();
} catch (Exception e){
    // de uitzondering wordt afgehandeld

}

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


public class Service extends Thread{

    Socket sService;        // de servicesocket

    // constructor
    public Service(Socket S){
        sService=S;
    }

// uitvoeren
public void run(){
    try{
        // de invoer- en uitvoerstromen worden aangemaakt
    BufferedReader in=new BufferedReader(new InputStreamReader(sService.getInputStream()));
    PrinttWriter out=new PrintWriter(sService.getOutputStream(),true);

    // verzoek-antwoordlus
    boolean  fini=false;
    String demande;
    String réponse;
    while (! fini){
        // het verzoek wordt gelezen
        demande=in.readLine();

        // de aanvraag wordt verwerkt 


        // het antwoord wordt voorbereid
        réponse=…

        // het antwoord wordt verzonden
        out.println(réponse);
    }
    // het is voltooid
    sService.close();
    } catch(Exception e){
    // de uitzondering wordt afgehandeld
    ….
    }// try
} // uitvoeren

8.4. Toepassingen

8.4.1. Echo-server

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

    java serveurEcho port

De server draait op de poort die als parameter wordt doorgegeven. Hij stuurt de client gewoon het verzoek terug dat deze hem heeft gestuurd, samen met zijn identiteit (IP+naam). Hij accepteert 2 verbindingen in zijn wachtrij. Hiermee hebben we alle onderdelen van een TCP-server. Het programma ziet er als volgt uit:

// aanroep: serveurEcho poort
// echo-server
// stuurt de regel die de client heeft verzonden terug naar de client


import java.net.*;
import java.io.*;

public class serveurEcho{
    public final static String syntaxe="Syntaxe : serveurEcho port";
    public final static int nbConnexions=2;

    // hoofdprogramma
    public static void main (String arg[]){

     // is er een argument
     if(arg.length != 1)
        erreur(syntaxe,1);

     // dit argument moet een geheel getal >0 zijn
     int port=0;
     boolean erreurPort=false;
     Exception E=null;
     try{
        port=Integer.parseInt(arg[0]);
     }catch(Exception e){
            E=e;
            erreurPort=true;
     }
     erreurPort=erreurPort || port <=0;
     if(erreurPort)
        erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

     // de luistersocket wordt aangemaakt
     ServerSocket ecoute=null;
     try{
        ecoute=new ServerSocket(port,nbConnexions);
     } catch (Exception e){
        erreur("Erreur lors de la création de la socket d'écoute ("+e+")",3);
     }

     // vervolg
     System.out.println("Serveur d'écho lancé sur le port " + port);

     // servicelus
     boolean serviceFini=false;
     Socket service=null;
     while (! serviceFini){
         // wachten op een client
        try{
            service=ecoute.accept();
        } catch (IOException e){
                erreur("Erreur lors de l'acceptation d'une connexion ("+e+")",4);
        }

         // de verbinding wordt geïdentificeerd
        try{
            System.out.println("Client ["+identifie(service.getInetAddress())+","+
            service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
            + "," + service.getLocalPort() + "]");
        } catch (Exception e) {
            erreur("identification liaison",1);
        }


         // de service wordt verzorgd door een andere taak
        new traiteClientEcho(service).start();
     }// einde while
    }// einde main

// fouten weergeven
    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }

    // identificeert
    private static String identifie(InetAddress Host){
        // hostidentificatie
        String ipHost=Host.getHostAddress();
        String nomHost=Host.getHostName();
        String idHost;
        if (nomHost == null) idHost=ipHost;
            else idHost=ipHost+","+nomHost;
        return idHost;
    }

}// einde class


// verzorgt de service voor een klant van de echo-server

class traiteClientEcho extends Thread{

    private Socket service;            // servicesocket
    private BufferedReader in;        // invoerstroom
    private PrintWriter out;            // uitgangssignaal

     // constructor
    public traiteClientEcho(Socket service){
        this.service=service;
    }

     // run-methode
    public void run(){

         // aanmaken van invoer- en uitvoerstromen
        try{
            in=new BufferedReader(new InputStreamReader(service.getInputStream()));
        } catch (IOException e){
                erreur("Erreur lors de la création du flux déentrée de la socket de service ("+e+")",1);
        }// einde try
        try{
            out=new PrintWriter(service.getOutputStream(),true);
        } catch (IOException e){
                erreur("Erreur lors de la création du flux de sortie de la socket de service ("+e+")",1);
        }// einde try

         // de identificatie van de verbinding wordt naar de client verzonden
        try{
            out.println("Client ["+identifie(service.getInetAddress())+","+
            service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
            + "," + service.getLocalPort() + "]");
        } catch (Exception e) {
            erreur("identification liaison",1);
        }

         // lus voor het lezen van verzoeken en het schrijven van antwoorden
        String demande,reponse;
        try{
             // de service stopt wanneer de client een einde-van-bestand-markering verstuurt
            while ((demande=in.readLine())!=null){
                // echo van het verzoek
                reponse="["+demande+"]";
                out.println(reponse);
                 // de service stopt wanneer de client "einde" verstuurt
                if(demande.trim().toLowerCase().equals("fin")) break;
            }// einde while
        } catch (IOException e){
                erreur("Erreur lors des échanges client/serveur ("+e+")",3);
        }// einde try

         // de socket wordt gesloten
        try{
            service.close();
        } catch (IOException e){
            erreur("Erreur lors de la fermeture de la socket de service ("+e+")",2);
        }// einde try
    }// einde run

     // fouten weergeven
    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }// einde fout

     // identificatie
    private String identifie(InetAddress Host){
         // hostidentificatie
        String ipHost=Host.getHostAddress();
        String nomHost=Host.getHostName();
        String idHost;
        if (nomHost == null) idHost=ipHost;
            else idHost=ipHost+","+nomHost;
        return idHost;
    }

}// einde klasse

De twee klassen die nodig zijn voor de service zijn in één bronbestand samengebracht. Slechts één ervan, namelijk die met de functie main, heeft het attribuut public. De structuur van de server komt overeen met de algemene architectuur van TCP-servers. Er is een methode (identifie) toegevoegd waarmee de verbinding tussen de server en een client kan worden geïdentificeerd. Hier volgen enkele resultaten:

De server wordt gestart met het commando

    java serveurEcho 187

Vervolgens geeft hij in het controlevenster het volgende bericht weer:

Serveur d'écho lancé sur le port 187

Om deze server te testen, gebruiken we het programma telnet, dat zowel onder Unix als onder Windows beschikbaar is. Telnet is een universele TCP-client die geschikt is voor alle servers die in hun communicatie tekstregels accepteren die eindigen met een regeleinde-teken. Dat is het geval bij onze echo-server. We starten een eerste telnet-client onder Windows (in dit voorbeeld Windows 2000) door telnet in te typen in een DOS-venster:


DOS>telnet
Microsoft (R) Windows 2000 (TM) version 5.00 (numéro 2195)
Client Telnet Microsoft
Client Telnet numéro 5.00.99203.1

Le caractère d'échappement est 'CTRL+$'

Microsoft Telnet> help

Les commandes peuvent être abrégées. Les commandes prises en charge sont :

close           ferme la connexion en cours
display         affiche les paramètres d'opération
open            ouvre une connexion à un site
quit            quitte telnet
set             définit les options (entrez 'set ?' pour afficher la liste)
status          affiche les informations d'état
unset           annule les options (entrez 'unset ?' pour afficher la liste)
? ou help       affiche des informations d'aide

Microsoft Telnet> set ?
NTLM            Active l'authentification NTLM.
LOCAL_ECHO      Active l'écho local.
TERM x          (où x est ANSI, VT100, VT52 ou VTNT))
CRLF            Envoi de CR et de LF

Microsoft Telnet> set local_echo

Microsoft Telnet> open localhost 187

Het programma telnet geeft standaard geen echo weer van de opdrachten die via het toetsenbord worden ingevoerd. Om deze echo te krijgen, voert u de volgende opdracht uit:

Microsoft Telnet> set local_echo

Om een verbinding met de server te openen, waarbij de poort van de echo-service (187) en het adres van de machine waarop deze zich bevindt (localhost) worden opgegeven, voer je het volgende commando uit:

Microsoft Telnet> open localhost 187

In het DOS-venster van de client verschijnt dan het volgende bericht:

Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]

In het servervenster verschijnt het bericht:

Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]

Hier verwijzen tahe en localhost naar dezelfde machine. In het clientvenster telnet kun je tekstregels invoeren. De server geeft deze weer:

Client [127.0.0.1,tahe,1059] connectÚ au serveur [127.0.0.1,tahe,187]
je suis là
[je suis là]
au revoir
[au revoir]

Merk op dat de poort van de client (1059) correct wordt gedetecteerd, maar dat de servicepoort (187) identiek is aan de luisterpoort (187), wat onverwacht is. Men zou namelijk verwachten dat de poort van de servicesocket wordt verkregen en niet de luisterpoort. Er zou moeten worden gecontroleerd of men onder Unix dezelfde resultaten krijgt. Laten we nu een tweede client starten: telnet. Het servervenster ziet er dan als volgt uit:

Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]
Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]

In het venster van de tweede client kun je ook tekstregels invoeren:

Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]
ligne1
[ligne1]
ligne2
[ligne2]

Zo zien we dat de echo-server meerdere clients tegelijk kan bedienen. De clients telnet kunnen worden beëindigd door het DOS-venster te sluiten waarin ze worden uitgevoerd.

8.4.2. Een Java-client voor de echo-server

In het vorige deel hebben we een telnet-client gebruikt om de echo-service te testen. Nu gaan we onze eigen client schrijven:

// aanroep: clientEcho machinepoort
// client van de echo-server
// stuurt regels naar de server, die deze terugkaatst

import java.net.*;
import java.io.*;

public class clientEcho{
    public final static String syntaxe="Syntaxe : clientEcho machine port";

    // hoofdprogramma    
    public static void main (String arg[]){

     // zijn er twee argumenten
     if(arg.length != 2)
        erreur(syntaxe,1);

     // het eerste argument moet de naam van een bestaande machine zijn
    String machine=arg[0];
    InetAddress serveurAddress=null;
    try{
        serveurAddress=InetAddress.getByName(machine);
    } catch (Exception e){
        erreur(syntaxe+"\nMachine "+machine+" inaccessible (" + e +")",2);
    }

     // de poort moet een geheel getal >0 zijn
     int port=0;
     boolean erreurPort=false;
     Exception E=null;
     try{
        port=Integer.parseInt(arg[1]);
     }catch(Exception e){
            E=e;
            erreurPort=true;
     }
     erreurPort=erreurPort || port <=0;
     if(erreurPort)
        erreur(syntaxe+"\nPort incorrect ("+E+")",3);

     // er wordt verbinding gemaakt met de server
     Socket sClient=null;
     try{
        sClient=new Socket(machine,port);
     } catch (Exception e){
        erreur("Erreur lors de la création de la socket de communication ("+e+")",4);
     }

     // de verbinding wordt geïdentificeerd
    try{
        System.out.println("Client : Client ["+identifie(InetAddress.getLocalHost())+","+
        sClient.getLocalPort()+"] connecté au serveur [" + identifie (sClient.getInetAddress())
        + "," + sClient.getPort() + "]");
    } catch (Exception e) {
        erreur("identification liaison ("+e+")",5);
    }

     // de leesstroom voor de toetsaanslagen wordt aangemaakt
    BufferedReader IN=null;
    try{
        IN=new BufferedReader(new InputStreamReader(System.in));
    } catch (Exception e){
        erreur("Création du flux d'entrée clavier ("+e+")",6);
    }
     // de invoerstroom voor de clientsocket wordt aangemaakt
    BufferedReader in=null;
    try{
        in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
    } catch (Exception e){
        erreur("Création du flux d'entrée de la socket client("+e+")",7);
    }
     // de uitvoerstroom aanmaken die bij de clientsocket hoort
    PrintWriter out=null;
    try{
        out=new PrintWriter(sClient.getOutputStream(),true);
    } catch (Exception e){
        erreur("Création du flux de sortie de la socket ("+e+")",8);
    }

     // lus van verzoeken en antwoorden
    boolean serviceFini=false;
    String demande=null;
    String reponse=null;

    // het door de server verzonden bericht wordt direct na het tot stand brengen van de verbinding gelezen 
    try{
        reponse=in.readLine();
    } catch (IOException e){
            erreur("Lecture réponse ("+e+")",4);
    }        

     // weergave van het antwoord
    System.out.println("Serveur : " +reponse);

    while (! serviceFini){
         // een via het toetsenbord ingevoerde regel lezen
        System.out.print("Client : ");
        try{
            demande=IN.readLine();
        } catch (Exception e){
            erreur("Lecture ligne ("+e+")",9);
        }
         // verzoek verzenden via het netwerk
        try{
            out.println(demande);
        } catch (Exception e){
            erreur("Envoi demande ("+e+")",10);
        }
         // wachten op/lezen van het antwoord
        try{
            reponse=in.readLine();
        } catch (IOException e){
                erreur("Lecture réponse ("+e+")",4);
        }
         // antwoord weergeven
        System.out.println("Serveur : " +reponse);
         // is het klaar?
        if(demande.trim().toLowerCase().equals("fin")) serviceFini=true;
    }
     // het is klaar
    try{
        sClient.close();
    } catch(Exception e){
        erreur("Fermeture socket ("+e+")",11);
    }
}// hand

// fouten weergeven
    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }

    // identificeren
    private static String identifie(InetAddress Host){
        // host-ID
        String ipHost=Host.getHostAddress();
        String nomHost=Host.getHostName();
        String idHost;
        if (nomHost == null) idHost=ipHost;
            else idHost=ipHost+","+nomHost;
        return idHost;
    }

}// einde klasse

De structuur van deze client komt overeen met de algemene architectuur van de tcp-clients. Hier zijn de verschillende mogelijke uitzonderingen één voor één afgehandeld, wat het programma zwaarder maakt. Dit zijn de resultaten die worden verkregen bij het testen van deze client:

Client : Client [127.0.0.1,tahe,1045] connecté au serveur [127.0.0.1,localhost,187]
Serveur : Client [127.0.0.1,localhost,1045] connectÚ au serveur [127.0.0.1,tahe,187]
Client : 123
Serveur : [123]
Client : abcd
Serveur : [abcd]
Client : je suis là
Serveur : [je suis là]
Client : fin
Serveur : [fin]

De regels die beginnen met Client zijn de regels die door de client zijn verzonden en de regels die beginnen met Serveur zijn de regels die door de server zijn teruggestuurd.

8.4.3. Een generieke TCP-client

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: java cltTCPgenerique server poort

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

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

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

  • 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>java clientTCPgenerique 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 maakt:
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
  • sommige services hebben een commando help dat informatie geeft over de commando's die met de service kunnen worden gebruikt. Hier is dat niet het geval. De commando's SMTP die in het voorbeeld worden gebruikt, zijn de volgende:
    • mail from: expéditeur, om het e-mailadres van de afzender van het bericht aan te geven
    • rcpt to: destinataire, om het e-mailadres van de ontvanger van het bericht aan te geven. Als er meerdere ontvangers zijn, wordt het commando rcpt to: zo vaak als nodig is herhaald voor elke ontvanger.
    • data, waarmee aan de server SMTP wordt aangegeven dat het bericht wordt verzonden. Zoals aangegeven in het antwoord van de server, bestaat dit bericht 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 netwerkverbinding. De leesthread kan deze gebeurtenis detecteren en stoppen.
  • De gebruiker typt vervolgens ‘fin’ op het toetsenbord om ook de thread die de ingevoerde opdrachten leest, te stoppen.

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

Image

Merk op dat de dienst SMTP niet kan vaststellen of een afzender geldig is of niet. Daarom kun je het veld from in 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. Deze dienst werkt op poort 110.


Dos> java clientTCPgenerique 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
  • wachtwoord password, waarbij je het wachtwoord opgeeft dat bij de vorige login 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> java clientTCPgenerique istia.univ-angers.fr 80
Commandes :
GET /index.html HTTP/1.0

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

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

commande1
commande2
...
commanden
[ligne vide]

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

GET /index.html HTTP/1.0

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

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

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

<-- HTTP/1.1 200 OK

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

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

geven de client door dat hij 11251 bytes zal ontvangen die de tekst HTML (HyperText Markup Language) vertegenwoordigen en dat de verbinding aan het einde van de verzending zal worden verbroken.

Dit is dus een erg handige TCP-client. Hij doet ongetwijfeld minder dan het programma telnet dat we eerder hebben gebruikt, maar het was interessant om hem zelf te schrijven. Het programma van de generieke TCP-client is als volgt:

// geïmporteerde pakketten
import java.io.*;
import java.net.*;

public class clientTCPgenerique{

    // ontvangt als parameter de kenmerken van een service in de vorm van
     // serverpoort
     // maakt verbinding met de service
     // 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 beëindigd met het commando 'fin' dat via het toetsenbord wordt ingevoerd

   // instantievariabele
  private static Socket client;

    public static void main(String[] args){

         // syntaxis
        final String syntaxe="pg serveur port";

         // aantal argumenten
        if(args.length != 2)
            erreur(syntaxe,1);

         // de naam van de server wordt genoteerd
        String serveur=args[0];

         // de poort moet een geheel getal >0 zijn
        int port=0;
        boolean erreurPort=false;
        Exception E=null;
        try{
            port=Integer.parseInt(args[1]);
        }catch(Exception e){
            E=e;
            erreurPort=true;
        }
        erreurPort=erreurPort || port <=0;
        if(erreurPort)
            erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

        client=null;
         // er kunnen problemen optreden
        try{
             // er wordt verbinding gemaakt met de dienst
            client=new Socket(serveur,port);
        }catch(Exception ex){
             // fout
            erreur("Impossible de se connecter au service ("+ serveur
                +","+port+"), erreur : "+ex.getMessage(),3);
             // einde
            return;
        }//catch

         // de lees- en schrijfthreads worden aangemaakt
    new ClientSend(client).start();
    new ClientReceive(client).start();

        // einde hoofdthread
        return;
    }// main

     // foutmeldingen weergeven
    public static void erreur(String msg, int exitCode){
         // fout weergeven
        System.err.println(msg);
         // afsluiten met fout
        System.exit(exitCode);
    }//fout
}//klasse  

class ClientSend extends Thread {
    // klasse die verantwoordelijk is voor het lezen van via het toetsenbord ingevoerde opdrachten
     // en deze via een als parameter doorgegeven TCP-client naar een server te verzenden

    private Socket client;    // de TCP-client

     // constructor
    public ClientSend(Socket client){
         // hier wordt de TCP-client vastgelegd
        this.client=client;
    }//constructor

     // de Run-methode van de thread
    public void run(){

        // lokale gegevens
        PrintWriter OUT=null;            // netwerk-schrijfstroom
    BufferedReader IN=null;        // toetsenbordstroom
        String commande=null;            // op het toetsenbord ingevoerde opdracht

         // foutbeheer
        try{
             // aanmaken van de netwerkschrijfstroom
            OUT=new PrintWriter(client.getOutputStream(),true);
      // aanmaken van de toetsenbordinvoerstroom
      IN=new BufferedReader(new InputStreamReader(System.in));
            // lus voor invoer en verzending van opdrachten
            System.out.println("Commandes : ");
            while(true){
                 // lezen van via het toetsenbord ingevoerde opdracht
                commande=IN.readLine().trim();
                // klaar?
                if (commande.toLowerCase().equals("fin")) break;
                // commando naar de server verzenden
                OUT.println(commande);
                 // volgend commando
            }//while
        }catch(Exception ex){
             // fout
            System.err.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // einde - de streams worden gesloten
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
         // het einde van de thread wordt gemeld
        System.out.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
    }//run
}//klasse

class ClientReceive extends Thread{
    // klasse die verantwoordelijk is voor het lezen van de tekstregels bestemd voor een 
     // TCP-client die als parameter is doorgegeven

    private Socket client;    // de TCP-client

     // constructor
    public ClientReceive(Socket client){
         // de TCP-client wordt geregistreerd
        this.client=client;
    }//constructor

     // Run-methode van de thread
    public void run(){

        // lokale gegevens
        BufferedReader IN=null;        // netwerkleesstroom
        String réponse=null;        // serverantwoord

         // foutbeheer
        try{
             // aanmaken van de netwerkleesstroom
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // lus voor het lezen van tekstregels uit de stream IN
            while(true){
                 // netwerkstream lezen
                réponse=IN.readLine();
                 // stream gesloten?
                if(réponse==null) break;
                // weergave
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // fout
            System.err.println("Réception : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // einde - de streams worden gesloten
        try{
            IN.close();client.close();
        }catch(Exception ex){}
         // het einde van de thread wordt gemeld
        System.out.println("[Réception : fin du thread de lecture des réponses du serveur]");
    }//run
}//klasse

8.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. De gebruiker fungeert dus als server.

Het programma wordt gestart met: java serveurTCPgenerique 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 client 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:

E:\data\serge\MSNET\c#\netwerk\generieke tcp-client> java clientTCPgenerique 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> java serveurTCPgenerique 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> java clientTCPgenerique 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> java serveurTCPgenerique 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- commande 3 du client 2
réponse 3 au client 2
2 : [fin du Thread de lecture des demandes du client 2]
fin
[fin du Thread de lecture des réponses du serveur au client 2]
^C

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


Dos> java serveurTCPgenerique 88
Serveur générique lancé sur le port 88

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

Image

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

Dos>java serveurTCPgenerique 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 verzond: GET. Dat was toen voldoende. We zien hier 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 het vervolgens weergeven:

Image

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

Image

dat wil zeggen precies wat we vanaf de generieke server hebben verzonden.

De code van de generieke TCP-server is als volgt:

// pakketten
import java.io.*;
import java.net.*;

public class serveurTCPgenerique{

    // hoofdprogramma
    public static void main (String[] args){

    // ontvangt verzoeken van klanten op de luisterpoort
     // 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

    final String syntaxe="Syntaxe : pg port";
   // instantievariabele
         // is er een argument
     if(args.length != 1)
        erreur(syntaxe,1);

         // de poort moet een geheel getal >0 zijn
        int port=0;
        boolean erreurPort=false;
        Exception E=null;
        try{
            port=Integer.parseInt(args[0]);
        }catch(Exception e){
            E=e;
            erreurPort=true;
        }
        erreurPort=erreurPort || port <=0;
        if(erreurPort)
            erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

     // de luisterdienst wordt aangemaakt
    ServerSocket ecoute=null;
    int nbClients=0;    // aantal verwerkte clients
        try{
             // de service wordt aangemaakt
            ecoute=new ServerSocket(port);
             // opvolging
            System.out.println("Serveur générique lancé sur le port " + port);

             // lus voor klantenservice
            Socket client=null;
            while (true){ // oneindige lus  wordt gestopt met Ctrl-C
                 // wachten op een klant
                client=ecoute.accept();

                 // de service wordt uitgevoerd door afzonderlijke threads
                nbClients++;

                 // de lees-/schrijfthreads worden aangemaakt
        new ServeurSend(client,nbClients).start();
        new ServeurReceive(client,nbClients).start();

                // we gaan weer over tot het afwachten van verzoeken
            }// einde while
        }catch(Exception ex){
             // de fout wordt gemeld
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
        }//catch
    }// einde main

     // fouten weergeven
    public static void erreur(String msg, int exitCode){
         // fout weergeven
        System.err.println(msg);
         // afsluiten met fout
        System.exit(exitCode);
    }//fout
}//klasse

class ServeurSend extends Thread{
    // klasse die verantwoordelijk is voor het lezen van antwoorden die via het toetsenbord worden ingevoerd
     // en deze via een aan de constructor doorgegeven TCP-client naar een client te verzenden

    Socket client;    // de TCP-client
    int numClient;        // clientnummer

     // constructor
    public ServeurSend(Socket client, int numClient){
         // we noteren de TCP-client
        this.client=client;
         // en het nummer ervan
        this.numClient=numClient;
    }//fabrikant

     // Run-methode van de thread
    public void run(){

        // lokale gegevens
        PrintWriter OUT=null;        // netwerk-schrijfstroom
        String réponse=null;        // van het toetsenbord gelezen antwoord
    BufferedReader IN=null;    // toetsenbordstroom

        // tracking
        System.out.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
         // foutbeheer
        try{
             // aanmaken van de netwerkschrijfstroom
            OUT=new PrintWriter(client.getOutputStream(),true);
      // aanmaken van de toetsenbordstroom
      IN=new BufferedReader(new InputStreamReader(System.in));
            // lus voor invoer en verzending van opdrachten
            while(true){
                 // klantidentificatie
                System.out.print("--> " + numClient + " : ");
                 // uitlezen van via het toetsenbord ingevoerde reactie
                réponse=IN.readLine().trim();
                // klaar?
                if (réponse.toLowerCase().equals("fin")) break;
                // antwoord naar de server verzenden
                OUT.println(réponse);
                 // volgend antwoord
            }//while
        }catch(Exception ex){
             // fout
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // einde - de streams worden gesloten
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
         // het einde van de thread wordt gemeld
        System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
    }//run
}//klasse

class ServeurReceive extends Thread{
    // klasse die verantwoordelijk is voor het lezen van de naar de server verzonden tekstregels 
     // via een TCP-client die aan de constructor wordt doorgegeven

    Socket client;    // de TCP-client
    int numClient;        // clientnummer

     // constructor
    public ServeurReceive(Socket client, int numClient){
         // de TCP-client wordt genoteerd
        this.client=client;
         // en het nummer ervan
        this.numClient=numClient;
    }//fabrikant

     // Run-methode van de thread
    public void run(){

        // lokale gegevens
        BufferedReader IN=null;        // netwerkleesstroom
        String réponse=null;        // serverantwoord

         // monitoring
        System.out.println("Thread de lecture des demandes du client "+ numClient + " lancé");
         // foutbeheer
        try{
             // aanmaken van de netwerkleesstroom
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // lus voor het lezen van tekstregels uit de stream IN
            while(true){
                 // netwerkstream lezen
                réponse=IN.readLine();
                 // stream gesloten?
                if(réponse==null) break;
                // weergave
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // fout
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // einde  de streams worden gesloten
        try{
            IN.close();client.close();
        }catch(Exception ex){}
         // het einde van de thread wordt gemeld
        System.out.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
    }//run
}//klasse

8.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 inhoud van deze URL op het scherm weergeeft. We gaan ervan uit dat de webserver die voor de URL wordt benaderd, het protocol HTTP 1.1 ondersteunt. Van de bovenstaande headers gebruiken we alleen de volgende:

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

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

Onze webclient wordt als volgt aangeroepen: java 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>java clientweb http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:23:37 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/
Cache-control: private

Het antwoord

HTTP/1.1 302 Object moved

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

Location: /IISSamples/Default/welcome.htm

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

dos>java 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:

// geïmporteerde pakketten
import java.io.*;
import java.net.*;

public class clientweb{

    // vraagt om een URL
     // geeft de inhoud ervan weer op het scherm

    public static void main(String[] args){
        // syntaxis
        final String syntaxe="pg URI GET/HEAD";

        // aantal argumenten
        if(args.length != 2)
            erreur(syntaxe,1);

         // de gevraagde URI wordt genoteerd
        String URLString=args[0];
        String commande=args[1].toUpperCase();

        // controle van de geldigheid van de URI
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI is onjuist
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//opvang
         // controle van de bestelling
        if(! commande.equals("GET") && ! commande.equals("HEAD")){
            // onjuiste bestelling
            erreur("Le second paramètre doit être GET ou HEAD",3);
        }

         // de relevante gegevens worden uit URL gehaald
    String path=url.getPath();
    if(path.equals("")) path="/";
    String query=url.getQuery();
    if(query!=null) query="?"+query; else query="";
    String host=url.getHost();
    int port=url.getPort();
    if(port==-1) port=url.getDefaultPort();

         // we kunnen aan de slag
        Socket  client=null;                        // de klant
        BufferedReader IN=null;                    // de leesstroom van de klant
        PrintWriter OUT=null;                        // de schrijfstroom van de klant
        String réponse=null;                        // antwoord van de server
        try{
             // er wordt verbinding gemaakt met de server
            client=new Socket(host,port);

            // de invoer- en uitvoerstromen van de klant worden aangemaakt TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // URL wordt opgevraagd - verzending van de headers HTTP
            OUT.println(commande + " " + path + query + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println();
             // het antwoord wordt gelezen
            while((réponse=IN.readLine())!=null){
                 // het antwoord wordt verwerkt
                System.out.println(réponse);
            }//while
             // het is klaar
            client.close();
        } catch(Exception e){
            // de uitzondering wordt afgehandeld
            erreur(e.getMessage(),4);
        }//catch
    }//main

     // foutmeldingen weergeven
    public static void erreur(String msg, int exitCode){
         // fout weergeven
        System.err.println(msg);
         // afsluiten met fout
        System.exit(exitCode);
    }//fout
}//klasse

Het enige nieuwe in dit programma is het gebruik van de klasse URL. 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 URL kunnen we de tekenreeks van de URL opsplitsen in de verschillende onderdelen. Er wordt een object URL aangemaakt op basis van de tekenreeks URLstring die als parameter is ontvangen:

         // geldigheid van de URL controleren
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI onjuist
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//catch

Als de als parameter ontvangen tekenreeks URL geen geldige URL is (ontbrekend protocol, ontbrekende server, ...), wordt er een uitzondering gegenereerd. Zo kunnen we de geldigheid van de ontvangen parameter controleren. Zodra het object URL is aangemaakt, hebben we toegang tot de verschillende elementen ervan. Als het object url uit de vorige code dus is aangemaakt op basis van de tekenreeks

http://server:poort/cheminPageHTML?param1=val1;param2=val2;... 

dan hebben we:

url.getHost()=serveur

url.getPort()=port of -1 als de poort niet is opgegeven

url.getPath()=cheminPageHTML of de lege tekenreeks als er geen pad is

url.getQuery()=param1=val1;param2=val2;... of null als er geen verzoek is

uri.getProtocol()=http

8.4.6. Webclient die omleidingen afhandelt

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

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

Hier volgt een voorbeeld:

Dos>java 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:

// geïmporteerde pakketten
import java.io.*;
import java.net.*;
import java.util.regex.*;

public class clientweb2{

    // vraagt om een URL
     // geeft de inhoud ervan weer op het scherm

    public static void main(String[] args){
        // syntaxis
        final String syntaxe="pg URL GET/HEAD";

        // aantal argumenten
        if(args.length != 2)
            erreur(syntaxe,1);

         // de aangevraagde URI wordt genoteerd
        String URLString=args[0];
        String commande=args[1].toUpperCase();

        // controle op de geldigheid van de URI
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI is onjuist
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//opgevangen
         // controle van de bestelling
        if(! commande.equals("GET") && ! commande.equals("HEAD")){
            // onjuiste bestelling
            erreur("Le second paramètre doit être GET ou HEAD",3);
        }

         // we kunnen aan de slag
        Socket  client=null;                        // de klant
        BufferedReader IN=null;                    // de leesstroom van de klant
        PrintWriter OUT=null;                        // de schrijfstroom van de klant
        String réponse=null;                        // antwoord van de server
        final int nbRedirsMax=1;                // er wordt niet meer dan één omleiding toegestaan
        int nbRedirs=0;                                    // aantal lopende omleidingen
        String premièreLigne;                        // eerste regel van het antwoord
        boolean redir=false;                        // geeft aan of er al dan niet een omleiding is
        String locationString="";                // de tekenreeks URL van een eventuele omleiding

         // reguliere expressie om een omleidingsstring URL te vinden
        Pattern location=Pattern.compile("^Location: (.+?)$");

         // foutafhandeling
        try{
             // er kunnen meerdere URL-strings worden opgevraagd als er omleidingen zijn
            while(nbRedirs<=nbRedirsMax){

                // we halen de nuttige informatie uit de URL
            String protocol=url.getProtocol();
            String path=url.getPath();
            if(path.equals("")) path="/";
            String query=url.getQuery();
            if(query!=null) query="?"+query; else query="";
            String host=url.getHost();
            int port=url.getPort();
            if(port==-1) port=url.getDefaultPort();

                 // er wordt verbinding gemaakt met de server
                client=new Socket(host,port);

                // we maken de in- en uitgaande stromen van de client TCP aan
                IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
                OUT=new PrintWriter(client.getOutputStream(),true);

                // URL wordt opgevraagd - verzending van de headers HTTP
                OUT.println(commande + " " + path + query + " HTTP/1.1");    
                OUT.println("Host: " + host + ":" + port);
                OUT.println("Connection: close");
                OUT.println();

                 // de eerste regel van het antwoord wordt gelezen
                premièreLigne=IN.readLine();
                 // schermuitvoer
                System.out.println(premièreLigne);

                // omleiding?
        if(premièreLigne.endsWith("302 Object moved")){     
                    // er is een omleiding
                    redir=true;
                    nbRedirs++;
                }//if

                // de volgende HTTP-headers totdat de lege regel wordt gevonden die het einde van de headers aangeeft
                boolean locationFound=false;
                while(!(réponse=IN.readLine()).equals("")){
                    // het antwoord wordt weergegeven
                    System.out.println(réponse);
                     // als er een omleiding is, zoek je naar de Location-header
                    if(redir && ! locationFound){
                        // de regel wordt vergeleken met de relationele uitdrukking location
                        Matcher résultat=location.matcher(réponse);
                        if(résultat.find()){
                            // als deze is gevonden, noteer dan de omleidingscode URL
                            locationString=résultat.group(1);             
                             // er wordt genoteerd dat er iets is gevonden
                            locationFound=true;
                        }//if
                    }//als
                    // volgende header
                }//terwijl

                 // de volgende regels van het antwoord
                System.out.println(réponse);
                while((réponse=IN.readLine())!=null){
                     // het antwoord wordt weergegeven
                    System.out.println(réponse);
                }//while
                 // de verbinding wordt verbroken
                client.close();
                 // zijn we klaar?
                if ( ! locationFound || nbRedirs>nbRedirsMax)
                    break;
                 // er moet een omleiding worden uitgevoerd – de nieuwe wordt opgebouwd URL
                URLString=protocol +"://"+host+":"+port+locationString;
                url=new URL(URLString);
                // tracking
                System.out.println("\n<--Redirection vers l'URL "+URLString+"-->\n");
            }//terwijl
        } catch(Exception e){
            // de uitzondering wordt afgehandeld
            erreur(e.getMessage(),4);
        }//catch
    }//main

     // foutmeldingen weergeven
    public static void erreur(String msg, int exitCode){
         // fout weergeven
        System.err.println(msg);
         // afsluiten met fout
        System.exit(exitCode);
    }//fout
}//klasse

8.4.7. Server voor belastingberekening

We gaan verder met de oefening IMPOTS, die al in verschillende vormen is behandeld. Laten we de laatste versie nog eens bekijken:

Er is een basisklasse voor belastingen aangemaakt. De attributen daarvan bestaan uit drie tabellen met getallen:

public class impots{

  // de gegevens die nodig zijn voor de berekening van de belasting
   // zijn afkomstig van een externe bron

  protected double[] limites=null;
  protected double[] coeffR=null;
  protected double[] coeffN=null;

   // veld ‘fabrikant’ leeg
  protected impots(){}

   // constructor
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{

De klasse impots heeft twee constructors:

  • een constructor waaraan de drie gegevensarrays worden doorgegeven die nodig zijn voor de berekening van de belasting
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
  • een constructor zonder parameters die alleen door onderliggende klassen kan worden gebruikt
  protected impots(){}

Van deze klasse is de klasse impotsJDBC afgeleid, waarmee de drie tabellen limites, coeffR en coeffN kunnen worden gevuld op basis van de inhoud van een database:

public class impotsJDBC extends impots{
  // toevoeging van een constructor waarmee kan worden geconstrueerd
   // de tabellen ‘limites’, ‘coeffr’ en ‘coeffn’ op basis van de tabel
   // belastingen uit een database
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: naam DSN van de database
     // userIMPOTS, mdpIMPOTS: gebruikersnaam/wachtwoord voor toegang tot de database

Er was een grafische applicatie geschreven. De applicatie maakte gebruik van een object van de klasse impotsJDBC. De applicatie en dit object bevonden zich op dezelfde machine. We zijn van plan om het testprogramma en het object impotsJDBC op verschillende machines te plaatsen. We krijgen dan een client-server-toepassing waarbij het externe object impotsJDBC als server fungeert. De nieuwe klasse heet ServeurImpots en is afgeleid van de klasse impotsJDBC:

// geïmporteerde pakketten
import java.net.*;
import java.io.*;
import java.sql.*;

public class ServeurImpots extends impotsJDBC {

    // attributen
    int portEcoute;                // de poort waarop wordt geluisterd naar verzoeken van klanten
    boolean actif;                // serverstatus

     // constructor
    public ServeurImpots(int portEcoute,String DSNimpots, String USERimpots, String MDPimpots)
      throws IOException, SQLException, ClassNotFoundException {
       // bovenliggende constructie
        super(DSNimpots, USERimpots, MDPimpots);
         // de luisterpoort wordt genoteerd
        this.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 op basis van deze opdrachten beheerd
        Thread admin=new Thread(){
        public void run(){
          try{
            admin();
        }catch (Exception ignored){}
      }
    };
    admin.start();
    }//ServeurImpots

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 impotsJDBC. De belasting-server wordt aangestuurd door commando's die via het toetsenbord worden ingevoerd. Daarom wordt er een thread aangemaakt om deze commando's te lezen. Er zijn twee mogelijke commando’s: start om de dienst te starten en stop om deze definitief te stoppen. De methode admin die deze commando’s afhandelt, is als volgt:

    public void admin() throws IOException{
        // leest de via het toetsenbord ingevoerde beheercommando's van de server
         // in een eindeloze lus
        String commande=null;
    BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
        while(true){
             // vraagt om invoer
            System.out.print("Serveur d'impôts>");
             // commando lezen
            commande=IN.readLine().trim().toLowerCase();
             // commando uitvoeren
            if(commande.equals("start")){
                // actief?
                if(actif){
                     //fout
                    System.out.println("Le serveur est déjà actif");
                     // we gaan verder
                    continue;
                }//if
                 // de luisterservice wordt aangemaakt en gestart
                Thread ecoute=new Thread(){
                public void run(){
                  ecoute();
              }
            };
            ecoute.start();
            }//if
            else if(commande.equals("stop")){
                // einde van alle uitvoeringsthreads
                System.exit(0);
            }//als
            else {
                // fout
                System.out.println("Commande incorrecte. Utilisez (start,stop)");
            }//if
        }//while
    }//admin

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

    public void ecoute(){
         // thread voor het afhandelen van verzoeken van klanten
         // de luisterservice wordt aangemaakt
        ServerSocket ecoute=null;
        try{
            // de service wordt aangemaakt
            ecoute=new ServerSocket(portEcoute);
             // opvolging
            System.out.println("Serveur d'impôts lancé sur le port " + portEcoute);

             // servicelus
            Socket liaisonClient=null;
            while (true){ // oneindige lus
                 // wachten op een klant
                liaisonClient=ecoute.accept();

                 // de service wordt door een andere taak verzorgd
                new traiteClientImpots(liaisonClient,this).start();

                 // we gaan weer over op het afwachten van verzoeken
            }// einde while
        }catch(Exception ex){
             // de fout wordt gemeld
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
        }//catch
    }//luisterthread

Er is een klassieke TCP-server die luistert op poort portEcoute. Verzoeken van clients worden verwerkt door de methode run van de thread traiteCientImpots, aan de constructor waarvan twee parameters worden doorgegeven:

  1. het Socket-object liaisonClient waarmee de client kan worden bereikt
  2. het object impotsJDBC this, dat toegang geeft tot de methode this.calculer voor het berekenen van de belasting.
// -------------------------------------------------------
// verzorgt de dienstverlening aan een klant van de belastingserver

class traiteClientImpots extends Thread{

    private Socket liaisonClient;            // verbinding met de klant
    private BufferedReader IN;                // invoerstroom
    private PrintWriter OUT;                    // uitgaande stroom
    private impotsJDBC objImpots;            // belastingobject

         // constructor
    public traiteClientImpots(Socket liaisonClient,impotsJDBC objImpots){
        this.liaisonClient=liaisonClient;
        this.objImpots=objImpots;
    }//bouwer

De methode run verwerkt verzoeken van klanten. Dit zijn tekstregels die twee vormen kunnen aannemen:

  1. berekening gehuwd (j/n) nbEnfants salaireAnnuel
  2. fincalculs

Vorm 1 maakt het mogelijk om belasting te berekenen, vorm 2 verbreekt de verbinding tussen client en server.

     // run-methode
    public void run(){
         // levert de dienst aan de klant
        try{
             // invoerstroom
            IN=new BufferedReader(new InputStreamReader(liaisonClient.getInputStream()));
             // uitvoerstroom
            OUT=new PrintWriter(liaisonClient.getOutputStream(),true);
             // een welkomstbericht naar de klant sturen
            OUT.println("Bienvenue sur le serveur d'impôts");

             // lus voor het lezen van verzoeken en het schrijven van antwoorden
            String demande=null;
            String[] champs=null;    // de onderdelen van het verzoek
            String commande=null;    // het commando van de klant: berekening of einde berekeningen
            while ((demande=IN.readLine())!=null){
                 // het verzoek wordt opgesplitst in velden
                champs=demande.trim().toLowerCase().split("\\s+");
                 // twee geaccepteerde verzoeken: berekening en eindberekeningen
                commande=champs[0];
                if(! commande.equals("calcul") && ! commande.equals("fincalculs")){
                     // fout bij de klant
                    OUT.println("Commande incorrecte. Utilisez (calcul,fincalculs).");
                     // volgende bestelling
                    continue;
                }//if
                if(commande.equals("calcul")) calculerImpôt(champs);
                if(commande.equals("fincalculs")){
                     // afscheidsbericht aan de klant
                    OUT.println("Au revoir...");
                     // vrijgeven van bronnen
                    try{ OUT.close();IN.close();liaisonClient.close();}
                    catch(Exception ex){}
                     // einde
                    return;
                }//if
                 //volgende aanvraag
            }//while
        }catch (Exception e){
            erreur("L'erreur suivante s'est produite ("+e+")",2);
        }// einde try
    }// einde Run

De belasting wordt berekend met behulp van de methode calculerImpôt, die als parameter de tabel met velden uit de aanvraag van de klant ontvangt. De geldigheid van de aanvraag wordt gecontroleerd en eventueel wordt de belasting berekend en teruggestuurd naar de klant.

     // belastingberekening
    public void calculerImpôt(String[] champs){
         // verwerkt de aanvraag: berekening gehuwden nbEnfants salaireAnnuel
         // opgesplitst in velden in de veldtabel

        String marié=null;
        int nbEnfants=0;
        int salaireAnnuel=0;

         // geldigheid van de argumenten
        try{
             // er zijn minimaal 4 velden nodig
            if(champs.length!=4) throw new Exception();
            // getrouwd
            marié=champs[1];
            if (! marié.equals("o") && ! marié.equals("n")) throw new Exception();
            // kinderen
            nbEnfants=Integer.parseInt(champs[2]);
             // salaris
            salaireAnnuel=Integer.parseInt(champs[3]);
        }catch (Exception ignored){
            // formaatfout
            OUT.println(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel");
             // voltooid
            return;
        }//if
         // de belasting kan worden berekend
        long impot=objImpots.calculer(marié.equals("o"),nbEnfants,salaireAnnuel);
         // het antwoord wordt naar de klant verzonden
        OUT.println(""+impot);
    }//berekenen

Een testprogramma zou er als volgt uit kunnen zien:

// oproep: serveurImpots poort dsnImpots userImpots mdpImpots

import java.io.*;

public class testServeurImpots{
    public static final String syntaxe="Syntaxe : pg port dsnImpots userImpots mdpImpots";

    // hoofdprogramma
    public static void main (String[] args){

        // er zijn 4 argumenten nodig
        if(args.length != 4)
            erreur(syntaxe,1);

         // de poort moet een geheel getal >0 zijn
        int port=0;
        boolean erreurPort=false;
        Exception E=null;
        try{
            port=Integer.parseInt(args[0]);
        }catch(Exception e){
            E=e;
            erreurPort=true;
        }
        erreurPort=erreurPort || port <=0;
        if(erreurPort)
            erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

         // de belasting-server wordt aangemaakt
        try{
            new ServeurImpots(port,args[1],args[2],args[3]);
        }catch(Exception ex){
             //fout
            System.out.println("L'erreur suivante s'est produite : "+ex.getMessage());
        }//catch
    }//Main

     // fouten weergeven
    public static void erreur(String msg, int exitCode){
         // fout weergeven
        System.err.println(msg);
         // afsluiten met fout
        System.exit(exitCode);
    }//fout
}// einde klasse

We voeren de gegevens die nodig zijn voor het aanmaken van een object ServeurImpots in het testprogramma in, waarna het dit object aanmaakt.

Laten we een eerste uitvoering proberen:

dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop

Het commando

dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots

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

dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124

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

Dos>java clientTCPgenerique localhost 124
Commandes :
<-- Bienvenue sur le serveur d'impôts

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

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

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

dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop

8.5. Oefeningen

8.5.1. Oefening 1 - Generieke grafische client TCP

8.5.1.1. Presentatie van de applicatie

We willen een programma maken dat via internet kan communiceren met de belangrijkste TCP-diensten. We noemen dit een generieke TCP-client. Als je deze applicatie eenmaal begrijpt, zul je merken dat alle TCP-clients op elkaar lijken. Het programmavenster ziet er als volgt uit:

Image

De betekenis van de verschillende besturingselementen is als volgt:

nr.
naam
type
functie
1
TxtRemoteHost
JTextField
naam van de machine die de gewenste dienst aanbiedt
2
TxtPort
JTextField
poort van de gevraagde dienst
3
TxtSend
JTextField
tekst van het bericht dat door de client naar de server wordt verzonden
4
OptRCLF
OptLF
JCheckBox
knoppen waarmee kan worden aangegeven hoe de regels in de client/server-dialoog eindigen
RCLF: carriage return (#13) + line feed (#10)
LF: regeleinde (#10)
5
LstSuivi
JList
toont berichten over de communicatiestatus tussen de client en de server
6
LstDialogue
JList
geeft de berichten weer die zijn uitgewisseld door de client (->) en de server (<-)
7
CmdAnnuler
JButton
verborgen – bevindt zich onder de dialooglijst – Verschijnt wanneer de verbinding actief is en maakt het mogelijk deze te verbreken als de server niet reageert

De beschikbare menuopties zijn:

optie
subopties
rol
Verbinding
Verbinden
verbindt de client met de server
 
Afmelden
sluit de verbinding af
 
Afsluiten
Sluit het programma af
Berichten
Verzenden
Verzendt het bericht van de besturingselement TxtSend naar de server
 
RazSuivi
Wist de lijst LstSuivi
 
RazDialogue
Wist de lijst LstDialogue
Auteur
 
toont een copyrightvenster

8.5.1.2. FONCTIONNEMENT DE De APPLICATION

Initialisatie van de applicatie

Wanneer het hoofdscherm van de applicatie wordt geladen, vinden de volgende acties plaats:

  • het venster wordt gecentreerd op het scherm
  • alleen de menuopties Connexion/Quitter & Auteur zijn actief
  • de knop Annuler is verborgen
  • de lijsten LstSuivi en LstDialogue zijn leeg
Menu Inloggen/Verbinden

Deze optie is alleen beschikbaar als de velden „Externe host“ en „Poortnummer“ zijn ingevuld en er momenteel geen verbinding actief is. Als u op deze optie klikt, gebeurt het volgende:

  • de geldigheid van de poort wordt gecontroleerd: het moet een geheel getal >0 zijn
  • er wordt een thread gestart om de verbinding met de server tot stand te brengen
  • de knop Annuler verschijnt, zodat de gebruiker de lopende verbinding kan onderbreken
  • alle menuopties worden uitgeschakeld, behalve Quitter en Auteur

De verbinding kan op verschillende manieren worden beëindigd:

  1. De gebruiker heeft op de knop Annuler gedrukt: de verbindingsthread wordt gestopt en het menu wordt teruggezet naar de oorspronkelijke staat. In de log wordt aangegeven dat de verbinding door de gebruiker is beëindigd.
  2. De verbinding wordt met een fout beëindigd: we doen hetzelfde als hierboven en geven bovendien in de log de oorzaak van de fout aan.
  3. De verbinding wordt correct beëindigd: de knop Annuler wordt verwijderd, in de log wordt aangegeven dat de verbinding tot stand is gebracht, het menu RazSuivi wordt vrijgegeven, het menu Connecter wordt geblokkeerd en het menu Déconnecter wordt vrijgegeven
Menu Verbinden/Verbinding verbreken

Deze optie is alleen beschikbaar wanneer er een verbinding met de server bestaat. Wanneer deze wordt geactiveerd, wordt de verbinding met de server verbroken en wordt het menu teruggezet naar de begintoestand. In de status wordt aangegeven dat de verbinding door de client is verbroken.

Menu Verbinden/Afsluiten

Deze optie verbreekt een eventuele actieve verbinding met de server en sluit de applicatie af.

Menu Berichten/Verzenden

Deze optie is alleen beschikbaar als aan de volgende voorwaarden is voldaan:

  • er is verbinding gemaakt met de server

  • er is een bericht dat verzonden moet worden

Als aan deze voorwaarden is voldaan, wordt de tekst in het veld TxtSend (3) naar de server verzonden, afgesloten met de reeks RCLF als de optie RCLF is aangevinkt, anders met de reeks LF. Een eventuele fout bij het verzenden wordt gemeld in de volglijst.

Menu’s RazSuivi en RazDialogue

Wissen respectievelijk de lijsten LstSuivi en LstDialogue. Deze opties zijn uitgeschakeld wanneer de bijbehorende lijsten leeg zijn.

De knop Annuleren

Deze knop onderaan het formulier verschijnt alleen wanneer de klant verbinding probeert te maken met de server. Deze verbinding kan mislukken omdat de server niet of onjuist reageert. De knop Annuler biedt de gebruiker dan de mogelijkheid om het verbindingsverzoek te onderbreken.

De volglijsten

De lijst LstSuivi (5) houdt de verbinding bij. Deze geeft de belangrijkste momenten van de verbinding weer:

  • het tot stand brengen ervan door de client

  • het afsluiten ervan door de server of de klant

  • alle fouten die kunnen optreden zolang de verbinding actief is

De lijst LstDialogue (6) houdt de dialoog bij die tussen de client en de server tot stand komt. Een thread houdt op de achtergrond in de gaten wat er op de communicatiesocket van de client gebeurt en geeft dit weer in lijst 6.

De optie Auteur

Dit menu opent een zogenaamd copyrightvenster:

Image

Foutbeheer

Verbindingsfouten worden gemeld in de volglijst 6, fouten met betrekking tot de client/server-dialoog in de dialooglijst 7. Bij een verbindingsfout wordt de client/server-dialoog gesloten en wordt het formulier teruggezet in de oorspronkelijke staat, klaar voor een nieuwe verbinding.

8.5.1.3. TRAVAIL naar FAIRE

Voer de hierboven beschreven werkzaamheden uit in twee vormen:

  • standalone-toepassing
  • applet

8.5.2. Oefening 2 - Een bronnen-server

8.5.2.1. INTRODUCTION

Een instelling beschikt over meerdere krachtige rekenerservers die via internet toegankelijk zijn. Elke machine die gebruik wil maken van deze rekendiensten, stuurt een gegevensbestand naar poort 756 van een van de servers. Dit bestand bevat diverse gegevens: gebruikersnaam, wachtwoord, opdrachten die het gewenste type berekening aangeven, en de gegevens waarop de berekening moet worden uitgevoerd. Als het gegevensbestand correct is, verwerkt de gekozen rekenserver het en stuurt de resultaten terug naar de client in de vorm van een tekstbestand.

Een dergelijke opzet biedt talrijke voordelen:

  • elk type client (PC, Mac, Unix,...) kan deze dienst gebruiken
  • de klant kan zich overal op het internet bevinden
  • de rekenmiddelen zijn geoptimaliseerd: er zijn slechts enkele krachtige machines nodig. Zo kan een kleine organisatie zonder eigen rekenmiddelen gebruikmaken van deze dienst tegen een financiële bijdrage die wordt berekend op basis van de gebruikte rekentijd.

Ondanks de kracht van de machines kan een berekening soms meerdere uren duren: de server is dan niet beschikbaar voor andere klanten. Voor een klant ontstaat dan het probleem om een beschikbare reken-server te vinden. Hiervoor wordt een „beheerder van rekenbronnen“ gebruikt, hierna de GRC-server genoemd. Deze dienst draait op één enkele machine en werkt op poort 864 in TCP-modus. Een klant die toegang tot een rekenserver wenst, richt zich tot deze dienst. De server GRC, die de volledige lijst met reken servers bevat, antwoordt door de naam van een momenteel inactieve server door te geven. De client hoeft vervolgens alleen nog maar zijn gegevens naar de aangewezen server te sturen.

We zijn van plan de server GRC te schrijven.

8.5.2.2. De INTERFACE VISUELLE

De visuele interface ziet er als volgt uit:

Image

De interface toont twee lijsten met servers:

  • links de lijst met inactieve servers, die dus beschikbaar zijn voor berekeningen
  • rechts de lijst met servers die bezet zijn door berekeningen van een klant.

De menustructuur is als volgt:

Hoofdmenu
Submenu
Rol
Service
Starten
Start de TCP-service op poort 864
 
Stoppen
De service stoppen
 
Afsluiten
Sluit de applicatie af
Auteur
 
Copyrightinformatie

De structuur van de besturingselementen op het formulier is als volgt:

Naam
Type
Rol
listLibres
JList
Lijst met beschikbare servers
listOccupés
JList
Lijst met bezette servers

8.5.2.3. FONCTIONNEMENT DE APPLICATION

De applicatie wordt geladen

Bij het laden van de applicatie wordt de lijst listLibres gevuld met de lijst van namen van de rekenerservers die worden beheerd door de GRC. Deze worden gedefinieerd in een bestand ‘Servers’ dat als parameter wordt doorgegeven. Dit bestand bevat een lijst met servernamen, één per regel, en wordt dus gebruikt om de lijst listLibres te vullen. Het menu ‘Starten’ is toegestaan, het menu ‘Stoppen’ is geblokkeerd.

Optie Service/Starten

Deze optie

  • start de luisterdienst op poort 864 van de machine
  • blokkeert het menu 'Starten'
  • maakt het menu ‘Stoppen’ beschikbaar
Optie Service/Stoppen

Deze optie stopt de dienst:

  • de lijst met bezette servers wordt leeggemaakt
  • de lijst met vrije servers wordt gevuld met de inhoud van het bestand 'Servers'
  • het menu 'Starten' wordt ingeschakeld
  • het menu Afsluiten wordt geblokkeerd
Optie Service/Afsluiten

De toepassing wordt afgesloten.

Client/server-dialoog

De client/server-communicatie verloopt via de uitwisseling van tekstregels die eindigen met de reeks RCLF. De server GRC herkent twee commando’s: getserveur en finservice. Hieronder wordt de functie van deze twee commando’s toegelicht:

  • 1-getserveur

De client vraagt of er een reken-server voor hem beschikbaar is.

De server GRC kiest dan de eerste server die in zijn lijst met vrije servers wordt gevonden en stuurt de naam daarvan terug naar de client in de vorm:

        100-nom du serveur

Bovendien voegt hij de aan de klant toegewezen server toe aan de lijst met bezette servers in de vorm:

        serveur (IP du client)

zoals blijkt uit het volgende voorbeeld, waarin de server calcul1.istia.univ-angers.fr bezig is met het bedienen van de client met het adres IP 193.52.43.5:

Image

Een client kan geen getserveur-commando verzenden als er al een reken-server aan hem is toegewezen. Voordat de server GRC de client antwoordt, controleert hij daarom of het adres IP van de client niet al voorkomt in de lijst met bezette servers. Als dat het geval is, antwoordt de server GRC:

        501-Vous avez actuellement une demande en cours

Ten slotte is er het geval waarin er geen enkele rekenserver beschikbaar is: de lijst met vrije servers is leeg. In dat geval antwoordt de server GRC:

        502- Il n’y a aucun serveur de calcul disponible

In alle gevallen verbreekt de server GRC, nadat hij de client heeft beantwoord, de verbinding met deze client om andere clients te kunnen bedienen.

  • 2-finservice

De client geeft aan dat hij de reken-server die hij gebruikte niet meer nodig heeft.

De server GRC controleert eerst of de klant inderdaad een klant is die hij bediende. Hiervoor controleert hij of het adres IP van de client voorkomt in de lijst met bezette servers. Als dat niet het geval is, antwoordt de server GRC:

        503-Aucun serveur ne vous a été attribué

Als de client wordt herkend, antwoordt de server GRC:

        101-Fin de service acceptée

en verplaatst de aan deze klant toegewezen rekenserver naar de lijst met vrije servers. Om het vorige voorbeeld te herhalen: als de klant het commando finservice verstuurt, wordt de weergave van de server GRC:

Image

Nadat het antwoord is verzonden, ongeacht de inhoud ervan, verbreekt de server GRC de verbinding.

8.5.2.4. TRAVAIL A FAIRE

Schrijf de applicatie als een op zichzelf staand programma dat bijvoorbeeld getest kan worden met een telnet-client of met de generieke TCP-client uit de vorige oefening.

8.5.3. Oefening 3 - een SMTP-client

8.5.3.1. INTRODUCTION

We willen hier een client bouwen voor de dienst SMTP (SendMail Transfer Protocol) waarmee e-mail kan worden verzonden. Onder Unix of Windows is het programma telnet een client die werkt met het protocol tcp. Het kan ‘communiceren’ met elke tcp-service die tekstopdrachten accepteert die eindigen op de reeks RCLF, dat wil zeggen de codetekens ASCII 13 en 10. Hier volgt een voorbeeld van een conversatie met de dienst smtp voor het verzenden van e-mail:

$ telnet istia.univ-angers.fr 25        // aanroep van de SMTP-service

// antwoord van de SMTP-server


Trying 193.52.43.2...
Connected to istia.univ-angers.fr.
Escape character is '^]'.
220-Istia.Istia.Univ-Angers.fr Sendmail 8.6.10/8.6.9 ready at Tue, 16 Jan 1996 07:53:12 +0100
220 ESMTP spoken here

// opmerkingen --------------

Het programma telnet kan elke service aanroepen via de syntaxis

telnet machine_service port_service

De communicatie tussen client en server verloopt via tekstregels die eindigen met de reeks RCLF.

De antwoorden van de dienst smtp hebben de volgende vorm:

berichtnummer of

berichtnummer

De SMTP-server kan meerdere antwoordregels verzenden. De laatste regel van het antwoord wordt aangeduid met een nummer gevolgd door een spatie, terwijl bij de voorgaande regels van het antwoord het nummer wordt gevolgd door een streepje -.

Een nummer groter dan of gelijk aan 500 duidt op een foutmelding.

// einde opmerkingen

help                        // opdracht ingevoerd via het toetsenbord

// antwoord van de SMTP-server

214-Commands:
214-    HELO    EHLO    MAIL    RCPT    DATA
214-    RSET    NOOP    QUIT    HELP    VRFY
214-    EXPN    VERB
214-For more info use "HELP <topic>".
214-To report bugs in the implementation send email to
214-    sendmail@CS.Berkeley.EDU.
214-For local information send email to Postmaster at your site.
214 End of HELP info
mail from: serge.tahe@istia.univ-angers.fr    // nieuw commando ingevoerd via het toetsenbord

// opmerkingen ---------

Het mail-commando heeft de volgende syntaxis:

mail from: e-mailadres van de afzender van het bericht

// einde opmerkingen

// antwoord van de SMTP-server

250 serge.tahe@istia.univ-angers.fr... Sender ok

// opmerkingen

De server smtp controleert de geldigheid van het adres van de afzender niet: hij neemt het adres over zoals het is opgegeven

// einde opmerkingen

rcpt to: user1@istia.univ-angers.fr        // nieuw commando ingevoerd via het toetsenbord

// opmerkingen ---------

Het commando rcpt heeft de volgende syntaxis:

rcpt to: e-mailadres van de ontvanger van het bericht

Als het e-mailadres een adres is van de machine waarop de server smtp draait, controleert deze of het adres daadwerkelijk bestaat; anders wordt er geen controle uitgevoerd. Als er een controle heeft plaatsgevonden en er is een fout gedetecteerd, wordt dit gemeld met een nummer >= 500.

Er kunnen zoveel rcpt to-opdrachten worden gegeven als gewenst: hiermee kan een bericht naar meerdere personen worden verzonden.

// einde opmerkingen

// antwoord van de SMTP-server

250 user1@istia.univ-angers.fr... Recipient ok
data                        // nieuwe opdracht via het toetsenbord

// opmerkingen ---------

Het commando data heeft de volgende syntaxis:

data

regel1

regel2

...

.

Daarna volgen de tekstregels waaruit het bericht bestaat; het bericht moet eindigen met een regel die uitsluitend het teken "punt" bevat.

Het bericht wordt vervolgens verzonden naar de ontvanger die is opgegeven met het commando rcpt.

// einde commentaar

// antwoord van de SMTP-server

354 Enter mail, end with "." on a line by itself

// tekst van het bericht, ingevoerd via het toetsenbord


subject: essai smtp

essai smtp a partir de telnet
.

// opmerkingen

In de tekstregels van het data-commando kan een 'subject:'-regel worden opgenomen om het onderwerp van de e-mail te specificeren. Deze regel moet worden gevolgd door een lege regel.

// antwoord van de SMTP-server

250 HAA11627 Message accepted for delivery
quit                            // nieuwe opdracht via het toetsenbord

// opmerkingen

Het commando quit verbreekt de verbinding met de dienst smtp

// einde opmerkingen

// antwoord van de SMTP-server

221 Istia.Istia.Univ-Angers.fr closing connection

8.5.3.2. De INTERFACE VISUELLE

We stellen voor om een programma te bouwen met de volgende visuele interface:

Image

De besturingselementen hebben de volgende functie:

Nummer
Type
Functie
1
JTextField
Reeks e-mailadressen gescheiden door een komma
2
JTextField
Onderwerp van het bericht
3
JTextField
Reeks e-mailadressen gescheiden door een komma
4
JTextField
Reeks e-mailadressen gescheiden door een komma
5
JTextArea
Tekst van het bericht
6
JList
volglijst
7
JList
dialooglijst
8
JButton
De knop 'Annuleren' wordt niet weergegeven, maar verschijnt wanneer de client verbinding vraagt met de server SMTP. Hiermee kan de gebruiker dit verzoek afbreken als de server niet reageert.

8.5.3.3. LES MENUS

De menustructuur van de applicatie is als volgt:

Hoofdmenu
Submenu
Rol
E-mail
  
 
Verzenden
Verzend het bericht van controle 5
 
Afsluiten
De applicatie afsluiten
Opties
  
 
Tracking verbergen
Maak bedieningselement 6 onzichtbaar
 
Tracking resetten
De volglijst leegmaken 6
 
Dialoogvenster verbergen
Maakt de dialooglijst onzichtbaar 7
 
Dialooglijst wissen
Dialooglijst leegmaken 7
 
Configureren
Hiermee kan de gebruiker het volgende opgeven
- het adres van de SMTP-server die door het programma wordt gebruikt
- zijn e-mailadres
 
Opslaan...
Slaat de vorige configuratie op in een .ini-bestand
Auteur
 
Copyright-informatie

8.5.3.4. FONCTIONNEMENT DE APPLICATION

Menu Opties/Configureren

Dit menu opent het volgende venster:

Image

Beide velden moeten worden ingevuld om de knop OK te activeren. Beide gegevens moeten in globale variabelen worden opgeslagen, zodat ze beschikbaar zijn voor andere modules.

Menu E-mail/Verzenden

Deze optie is alleen beschikbaar als aan de volgende voorwaarden is voldaan:

  • de configuratie is voltooid
  • er is een bericht om te verzenden
  • er is een onderwerp
  • er is ten minste één ontvanger in de velden 1, 3 en 4

Als aan deze voorwaarden is voldaan, verloopt de reeks gebeurtenissen als volgt:

  • het formulier wordt in een status gezet waarin alle acties die de communicatie tussen client en server kunnen verstoren, worden geblokkeerd
  • er wordt verbinding gemaakt via poort 25 van de server die in de configuratie is opgegeven
  • de client communiceert vervolgens met de SMTP-server volgens het hierboven beschreven protocol
  • het veld „mail from:“ gebruikt het e-mailadres van de afzender dat in de configuratie is opgegeven
  • het veld „rcpt to:“ wordt gebruikt voor elk van de e-mailadressen die in de velden 1, 3 en 4 zijn aangetroffen
  • in de regels die na het commando data worden verzonden, staan de volgende teksten:
    • een regel Subject: met de tekst van het onderwerp van controle 2
    • een regel Cc:: adressen van de controle 3
    • een regel Bcc: met de adressen van controle 4
    • de tekst van het bericht van controle 5
    • het eindpunt
De knop Annuleren

Deze knop onderaan het formulier verschijnt alleen wanneer de klant verbinding maakt met de server smtp. Deze verbinding kan mislukken omdat de server smtp niet of onjuist reageert. De knop Annuler biedt de gebruiker dan de mogelijkheid om het verbindingsverzoek te onderbreken.

De volglijsten

De lijst (6) houdt de verbinding bij. Deze geeft de belangrijkste momenten van de verbinding weer:

  • het tot stand brengen ervan door de client
  • de afsluiting door de server of de klant
  • alle verbindingsfouten

De lijst (7) houdt de dialoog smtp bij die tussen de client en de server tot stand komt.

Deze twee lijsten zijn gekoppeld aan menuopties:

Volglijst verbergen
Maakt de volglijst 6 en de tekst erboven onzichtbaar. Als de hoogte die deze twee besturingselementen innemen H is, worden alle besturingselementen eronder met een hoogte van H omhoog geschoven en wordt de totale grootte van het formulier met H verkleind. Bovendien maakt ‘Volgnummer verbergen’ de optie RazSuivi hieronder onzichtbaar.
Tracking wissen
Wist de volglijst 6
Dialoogvenster verbergen
Maakt de dialooglijst 7 onzichtbaar, evenals de tekst erboven en de menuoptie RazDialogue eronder. Net als bij 'Tracking verbergen' wordt de positie van de onderliggende bedieningselementen (bijvoorbeeld knop Annuler) opnieuw berekend en wordt de venstergrootte verkleind.
Dialoogvenster leegmaken
Wist de dialooglijst 7
De optie ‘Auteur’

Dit menu opent een zogenaamd copyrightvenster:

Image

Foutbeheer

Verbindingsfouten worden gemeld in de volglijst 6, fouten met betrekking tot de client/server-dialoog in de dialooglijst 7. Bij een fout wordt de gebruiker hiervan op de hoogte gesteld via een foutmelding en wordt de lijst met de oorzaak van de fout weergegeven, indien deze eerder verborgen was. Bovendien wordt de client/server-dialoog gesloten en wordt het formulier teruggezet naar de oorspronkelijke staat.

8.5.3.5. GESTION van UN FICHIER DE CONFIGURATION

Het is wenselijk dat de gebruiker de software niet telkens opnieuw hoeft te configureren wanneer hij deze gebruikt. Als de optie ‘Opties/Configuratie opslaan bij afsluiten’ is aangevinkt, worden bij het afsluiten van het programma de twee gegevens die via de optie Options/Configurer zijn verkregen, evenals de status van de twee volglijsten, opgeslagen in een bestand sendmail.ini dat zich in dezelfde map bevindt als het .exe-bestand van het programma. Dit bestand heeft de volgende indeling:

SmtpServer=shiva.istia.univ-angers.fr
ReplyAddress=serge.tahe@istia.univ-angers.fr
Suivi=0
Dialogue=1

De regels SmtpServer en ReplyAddress bevatten de twee gegevens die zijn opgehaald via de optie Opties/Configureren. De regels Suivi en Dialogue geven de status van de lijsten ‘Opvolging’ en ‘Dialoog’ weer: 1 (aanwezig), 0 (afwezig).

Bij het laden van het programma wordt het bestand sendmail.ini gelezen, indien het bestaat, en wordt het formulier dienovereenkomstig geconfigureerd. Als het bestand sendmail.ini niet bestaat, wordt er gehandeld alsof het wel bestaat:

SmtpServer=
ReplyAddress=
Suivi=1
Dialogue=1

Als het bestand sendmail.ini wel bestaat maar onvolledig is (er ontbreken regels), wordt de ontbrekende regel vervangen door de overeenkomstige regel hierboven. Als de regel Suivi=... dus ontbreekt, doen we alsof we Suivi=1 hebben.

Alle regels die niet overeenkomen met het sjabloon:

    mot clé= valeur

worden genegeerd, evenals die waarbij het trefwoord ongeldig is. Het trefwoord mag in hoofdletters of kleine letters worden geschreven: dat maakt niet uit.

In de optie Options/Configurer worden de waarden SmtpServer en ReplyAddress weergegeven die momenteel actief zijn. De gebruiker kan deze vervolgens desgewenst wijzigen.

8.5.3.6. TRAVAIL naar FAIRE

Voer de hierboven beschreven werkzaamheden uit. Het wordt aangeraden om het beheer van het configuratiebestand als laatste af te handelen.

8.5.4. Oefening 4 - client POPPASS

8.5.4.1. Introduction

We zijn van plan een client TCP te maken die kan communiceren met de server POPPASSD, die op poort 106 draait. Met deze dienst kan men zijn wachtwoord wijzigen op een machine UNIX. Het protocol voor de client/server-communicatie is als volgt:

1 - De communicatie verloopt via de uitwisseling van berichten die eindigen met de reeks RCLF

2 - De client stuurt opdrachten naar de server

  • De server antwoordt met berichten die beginnen met driecijferige getallen: XXX. Als XXX=200 is, is het commando correct uitgevoerd; anders is er een fout opgetreden.

3 - De volgorde van de uitwisselingen is als volgt:

A    - le client se connecte
  • de server antwoordt met een welkomstbericht
B    - le client envoie USER login
  • de server vraagt om het wachtwoord als de aanmelding wordt geaccepteerd; anders wordt er een foutmelding gegeven
C    - le client envoie PASS mot_de_passe
  • de server vraagt om het nieuwe wachtwoord; als het wachtwoord wordt geaccepteerd, volgt er een foutmelding
D    - le client envoie NEWPASS nouveau_mot_de_passe
  • de server bevestigt dat het nieuwe wachtwoord is geaccepteerd; anders wordt er een foutmelding gegeven
E    - le client envoie la commande QUIT
  • de server stuurt een afsluitbericht en verbreekt de verbinding

8.5.4.2. Het formulier van de client

Image

De betekenis van de verschillende controles is als volgt:

nr.
naam
type
functie
1
txtRemoteHost
JTextField
servernaam
2
txtLogin
JTextField
gebruikersnaam
3
txtMdp
JTextField
Wachtwoord van de gebruiker
4
txtNewMdp
JTextField
Nieuw wachtwoord van de gebruiker
5
txtConfirmation
JTextField
Bevestiging van het nieuwe wachtwoord
6
lstSuivi
JList
Berichten over de verbindingsstatus
7
lstDialogue
JList
Berichten van de client/server-dialoog
10
cmdAnnuler
JButton
niet afgebeeld - Knop die verschijnt wanneer er verbinding met de server wordt gemaakt. Hiermee kunt u de verbinding verbreken.

8.5.4.3. De menu's

Titel
Naam van het besturingselement
Functie
Verbinding
mnuconnexion
 
Verbinden
mnuconnecter
start de verbinding met de server
Afsluiten
mnuQuitter
sluit de applicatie af
Berichten
mnuMessages
 
RazSuivi
mnuRazSuivi
wist de lijst lstSuivi
RazDialogue
mnuRazDialogue
wist de lijst lstDialogue
Auteur
mnuAuteur
toont het copyrightvenster

8.5.4.4. Werking van de applicatie

Initialisatie van de applicatie

Wanneer het hoofdscherm van de applicatie wordt geladen, vinden de volgende handelingen plaats:

  • het scherm wordt gecentreerd op het beeldscherm
  • alleen de menuopties Connexion/Quitter en Auteur zijn actief
  • de knop Annuler is verborgen
  • de lijsten LstSuivi en LstDialogue zijn leeg
Menu Inloggen/Verbinden

Deze optie is alleen beschikbaar als de velden 1 tot en met 5 zijn ingevuld. Als u op deze optie klikt, gebeurt het volgende:

  • er wordt een thread gestart om de verbinding met de server tot stand te brengen
  • de knop Annuler verschijnt, zodat de gebruiker de lopende verbinding kan onderbreken
  • alle menuopties worden uitgeschakeld, behalve Quitter & Auteur

De volgorde van de gebeurtenissen is vervolgens als volgt:

  1. De gebruiker heeft op de knop Annuler gedrukt: de verbindingsthread wordt gestopt en het menu wordt teruggezet naar de oorspronkelijke staat. In de log wordt aangegeven dat de verbinding door de gebruiker is verbroken.
  2. Het verbindingsverzoek wordt door de server geaccepteerd. Vervolgens wordt de dialoog met de server gestart om het wachtwoord te wijzigen. De uitwisselingen van deze dialoog worden vastgelegd in de lijst LstDialogue. Zodra de dialoog is beëindigd, wordt de verbinding met de server verbroken en wordt het formuliermenu teruggezet naar de oorspronkelijke toestand.
  3. Zolang de dialoog actief is, blijft de knop Annuleren zichtbaar, zodat de gebruiker de verbinding kan beëindigen als hij dat wenst.
  4. Als er tijdens de communicatie een fout optreedt, wordt de verbinding verbroken en wordt de oorzaak van de fout weergegeven in de volglijst LstSuivi.
Menu Verbinding/Afsluiten

Deze optie verbreekt een eventuele actieve verbinding met de server en sluit de toepassing af.

Menu’s RazSuivi en RazDialogue

Wissen respectievelijk de lijsten LstSuivi en LstDialogue. Deze opties zijn uitgeschakeld wanneer de bijbehorende lijsten leeg zijn.

De knop Annuleren

Deze knop onderaan het formulier verschijnt alleen wanneer de klant bezig is met inloggen of al is ingelogd op de server. De knop Annuler biedt de gebruiker de mogelijkheid om de verbinding met de server te verbreken.

De volglijsten

De lijst LstSuivi (5) houdt de verbinding bij. Deze geeft de belangrijkste momenten van de verbinding weer:

  • het tot stand brengen ervan door de client

  • het afsluiten ervan door de server of de klant

  • alle fouten die kunnen optreden zolang de verbinding actief is

De lijst LstDialogue (6) houdt de dialoog bij die tussen de client en de server tot stand komt.

De optie Auteur

Dit menu opent een zogenaamd copyrightvenster:

Image

Foutbeheer

Communicatiefouten worden weergegeven in de volglijst 6, fouten met betrekking tot de client/server-dialoog in de dialooglijst 7. Bij een verbindingsfout wordt de client/server-dialoog gesloten en wordt het formulier teruggezet naar de oorspronkelijke staat, klaar voor een nieuwe verbinding.

8.5.4.5. TRAVAIL A FAIRE

Voer de hierboven beschreven werkzaamheden eerst uit in de vorm van een zelfstandige toepassing en vervolgens als applet.