Clients TCP simples

Fermez la porte en sortant

On cherche a réaliser un client TCP très simple. Le protocole est le suivant. Le client établit la connexion avec le serveur, puis écrit une chaîne en UTF-8 à destination du serveur et ferme la connexion en écriture. Ensuite il lit sur la connexion une chaîne de caractères en UTF-8 dont la fin est signalée par la fermeture de la connexion en lecture.

En partant du fichier ClientEOS.java, écrivez la fonction getFixedSizeResponse qui prend en paramètre la chaîne de caractères à envoyer, l'adresse du serveur et la taille du buffer de réception.
Si la réponse du serveur dépasse la taille du buffer, on ignore la fin de la réponse.

La méthode main appelle getFixedSizeResponse avec :

var google = new InetSocketAddress("www.google.fr", 80);
getFixedSizeResponse("GET / HTTP/1.1\r\nHost: www.google.fr\r\n\r\n", google, 512);
Notre client envoie donc la requête GET ... au serveur HTTP www.google.fr sur le port 80. Une fois que la connexion est fermée en écriture, le serveur renvoie la page d'accueil de Google.
Vous devriez afficher :
HTTP/1.1 200 OK
Date: Tue, 27 Feb 2018 21:35:47 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Server: gws
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Set-Cookie: 1P_JAR=2018-02-27-21; expires=Thu, 29-Mar-2018 21:35:47 GMT; path=/; domain=.google.fr
Set-Cookie: NID=124=VPRK8vfhv8XgJdFfzadSN-WSa5C7RvGCOlWBqZ_-Hvnv9O1wWnKSMcXUY3HyNxC2wCoafO7VJ482Bp0njEQV7PtMJeoPjtr_fWH2-

Écrivez ensuite la fonction getUnboundedResponse qui doit lire la réponse quelle que soit la taille de la chaîne de caractères renvoyée par le serveur.

Lors des lectures sur la SocketChannel, votre méthode doit gérer à la fois le cas où la connexion est fermée et celui où le buffer est plein, ce qui peut mener à un code peu lisible. Une solution pour clarifier le code est d'avoir une méthode annexe qui s'occupe de remplir un buffer, si possible.

Écrivez une méthode boolean readFully(ByteBuffer buffer, SocketChannel sc) qui remplit complètement le buffer (si c'est possible) en lisant sur la SocketChannel sc. De plus, la méthode renvoie false si read a renvoyé -1 et true sinon.

"Simplifiez" votre code en utilisant la méthode readFully.

Le protocole, y'a que ça de vrai

Le client précédent a deux inconvénients majeurs : il n'est pas très efficace car il demande de manipuler plusieurs fois les données reçues pour reconstituer la réponse, et surtout il ne permet pas de réutiliser la connexion pour une deuxième requête. On va donc maintenant écrire un client qui envoie et reçoit des informations dont le format est précisé par un protocole.

Le but de l'exercice est de réaliser un client qui interroge un serveur qui doit réaliser la somme d'une liste d'entiers long en suivant le protocole TCP suivant :
  1. le client se connecte au serveur
  2. le client envoie un int (en Big Endian) donnant le nombre d'opérandes de l'opération à réaliser, puis chacun des opérandes qui sont des long en Big Endian;
  3. le client lit ensuite la réponse du serveur qui est un long correspondant à la somme des opérandes.
  4. le serveur ne ferme pas la connexion qui peut donc être utilisée pour faire une autre somme en repartant à l'étape 2.

En partant du fichier ClientLongSum.java, vous devez réaliser un client qui prend en paramètre l'adresse et le port du serveur. Il se lancera par exemple avec:

java fr.upem.net.tcp.ClientLongSum localhost 7777
La méthode requestSumForList(SocketChannel sc, List<Long> list) de votre client doit envoyer la liste d'opérandes au serveur en respectant le protocole, puis lire la réponse du serveur et la renvoyer.
Si le serveur ferme la connexion avant d'avoir fini l'échange, votre client loggera le message :
Connection with server lost.
et quittera.

Remarque : vous pouvez utiliser la méthode readFully écrite lors de l'exercice précédent...

Vous pouvez tester votre client en lançant le serveur ServerLongSumTCP.jar comme suit :

java -jar ServerLongSumTCP.jar 7777

Pour tester le cas où le serveur ferme la connexion de manière imprévue, vous lancerez le serveur avec l'option -bug qui introduit une probabilité que le serveur ferme la connexion.
java -jar ServerLongSumTCP.jar -bug 7777

Bout à bout

Le même genre de protocole peut être mis en place pour échanger des chaînes de caractères.

Le but de cet exercice est de réaliser un client qui se connecte à un serveur de concaténation de chaînes avec le protocole TCP suivant :
  • le client envoie un int (en Big Endian) donnant le nombre de chaînes de caractères, puis envoie chacune de ces chaînes encodées par un int (pour la taille en nombre d'octets de la chaîne encodée en UTF-8) suivi des octets représentant la chaîne en UTF-8.
  • Le serveur répond avec une seule chaîne, représentée sous le même format, correspondant à la concaténation de toutes les chaînes, séparées par des virgules. Plus précisément, le serveur envoie un int (pour la taille en nombre d'octets de la chaîne encodée en UTF-8) suivi des octets représentant la chaîne en UTF-8.

Vous réaliserez un client ClientConcatenation.java qui prend en paramètre l'adresse et le port du serveur. Il se lancera par exemple avec :

java upem.net.tcp.ClientConcatenation localhost 7777
Votre client va lire des chaînes au clavier jusqu'à ce qu'il lise une chaîne vide. Il enverra ensuite ces chaînes (une par une et sans la chaîne vide) au serveur en respectant le protocole et affichera la réponse du serveur.
Si le serveur ferme la connexion avant d'avoir envoyé la réponse, votre client affichera le message :
Connection with server lost.
et quittera.

Vous pouvez tester votre client en lançant le serveur ServerConcatenationTCP.jar comme suit :

java -jar ServerConcatenationTCP.jar 7777
Le serveur écoute sur le port 7777.

Remarque : envoyer les chaînes une par une revient à faire beaucoup d'appels à write() ce qui n'est pas très efficace. On peut également préparer tous les buffers encodés, mais on stocke ainsi beaucoup de données. La "bonne" façon de faire serait d’utiliser un buffer de taille fixe, et de le remplir "au mieux" avant chaque appel à write().