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 :
-l, --listening : Display only listening sockets.
-n, --numeric : Do not try to resolve service names.
-t, --tcp: Display TCP sockets.
-p : Show process using socket.
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 : !