Ressources informatiques

Ressources informatiques

Ressources informatiques

Créer un serveur TCP

Notre objectif est de créer un serveur TCP minimal : le serveur envoie la lettre suivante dans l'alphabet lorsque'il recoit une lettre. Il renvoie le caractère '!' s'il reçoit un caractère qui n'est pas une lettre ou s'il reçoit la lettre 'z' ou la lettre 'Z'.

Se documenter

Code du serveur

/**
 * @brief Reçoit une lettre du client. Il envoie au client la lettre suivante dans l'alphabet.
 * Il envoie le caractère '!' s'il reçoit un caractère qui n'est pas une lettre ou s'il reçoit la lettre 'z' ou la lettre 'Z'.
 */
#include <string.h>      // pour memset()
#include <sys/types.h>   // pour socket(), setsockopt(), bind(), listen(), accept(), recv(), send(), wait()
#include <sys/socket.h>  // pour socket(), setsockopt(), bind(),listen(), accept(), recv(), send()
#include <stdio.h>       // pour fprintf(), perror()
#include <arpa/inet.h>   // pour htonl(), htons()
#include <unistd.h>      // pour fork(), close()
#include <sys/wait.h>    // pour wait()

// taille de la file d'attente de demandes de connexion
#define TAILLE_FILE_DATTENTE_CONNEXION 5
// Numéro de port du serveur
#define NUMERO_PORT_SERVEUR 40000

// Routine d'interception du signal SIGCHLD emis par les fils
void intercepter(int numSignal);

int communiquerAvecLeClient(int socketConnectee);

int main() {
 struct sockaddr_in coupleIPPortServeur;
 struct sockaddr_in coupleIPPortClient;
 int socketConnexion;
 int socketConnectee;
 socklen_t longueurClient;
 int optval;

 // Mettre en place de l'interception de SIGCHLD emis par les fils
 signal(SIGCHLD,intercepter);

 // Initialiser les structures a des octets de valeurs 0
 memset(&coupleIPPortServeur,0,sizeof(struct sockaddr_in));

 // Creer la socket serveur en mode TCP
 if ((socketConnexion = socket(AF_INET,SOCK_STREAM,0)) == -1)
 {perror("socket");return -1;}

 // Reutiliser le meme port en cas d'interruption brutal du serveur
 optval = 1;
 setsockopt(socketConnexion, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);

 // Associer la socket serveur a un couple adresse IP, numero de port
 coupleIPPortServeur.sin_family = AF_INET;
 coupleIPPortServeur.sin_addr.s_addr = htonl(INADDR_ANY);
 coupleIPPortServeur.sin_port = htons(NUMERO_PORT_SERVEUR);
 if (bind(socketConnexion, (struct sockaddr *)(&coupleIPPortServeur), sizeof(struct sockaddr_in)) == -1)
 {perror("bind");return -1;}

 // Creer une file d'attente de demandes de connexion
 if (listen(socketConnexion,TAILLE_FILE_DATTENTE_CONNEXION) == -1)
 {perror("listen");return -1;}

 longueurClient = sizeof(struct sockaddr_in);

 while(1) {
  // Accepter une demande de connexion
  socketConnectee = accept(socketConnexion, (struct sockaddr *)(&coupleIPPortClient), &longueurClient);
  if (socketConnectee == -1) {
   perror ("accept");
  } else {
   // Creer un fils pour traiter la communication
   if (fork() == 0) {
    // Dans le fils
    // Fermer la socket serveur permettant d'effectuer la demande
    close(socketConnexion);

    // Communiquer avec le client
    if (communiquerAvecLeClient(socketConnectee) == -1 )
    {fprintf(stderr,"communiquerAverLeClient : echec\n"); close(socketConnectee); return -1;}

    // Fermer dans le fils la socket connectee
    close(socketConnectee);

    return 0;
   }

   // Dans le pere
   // Fermer la socket connectee
   close(socketConnectee);
  }
 }
 return 0;
}

void intercepter(int numSignalRecu) {
 wait(NULL);
}

int communiquerAvecLeClient(int socketConnectee) {
 char lettre;
 char lettreReponse = '!';

 // Recevoir la requête
 if (recv(socketConnectee, &lettre, sizeof(lettre), 0)  == -1)
 {perror("recv");return -1;}

 // Traiter la requete - construire la réponse
 if ((lettre >= 'a' && lettre < 'z') || (lettre >= 'A' && lettre <  'Z'))
  lettreReponse = lettre + 1;

 // Envoyer la réponse
 if (send(socketConnectee, &lettreReponse, sizeof(lettreReponse), 0) == -1)
 {perror("send");return -1;}

 return 0;
}

Tester

Vérifier que le port 40000 de notre serveur TCP est ouvert

Commande ss : another utility to investigate sockets
Options :

doe@debian:~$ ss -lntp sport = :40000
State       Recv-Q      Send-Q           Local Address:Port            Peer Address:Port
LISTEN      0           5                      0.0.0.0:40000                0.0.0.0:*         users:(("serveurTcp",pid=1176,fd=3))

Valider le protocole applicatif

Avec la commande telnet

Cette procédure de test ne fonctionne qu'avec les protocoles de types chaînes de caractères

doe@debian:~$ telnet 192.168.1.101 40000
Trying 192.168.1.101...
Connected to 192.168.1.101.
Escape character is '^]'.
d
eConnection closed by foreign host.
doe@debian:~$ telnet 192.168.1.101 40000
Trying 192.168.1.101...
Connected to 192.168.1.101.
Escape character is '^]'.
D
EConnection closed by foreign host.
doe@debian:~$ telnet 192.168.1.101 40000
Trying 192.168.1.101...
Connected to 192.168.1.101.
Escape character is '^]'.
z
!Connection closed by foreign host.
doe@debian:~$ telnet 192.168.1.101 40000
Trying 192.168.1.101...
Connected to 192.168.1.101.
Escape character is '^]'.
Z
!Connection closed by foreign host.
doe@debian:~$ telnet 192.168.1.101 40000
Trying 192.168.1.101...
Connected to 192.168.1.101.
Escape character is '^]'.
?
!Connection closed by foreign host.
Avec le client TCP correspondant.
doe@debian:~$ ./clientTcp 
usage : clientTcp   
exemple : clientTcp 192.168.1.11 50000 a
doe@debian:~$ ./clientTcp 192.168.1.101 40000 a
connect: Connection refused
doe@debian:~$ ./clientTcp 192.168.1.101 50000 d
J'ai émis : d, j'ai reçu : e
doe@debian:~$ ./clientTcp 192.168.1.101 50000 D
J'ai émis : D, j'ai reçu : E
doe@debian:~$ ./clientTcp 192.168.1.101 50000 z
J'ai émis : z, j'ai reçu : !
doe@debian:~$ ./clientTcp 192.168.1.101 50000 Z
J'ai émis : Z, j'ai reçu : !
doe@debian:~$ ./clientTcp 192.168.1.101 50000 ?
J'ai émis : ?, j'ai reçu : !