IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Bien programmer en langage C

Date de publication : 28 mai 2008


VII. Notes brutes concernant les réseaux
VII-A. Ouvrages de références
VII-B. Dialogue Terminal Modem
VII-C. Réalisation d'un projet réseau
VII-C-1. Sockets
VII-C-1-a. Introduction
VII-C-1-b. Protocoles
VII-C-1-c. Fonctions
VII-C-1-d. Serveur
VII-C-1-e. Client
VII-C-2. Réalisation d'un Client / Serveur UDP/IP
VII-C-2-a. Serveur UDP/IP
VII-C-2-b. Client UDP/IP
VII-C-3. Réalisation d'un Client / Serveur TCP/IP
VII-C-3-a. Serveur TCP/IP
VII-C-3-b. Client TCP/IP
VII-C-3-c. Serveur multiclient en TCP/IP
VII-D. Réalisation pas à pas d'un serveur TCP/IP
VII-D-1. Introduction
VII-D-1-a. Avertissement
VII-D-1-b. Environnement de développement
VII-D-1-c. Environnement de test
VII-D-2. 01 - Initialisation, terminaison
VII-D-3. 02 - Création, suppression de sockets
VII-D-4. 03 - Mini serveur : Mise en écoute du port 23
VII-D-5. 04 - Mini serveur : Connexion du client/ Déconnexion du client
VII-D-6. 05 - Reception d'un caractère puis fermeture
VII-D-7. 06 - Mini serveur : Reception d'un bloc de données puis fermeture sur ESC
VII-D-7-a. Pseudo-code
VII-D-7-b. Code
VII-D-8. 07 - Mini serveur : Reception d'un bloc de données puis deconnexion sur ESC
VII-D-8-a. Pseudo-code
VII-D-8-b. Code
VII-D-9. 08 - Serveur-multi-clients : Reception d'un bloc de données puis deconnexion sur ESC
VII-D-9-a. Pseudo-code
VII-D-9-b. Code
VII-D-10. Conclusion
VII-E. Transmettre du texte par sockets
VII-E-1. Emission de petits blocs (cas simple)
VII-E-2. Reception de petits blocs
VII-E-3. Emission de gros blocs (cas général)
VII-E-4. Réception de gros blocs
VII-F. Etude de cas : transmettre un fichier texte.
VII-F-1. Spécifications
VII-F-2. Conception
VII-F-2-a. Protocole
VII-F-2-a-i. Format des messages
VII-F-2-a-ii. Commandes
VII-F-2-a-iii. Réponses
VII-F-2-a-iv. Exemple de dialogue
VII-G. Du bon usage de select()
VII-G-1. Introduction
VII-G-2. Programmation
VII-G-3. Exemples d'utilisation
VII-G-3-a. Suspension temporisée
VII-G-3-b. Surveillance d'un flux en réception
VII-G-3-c. Surveillance de deux flux en réception
VII-G-4. Conclusion
VII-H. La récupération de l'IP à partir du nom du serveur
VII-I. Ebauche d'une bibliothèque sockets portable (psock)
VII-I-1. Interfaces communes
VII-I-2. Sockets UNix (sun)
VII-I-3. Sockets IP (Internet Protocol)
VII-J. Ressources


VII. Notes brutes concernant les réseaux


VII-A. Ouvrages de références

tome 1 du 'Unix network programming' de W. Richard Stevens


VII-B. Dialogue Terminal Modem


        Terminal              Modem
            |                    |
            |    commande        |
            |------------------->|
            |    réponse         | Traitement
            |<------------------ |
Traitement  |                    |

VII-C. Réalisation d'un projet réseau

La realisation d'un projet réseau est basée sur l'utilisation des sockets (BSD, Windows). Il y a quelques nuances selon le système utilisé, mais dans l'ensemble, les fonctions et comportements sont identiques. Le but de cet article est de montrer la réalisation de clients / serveurs en UDP/IP et TCP/IP.


VII-C-1. Sockets


VII-C-1-a. Introduction
Les sockets sont des flux de données (octets) très similaires aux flux d'entrée / sortie standards ou aux fichiers, mais qui permettent de réaliser des connexions de données bidirectionnelles entre des machines (locales ou distantes) via un réseau de données (boucle, réseau local, Internet, X.25 etc.). Ils sont mis en oeuvre via une série de fonctions regroupées sous le nom de API sockets ou sockets BSD ou sockets.


VII-C-1-b. Protocoles
Les connexions de données sont gérées par différent protocoles (niv 3 et 4) et différentes liaisons (niv. 2) et interfaces physiques (niv. 1). Le sockets ignorent les interfaces (gérés par les drivers systèmes), mais connaissent les protocoles de niveau 3 et 4 (IP, UNIX / TCP, UDP, etc.). Ensuite, ils savent travailler en mode non connecté (datagrams, simple, pas de vérification) ou connecté (paquets, plus complexe, données vérifiée, integrité des données).

Par exemple, le protocole de niveau 3 IP (Internet Protocol) sait travailler en mode de niveau 4 connecté (TCP) ou non connecté (UDP).


VII-C-1-c. Fonctions
Les sockets sont manipulés par des fonctions générales :

  • socket()
  • close() ou closesocket()
  • bind()
  • send(), recv(), (mode connecté)
  • sendto(), recvfrom() (mode non connecté)
  • et des fonctions spécialisées qui dépendent si l'application est un serveur ou un client et si on utilise un mode connecté ou non.

VII-C-1-d. Serveur
  • listen()
  • accept() (mode connecté)

VII-C-1-e. Client
  • connect() (mode connecté)
Rappel : La documentation complète des fonctions se trouve dans les 'pages man', comme, par exemple, celles-ci.


VII-C-2. Réalisation d'un Client / Serveur UDP/IP


          Client              Serveur
            |                    |
            |   indication       |
            |<- - - - - - - - -  |
            |    commande        |
            |------------------->|
            |   [réponse]        | Traitement
            |<- - - - - - - - -  |
[Traitement]|                    |
Le mode UDP (Datagram sockets) est rustique et ne necessite pas de connexion. Il agit par échange de blocs de données appelés 'Datagrammes'.


VII-C-2-a. Serveur UDP/IP
Les opérations à réaliser sont:

  • initialisation
  • ouverture d'un socket en mode datagramme (UDP/IP): socket(AF_INET, SOCK_DGRAM, 0)
  • configurer l'adresse et le port: bind()
  • dans une boucle
  • tester la réception : select() [Facultatif]
  • lire le bloc reçu : recvfrom()
  • traitement des données...
  • émission du bloc : sendto()
  • fin
  • fermeture du socket : closesocket()

VII-C-2-b. Client UDP/IP
Les opérations à réaliser sont:

  • initialisation
  • ouverture d'un socket en mode datagramme (UDP/IP): socket(AF_INET, SOCK_DGRAM, 0)
  • selon la demande
  • fabrication des données à émettre...
  • émission du bloc : sendto()
  • tester la réception : select() [Facultatif]
  • lire le bloc reçu : recvfrom()
  • traitement des données reçues...
  • fin
  • fermeture du socket : closesocket()

VII-C-3. Réalisation d'un Client / Serveur TCP/IP


          Client              Serveur
            |                    |
            | dem. de connexion  |
            |------------------->|
            |   acceptation      |
            |<- - - - - - - - -  |
[Traitement]|   indication       |
            |<- - - - - - - - -  |
            |    commande        |
            |------------------->|
            |   [réponse]        | Traitement
            |<- - - - - - - - -  |
[Traitement]|                    |
            |    deconnexion     |
            |<- - - - - - - - - >|
Le mode TCP (Stream sockets) est solide. Il garanti la transmission des données. Il nécessite l'établissement d'une connexion. Ensuite, les blocs de données peuvent être échangés.


VII-C-3-a. Serveur TCP/IP
Les opérations à réaliser sont:

  • initialisation
  • ouverture d'un socket en mode flux (TCP/IP): socket(AF_INET, SOCK_STREAM, 0)
  • configurer l'adresse et le port: bind()
  • configurer le nombre d'écoutes: listen()
  • dans une boucle
  • accepter une connexion: accept()
  • tester la réception : select() [Facultatif]
  • recevoir des données: recv()
  • émettre des données: send()
  • déconnecter : shutdown()
  • fin
  • fermeture du socket : closesocket()

VII-C-3-b. Client TCP/IP
Les opérations à réaliser sont:

  • initialisation
  • ouverture d'un socket en mode flux (TCP/IP): socket(AF_INET, SOCK_STREAM, 0)
  • connexion: connect()
  • selon la demande
  • émettre des données: send()
  • tester la réception : select() [Facultatif]
  • recevoir des données: recv()
  • fin
  • déconnecter : shutdown()
  • fermeture du socket : closesocket()

VII-C-3-c. Serveur multiclient en TCP/IP
Le principe est de réduire la boucle principale à la surveillance des connexions entrantes (accept()) et de créer un thread comprenant la boucle de traitement avec les données de la connexion client.

thread 'serveur' : dans une boucle :

  • attendre une connexion: accept()
  • créer un thread avec le socket client
thread 'client' : dans une boucle :

  • attendre des données et les lire: recv()
  • émettre des données en réponse: send()
Apres la boucle (deconnexion, timeout...)

  • déconnecter : shutdown()

VII-D. Réalisation pas à pas d'un serveur TCP/IP


VII-D-1. Introduction

Il s'agit d'une application de mise en oeuvre écrite en langage C. Le détail des fonctions (paramètres, comportement) doit être consulté dans les documents de références habituels (msdn, man etc.).


VII-D-1-a. Avertissement
Cette réalisation demande une bonne connaissance du langage C. Cependant, je m'efforce de n'utiliser que des concepts simples du langage. Si je juge qu'un point est difficile, je fais une remarque ou je renvoie à une explication détaillée. En cas de difficulté avérée, me contacter directement.


VII-D-1-b. Environnement de développement
Le code est écrit et validé sous Windows avec Dev-C++/Code::Blocks et la blibiothèque -lws2_32. Il compile sous Linux, mais n'est pas validé pour le moment. (Si quelqu'un veut le faire, il est le bienvenu)


VII-D-1-c. Environnement de test
Il suffit d'une machine supportant le protocole TCP/IP, et munie d'une adresse IP (sinon, utiliser 127.0.0.1). Si on dispose de 2 machines, on peut espionner les échanges de trames avec EtherReal.


VII-D-2. 01 - Initialisation, terminaison

Sous Windows, il est nécessaire d'indiquer au système que le processus courant veut utiliser les sockets. Ce n'est pas nécessaire sous Linux. Sous Windows, winsock2 est compatible avec les sockets BSD. Il est donc indispensable de vérifier qu'on a bien la bonne version de sockets sur la machine cible. De plus, Windows exige de signaler la fin d'utilisation des sockets par le processus.

Voici notre premier programme 'réseau'. Pour le moment, il ne fait rien sous unixoïde, mais sous Windows, il vérifie la version des sockets.

/* 01.c */

#ifdef __cplusplus
#error Be sure you are using a C compiler...
#endif

#if defined (WIN32)

#include <winsock2.h>

#elif defined (linux) || defined (_POSIX_VERSION) || defined (_POSIX2_C_VERSION)\
 || defined (_XOPEN_VERSION)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> /* close */

#define INVALID_SOCKET -1
#define SOCKET_ERROR -1

#define closesocket(s) close (s)
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;

#else
#error not defined for this platform
#endif

#include <stdio.h>
#include <stdlib.h>

/* macros ============================================================== */
/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* private data ======================================================== */
/* private functions =================================================== */
/* entry point ========================================================= */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
int main (void)
{
   int ret;

#if defined (WIN32)
   WSADATA wsa_data;
   int err = WSAStartup (MAKEWORD (2, 2), &wsa_data);

   if (!err)
   {
      puts ("WIN: winsock2: OK");
#else
   int err;
#endif

   /* to be continued ... */

#if defined (WIN32)
      WSACleanup ();
   }
#endif

   if (err)
   {
      ret = EXIT_FAILURE;
   }
   else
   {
      ret = EXIT_SUCCESS;
   }
   return ret;
}
Sortie attendue :

WIN: winsock2: OK

VII-D-3. 02 - Création, suppression de sockets

Ce programme ne fait rien de visible. Il se contente de créer un socket en mode 'Internet' (IP), puis de le fermer proprement.

Nouvelles fonctions : socket(), closesocket()

/* 02.c */

#ifdef __cplusplus
#error Be sure you are using a C compiler...
#endif

#if defined (WIN32)

#include <winsock2.h>

#elif defined (linux) || defined (_POSIX_VERSION) || defined (_POSIX2_C_VERSION)\
 || defined (_XOPEN_VERSION)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> /* close */

#define INVALID_SOCKET -1
#define SOCKET_ERROR -1

#define closesocket(s) close (s)
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;

#else
#error not defined for this platform
#endif

#include <stdio.h>
#include <stdlib.h>

/* macros ============================================================== */
/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* private data ======================================================== */
/* private functions =================================================== */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static int app (void)
{
   int err = 0;

   /* open a socket in TCP/IP mode. */
   SOCKET sock = socket (AF_INET, SOCK_STREAM, 0);

   if (sock != INVALID_SOCKET)
   {
      printf ("socket %d is now opened in TCP/IP mode\n", sock);

      {
         int sock_err;

         /* to be continued ... */

         printf ("closing socket %d...\n", sock);

         /* close the socket. */
         sock_err = closesocket (sock), sock = INVALID_SOCKET;

         printf ("the socket is now closed\n");

         if (sock_err)
         {
            perror ("socket.close");
            err = 1;
         }
      }
   }
   else
   {
      perror ("socket.open");
      err = 1;
   }

   return err;
}

/* entry point ========================================================= */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
int main (void)
{
   int ret;
#if defined (WIN32)
   WSADATA wsa_data;
   int err = WSAStartup (MAKEWORD (2, 2), &wsa_data);

   if (!err)
   {
      puts ("WIN: winsock2: OK");
#else
   int err;
#endif

      err = app ();

#if defined (WIN32)
      WSACleanup ();
   }
#endif

   if (err)
   {
      ret = EXIT_FAILURE;
   }
   else
   {
      ret = EXIT_SUCCESS;
   }
   return ret;
}
Exemple de sortie attendue

WIN: winsock2: OK
socket 44 is now opened in TCP/IP mode
closing socket 44...
the socket is now closed

VII-D-4. 03 - Mini serveur : Mise en écoute du port 23

Le port 23 est le port Telnet par défaut. La encore, rien de spectaculaire, si ce n'est que si la machine dispose d'un pare-feu (comme Zone Alarm, par exemple), celui-ci signale que le programme veut agir en tant que serveur sur le port SMTP. C'est la conséquence de l'exécution de la fonction 'listen()'. Si le pare-feu demande confirmation, l'exécution du programme est suspendue jusqu'à ce que la confirmation soit validée. Ensuite, comme il n'y a pour le moment rien d'autre à faire, le programme se termine.

warning Attention, sous unixoïde, le port 23 n'est accessible que si on est en root.
Nouvelles fonctions : bind(), listen(), htons(), htonl().

/* 03s.c */
#ifdef __cplusplus
#error Be sure you are using a C compiler...
#endif

#if defined (WIN32)

#include <winsock2.h>

#elif defined (linux) || defined (_POSIX_VERSION) || defined (_POSIX2_C_VERSION)\
 || defined (_XOPEN_VERSION)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> /* close */

#define INVALID_SOCKET -1
#define SOCKET_ERROR -1

#define closesocket(s) close (s)
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;

#else
#error not defined for this platform
#endif

#include <stdio.h>
#include <stdlib.h>

/* macros ============================================================== */

#define TELNET 23

/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* private data ======================================================== */
/* private functions =================================================== */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static int app (void)
{
   int err = 0;

   /* open a socket in TCP/IP mode. */
   SOCKET sock = socket (AF_INET, SOCK_STREAM, 0);

   if (sock != INVALID_SOCKET)
   {
      printf ("socket %d is now opened in TCP/IP mode\n", sock);

      /* we want to listen on the TELNET port */
      {
         int sock_err;
         /* assign the listening port */
         SOCKADDR_IN sin =
         {0};

         /* automatic IP address */
         sin.sin_addr.s_addr = htonl (INADDR_ANY);

         /* protocol family (IP) */
         sin.sin_family = AF_INET;

         /* listening port */
         sin.sin_port = htons (TELNET);

         /* bind */
         sock_err = bind (sock, (SOCKADDR *) &sin, sizeof sin);

         if (sock_err != SOCKET_ERROR)
         {
            /* start listening (server mode) */
            sock_err = listen (sock, 5);

            printf ("listening on port %d...\n", TELNET);

            if (sock_err != SOCKET_ERROR)
            {
               /* to be continued ... */
            }
            else
            {
               err = 1;
            }
         }
         else
         {
            err = 1;
         }

         printf ("closing socket %d...\n", sock);

         /* close the socket. */
         sock_err = closesocket (sock), sock = INVALID_SOCKET;

         printf ("the socket is now closed\n");

         if (sock_err)
         {
            perror ("socket.close");
            err = 1;
         }
      }
   }
   else
   {
      perror ("socket.open");
      err = 1;
   }

   return err;
}

/* entry point ========================================================= */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
int main (void)
{
   int ret;
#if defined (WIN32)
   WSADATA wsa_data;
   int err = WSAStartup (MAKEWORD (2, 2), &wsa_data);

   if (!err)
   {
      puts ("WIN: winsock2: OK");
#else
   int err;
#endif

      err = app ();

#if defined (WIN32)
      WSACleanup ();
   }
#endif

   if (err)
   {
      ret = EXIT_FAILURE;
   }
   else
   {
      ret = EXIT_SUCCESS;
   }
   return ret;
}
Exemple de sortie attendue

WIN: winsock2: OK
socket 44 is now opened in TCP/IP mode
listening on port 23...
closing socket 44...
the socket is now closed

VII-D-5. 04 - Mini serveur : Connexion du client/ Déconnexion du client

Le serveur va maintenant se bloquer en attente de connexion d'un client. On peut alors lancer un client Telnet sur cette machine (ou une autre sur le même réseau) avec le port 23. On constate alors, sur le client, que la connection est immédiatement perdue. Normal, car le serveur a renvoyé une deconnexion immédiate du socket client.

Nouvelles fonctions : accept(), shutdown(), inet_ntoa().

/* 04s.c */
#ifdef __cplusplus
#error Be sure you are using a C compiler...
#endif

#if defined (WIN32)

#include <winsock2.h>

#elif defined (linux) || defined (_POSIX_VERSION) || defined (_POSIX2_C_VERSION)\
 || defined (_XOPEN_VERSION)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> /* close */

#define INVALID_SOCKET -1
#define SOCKET_ERROR -1

#define closesocket(s) close (s)
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;

#else
#error not defined for this platform
#endif

#include <stdio.h>
#include <stdlib.h>

/* macros ============================================================== */

#define TELNET 23

/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* private data ======================================================== */
/* private functions =================================================== */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static int app (void)
{
   int err = 0;

   /* open a socket in TCP/IP mode. */
   SOCKET sock = socket (AF_INET, SOCK_STREAM, 0);

   if (sock != INVALID_SOCKET)
   {
      printf ("socket %d is now opened in TCP/IP mode\n", sock);

      /* we want to listen on the TELNET port */
      {
         int sock_err;
         /* assign the listening port */
         SOCKADDR_IN sin =
         {0};

         /* automatic IP address */
         sin.sin_addr.s_addr = htonl (INADDR_ANY);

         /* protocol family (IP) */
         sin.sin_family = AF_INET;

         /* listening port */
         sin.sin_port = htons (TELNET);

         /* bind */
         sock_err = bind (sock, (SOCKADDR *) &sin, sizeof sin);

         if (sock_err != SOCKET_ERROR)
         {
            /* start listening (server mode) */
            sock_err = listen (sock, 5);

            printf ("listening on port %d...\n", TELNET);

            if (sock_err != SOCKET_ERROR)
            {
               /* wait for a client connection */
               printf ("waiting for a client connection on port %d...\n", TELNET);
               {
                  SOCKADDR_IN csin =
                  {0};
                  int recsize = (int) sizeof csin;
                  SOCKET csock = accept (sock, (SOCKADDR *) & csin, &recsize);

                  if (csock != INVALID_SOCKET)
                  {
                     printf ("client connected with socket %d from %s:%d\n"
                             ,csock
                             ,inet_ntoa (csin.sin_addr)
                             ,htons (csin.sin_port));

                     /* to be continued ... */

                     shutdown (csock, 2);
                     printf ("closing client socket %d...\n", csock);
                     closesocket (csock), csock = INVALID_SOCKET;

                  }
                  else
                  {
                     perror ("socket.accept");
                     err = 1;
                  }
               }
            }
            else
            {
               perror ("socket.listen");
               err = 1;
            }
         }
         else
         {
            perror ("socket.bind");
            err = 1;
         }

         printf ("closing socket %d...\n", sock);

         /* close the socket. */
         sock_err = closesocket (sock), sock = INVALID_SOCKET;

         printf ("the socket is now closed\n");

         if (sock_err)
         {
            perror ("socket.close");
            err = 1;
         }
      }
   }
   else
   {
      perror ("socket.open");
      err = 1;
   }

   return err;
}

/* entry point ========================================================= */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
int main (void)
{
   int ret;
#if defined (WIN32)
   WSADATA wsa_data;
   int err = WSAStartup (MAKEWORD (2, 2), &wsa_data);

   if (!err)
   {
      puts ("WIN: winsock2: OK");
#else
   int err;
#endif

      err = app ();

#if defined (WIN32)
      WSACleanup ();
   }
#endif

   if (err)
   {
      ret = EXIT_FAILURE;
   }
   else
   {
      ret = EXIT_SUCCESS;
   }
   return ret;
}
Exemple de sortie attendue

WIN: winsock2: OK
socket 44 is now opened in TCP/IP mode
listening on port 23...
waiting for a client connection on port 23...
client connected with socket 48 from 192.168.0.17:1290
closing client socket 48...
closing socket 44...
the socket is now closed

VII-D-6. 05 - Reception d'un caractère puis fermeture

Une fois que le client Telnet est lance, le serveur se bloque sur l'attente d'un bloc de donné. Le client telnet envoyant en temps réel toutes la frappes du clavier, un seul caractère suffit à débloquer le serveur qui envoi alors une commande de deconnexion.

Nouvelles fonctions : recv().

/* 05s.c */
#ifdef __cplusplus
#error Be sure you are using a C compiler...
#endif

#if defined (WIN32)

#include <winsock2.h>

#elif defined (linux) || defined (_POSIX_VERSION) || defined (_POSIX2_C_VERSION)\
 || defined (_XOPEN_VERSION)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> /* close */

#define INVALID_SOCKET -1
#define SOCKET_ERROR -1

#define closesocket(s) close (s)
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;

#else
#error not defined for this platform
#endif

#include <stdio.h>
#include <stdlib.h>

/* macros ============================================================== */

#define TELNET 23

/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* private data ======================================================== */
/* private functions =================================================== */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static int app (void)
{
   int err = 0;

   /* open a socket in TCP/IP mode. */
   SOCKET sock = socket (AF_INET, SOCK_STREAM, 0);

   if (sock != INVALID_SOCKET)
   {
      printf ("socket %d is now opened in TCP/IP mode\n", sock);

      /* we want to listen on the TELNET port */
      {
         int sock_err;
         /* assign the listening port */
         SOCKADDR_IN sin =
         {0};

         /* automatic IP address */
         sin.sin_addr.s_addr = htonl (INADDR_ANY);

         /* protocol family (IP) */
         sin.sin_family = AF_INET;

         /* listening port */
         sin.sin_port = htons (TELNET);

         /* bind */
         sock_err = bind (sock, (SOCKADDR *) &sin, sizeof sin);

         if (sock_err != SOCKET_ERROR)
         {
            /* start listening (server mode) */
            sock_err = listen (sock, 5);

            printf ("listening on port %d...\n", TELNET);

            if (sock_err != SOCKET_ERROR)
            {
               /* wait for a client connection */
               printf ("waiting for a client connection on port %d...\n", TELNET);

               {
                  SOCKADDR_IN csin =
                  {0};
                  int recsize = (int) sizeof csin;
                  SOCKET csock = accept (sock, (SOCKADDR *) & csin, &recsize);

                  if (csock != INVALID_SOCKET)
                  {
                     printf ("client connected with socket %d from %s:%d\n"
                             ,csock
                             ,inet_ntoa (csin.sin_addr)
                             ,htons (csin.sin_port));

                     {
                        /* wait for the receive of a data block (string expected, hence + 1) */
                        unsigned char data[128 + 1];
                        sock_err = recv (csock, data, (int) sizeof data - 1, 0);

                        if (sock_err != SOCKET_ERROR)
                        {
                           size_t nb_rec = (size_t) sock_err;
                           /* convert to string */
                           data[nb_rec] = 0;
                           printf ("%u byte%s received:\n%s\n"
                                   ,(unsigned) nb_rec
                                   ,nb_rec > 1 ? "s" : ""
                                   ,data);
                           fflush (stdout);

                           /* to be continued ... */
                        }
                        else
                        {
                           perror ("socket.recv");
                           err = 1;
                        }
                     }
                     shutdown (csock, 2);
                     printf ("closing client socket %d...\n", csock);
                     closesocket (csock), csock = INVALID_SOCKET;

                  }
                  else
                  {
                     perror ("socket.accept");
                     err = 1;
                  }
               }
            }
            else
            {
               perror ("socket.listen");
               err = 1;
            }
         }
         else
         {
            perror ("socket.bind");
            err = 1;
         }

         printf ("closing socket %d...\n", sock);

         /* close the socket. */
         sock_err = closesocket (sock), sock = INVALID_SOCKET;

         printf ("the socket is now closed\n");

         if (sock_err)
         {
            perror ("socket.close");
            err = 1;
         }
      }
   }
   else
   {
      perror ("socket.open");
      err = 1;
   }

   return err;
}

/* entry point ========================================================= */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
int main (void)
{
   int ret;
#if defined (WIN32)
   WSADATA wsa_data;
   int err = WSAStartup (MAKEWORD (2, 2), &wsa_data);

   if (!err)
   {
      puts ("WIN: winsock2: OK");
#else
   int err;
#endif

      err = app ();

#if defined (WIN32)
      WSACleanup ();
   }
#endif

   if (err)
   {
      ret = EXIT_FAILURE;
   }
   else
   {
      ret = EXIT_SUCCESS;
   }
   return ret;
}
Exemples de sortie attendues

L'utilisateur tape 'a' sur la console du client:

WIN: winsock2: OK
socket 44 is now opened in TCP/IP mode
listening on port 23...
waiting for a client connection on port 23...
client connected with socket 48 from 192.168.0.17:1299
1 byte received:
a
closing client socket 48...
closing socket 44...
the socket is now closed

VII-D-7. 06 - Mini serveur : Reception d'un bloc de données puis fermeture sur ESC

L'ajout d'une boucle sur receive() permet de recevoir plusieurs caractères. Si on reçoit ESC (27), la boucle est rompue et une demande de deconnexion est émise vers le client. Le serveur s'arrête.


VII-D-7-a. Pseudo-code

BEGIN
   sock := socket()
   bind(sock, ALL_IP, 23)
   listen(sock, 5)
   csock := accept(sock, csin)
   REPEAT
      recv(csock, data)
      send(csock, "OK")
   UNTIL data[0] = ESC
   closesocket(csock)
   closesocket(sock)
END
Ce fonctionnement est simple, mais il n'accepte qu'une seule connexion. Dès qu'elle se termine, le serveur s'arrête. Les fonctions accept() et recv() sont bloquantes.


VII-D-7-b. Code

/* 06s.c */
#ifdef __cplusplus
#error Be sure you are using a C compiler...
#endif

#if defined (WIN32)

#include <winsock2.h>

#elif defined (linux) || defined (_POSIX_VERSION) || defined (_POSIX2_C_VERSION)\
 || defined (_XOPEN_VERSION)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>             /* close */

#define INVALID_SOCKET -1
#define SOCKET_ERROR -1

#define closesocket(s) close (s)
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;


#else
#error not defined for this platform
#endif

#include <stdio.h>
#include <stdlib.h>

/* macros ============================================================== */

#define TELNET 23

#define ESC 27

/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* private data ======================================================== */
/* private functions =================================================== */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static int app (void)
{
   int err = 0;

   /* open a socket in TCP/IP mode. */
   SOCKET sock = socket (AF_INET, SOCK_STREAM, 0);

   if (sock != INVALID_SOCKET)
   {
      printf ("socket %d is now opened in TCP/IP mode\n", sock);

      /* we want to listen on the TELNET port */
      {
         int sock_err;
         /* assign the listening port */
         SOCKADDR_IN sin =
         {0};

         /* automatic IP address */
         sin.sin_addr.s_addr = htonl (INADDR_ANY);

         /* protocol family (IP) */
         sin.sin_family = AF_INET;

         /* listening port */
         sin.sin_port = htons (TELNET);

         /* bind */
         sock_err = bind (sock, (SOCKADDR *) &sin, sizeof sin);

         if (sock_err != SOCKET_ERROR)
         {
            /* start listening (server mode) */
            sock_err = listen (sock, 5);

            printf ("listening on port %d...\n", TELNET);

            if (sock_err != SOCKET_ERROR)
            {
               /* wait for a client connection */
               printf ("waiting for a client connection on port %d...\n", TELNET);

               {
                  SOCKADDR_IN csin =
                  {0};
                  int recsize = (int) sizeof csin;
                  SOCKET csock = accept (sock, (SOCKADDR *) &csin, &recsize);

                  if (csock != INVALID_SOCKET)
                  {
                     printf ("client connected with socket %d from %s:%d\n"
                             ,csock
                             ,inet_ntoa (csin.sin_addr)
                             ,htons (csin.sin_port));
                     {
                        int end = 0;
                        do
                        {
                           /* wait for the receive of a data block (string expected, hence + 1) */
                           unsigned char data[128 + 1];
                           sock_err = recv (csock, data, (int) sizeof data - 1, 0);

                           if (sock_err != SOCKET_ERROR)
                           {
                              size_t nb_rec = (size_t) sock_err;
                              /* convert to string */
                              data[nb_rec] = 0;
                              printf ("%u byte%s received:\n%s\n"
                                      ,(unsigned) nb_rec
                                      ,nb_rec > 1 ? "s" : ""
                                      ,data);
                              fflush (stdout);

                              /* to be continued ... */
                              if (data[0] == ESC)
                              {
                                 end = 1;
                              }
                           }
                           else
                           {
                              perror ("socket.recv");
                              err = 1;
                              end = 1;
                           }
                        }
                        while (!end);
                     }
                     shutdown (csock, 2);
                     printf ("closing client socket %d...\n", csock);
                     closesocket (csock), csock = INVALID_SOCKET;

                  }
                  else
                  {
                     perror ("socket.accept");
                     err = 1;
                  }
               }
            }
            else
            {
               perror ("socket.listen");
               err = 1;
            }
         }
         else
         {
            perror ("socket.bind");
            err = 1;
         }

         printf ("closing socket %d...\n", sock);

         /* close the socket. */
         sock_err = closesocket (sock), sock = INVALID_SOCKET;

         printf ("the socket is now closed\n");

         if (sock_err)
         {
            perror ("socket.close");
            err = 1;
         }
      }
   }
   else
   {
      perror ("socket.open");
      err = 1;
   }

   return err;
}

/* entry point ========================================================= */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
int main (void)
{
   int ret;
#if defined (WIN32)
   WSADATA wsa_data;
   int err = WSAStartup (MAKEWORD (2, 2), &wsa_data);

   if (!err)
   {
      puts ("WIN: winsock2: OK");
#else
      int err;
#endif

      err = app ();

#if defined (WIN32)
      WSACleanup ();
   }
#endif

   if (err)
   {
      ret = EXIT_FAILURE;
   }
else
   {
      ret = EXIT_SUCCESS;
   }
   return ret;
}

VII-D-8. 07 - Mini serveur : Reception d'un bloc de données puis deconnexion sur ESC

L'ajout d'une boucle sur accept() permet de recevoir plusieurs demandes de connexions à la suite. Le serveur ne s'arrête jamais.


VII-D-8-a. Pseudo-code

BEGIN
   sock := socket()
   bind(sock, ALL_IP, 23)
   listen(sock, 5)
   DO
      csock := accept(sock, csin)
      closesocket(csock)
   FOREVER
   closesocket(sock)
END
Afin de simplifier l'organisaton du code, les boucles sont implémentées dans les fonctions clients() et client().

FUNCTION client(csock:SOCKET)
BEGIN
   REPEAT
      recv(csock, data)
      send(csock, "OK")
   UNTIL data[0] = ESC
   closesocket(csock)
END

FUNCTION clients(sock:SOCKET)
BEGIN
   DO
      csock := accept(sock, csin)
      client(csock)
   FOREVER
END

BEGIN
   sock := socket()
   bind(sock, ALL_IP, 23)
   listen(sock, 5)
   clients(sock)
   closesocket(sock)
END
Ce fonctionnement reste simple, mais il n'accepte qu'une seule connexion à la fois. Par contre, dès qu'elle se termine, le serveur est près à accepter une nouvelle connexion. Les fonctions accept() et recv() sont bloquantes. Si une demande de connexion se fait pendant que le serveur traite un client, la connexion est établie, mais le serveur ne traite pas les données tant que la connexion courante n'est pas terminée. listen(..., 5) permet de traiter jusqu'à 5 demandes simultanées.


VII-D-8-b. Code

/* 7s.c */
#ifdef __cplusplus
#error Be sure you are using a C compiler...
#endif

#if defined (WIN32)

#include <winsock2.h>

#elif defined (linux) || defined (_POSIX_VERSION) || defined (_POSIX2_C_VERSION)\
 || defined (_XOPEN_VERSION)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>             /* close */

#define INVALID_SOCKET -1
#define SOCKET_ERROR -1

#define closesocket(s) close (s)
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;

#else
#error not defined for this platform
#endif

#include <stdio.h>
#include <stdlib.h>

/* macros ============================================================== */

#define TELNET 23

/* we want to listen to the TELNET port */
#define PORT TELNET

#define ESC 27

/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */

struct cli
{
   SOCKADDR_IN sin;
   int recsize;
   SOCKET sock;
   int err;
};

/* private data ======================================================== */
/* private functions =================================================== */

/* thread-like client function */
static void *client (void *p_data)
{
   struct cli *p_cli = p_data;
   if (p_cli != NULL)
   {
      int end = 0;
      do
      {
         /* wait for the receive of a data block */
         unsigned char data[128];
         int sock_err = recv (p_cli->sock, data, (sizeof data - 1), 0);

         if (sock_err != SOCKET_ERROR)
         {
            size_t nb_rec = sock_err;
            if (nb_rec > 0)
            {
               /* convert to string */
               data[nb_rec] = 0;
               printf ("%u byte%s received:\n'%s'\n",
                       (unsigned) nb_rec, nb_rec > 1 ? "s" : "", data);
               fflush (stdout);

               if (data[0] == ESC)
               {
                  end = 1;
               }
               else
               {
                  /* send some YES-TO-ALL answer */
                  char const response[] = "OK\n";
                  send (p_cli->sock, response, strlen (response), 0);
               }
            }
            else
            {
               puts ("client is disconnected");
               end = 1;
            }
         }
         else
         {
            perror ("socket.recv");
            p_cli->err = 1;
            end = 1;
         }
      }
      while (!end);
      shutdown (p_cli->sock, 2);
      printf ("closing client socket %d...\n", p_cli->sock);
      closesocket (p_cli->sock), p_cli->sock = INVALID_SOCKET;
   }

   return NULL;
}

static int clients (SOCKET sock)
{
   int err = 0;
   int end = 0;
   do
   {
      /* wait for a client connection */
      printf ("waiting for a client connection on port %d...\n", PORT);

      {
         /* create a new client context */
         struct cli *p_cli = malloc (sizeof *p_cli);
         if (p_cli != NULL)
         {
            p_cli->recsize = (int) sizeof p_cli->sin;
            p_cli->sock =
               accept (sock, (SOCKADDR *) &p_cli->sin, &p_cli->recsize);

            if (p_cli->sock != INVALID_SOCKET)
            {
               printf
                  ("client connected with socket %d from %s:%d\n",
                   p_cli->sock, inet_ntoa (p_cli->sin.sin_addr),
                   htons (p_cli->sin.sin_port));

               client (p_cli);

            }
            else
            {
               perror ("socket.accept");
               err = 1;
            }

            /* free the client context */
            free (p_cli), p_cli = NULL;
         }
         else
         {
            printf ("client creation failed : memory error\n");
         }
      }
   }
   while (!end);
   return err;
}

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static int app (void)
{
   int err = 0;

   /* open a socket in TCP/IP mode. */
   SOCKET sock = socket (AF_INET, SOCK_STREAM, 0);

   if (sock != INVALID_SOCKET)
   {
      printf ("socket %d is now opened in TCP/IP mode\n", sock);

      /* we want to listen on the TELNET port */
      {
         int sock_err;
         /* assign the listening port */
         SOCKADDR_IN sin = { 0 };

         /* automatic IP address */
         sin.sin_addr.s_addr = htonl (INADDR_ANY);

         /* protocol family (IP) */
         sin.sin_family = AF_INET;

         /* listening port */
         sin.sin_port = htons (PORT);

         /* bind */
         sock_err = bind (sock, (SOCKADDR *) &sin, sizeof sin);

         if (sock_err != SOCKET_ERROR)
         {
            /* start listening (server mode) */
            sock_err = listen (sock, 5);

            printf ("listening on port %d...\n", PORT);

            if (sock_err != SOCKET_ERROR)
            {
               err = clients (sock);
            }
            else
            {
               perror ("socket.listen");
               err = 1;
            }
         }
         else
         {
            perror ("socket.bind");
            err = 1;
         }

         printf ("closing socket %d...\n", sock);

         /* close the socket. */
         sock_err = closesocket (sock), sock = INVALID_SOCKET;

         printf ("the socket is now closed\n");

         if (sock_err)
         {
            perror ("socket.close");
            err = 1;
         }
      }
   }
   else
   {
      perror ("socket.open");
      err = 1;
   }

   return err;
}

/* entry point ========================================================= */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
int main (void)
{
   int ret;
#if defined (WIN32)
   WSADATA wsa_data;
   int err = WSAStartup (MAKEWORD (2, 2), &wsa_data);

   if (!err)
   {
      puts ("WIN: winsock2: OK");
#else
      int err;
#endif

      err = app ();

#if defined (WIN32)
      WSACleanup ();
   }
#endif

   if (err)
   {
      ret = EXIT_FAILURE;
   }
   else
   {
      ret = EXIT_SUCCESS;
   }

   return ret;
}
Pour réaliser une serveur plus conséquent, on peut créer un thread (ou un processus) par client connecté.


VII-D-9. 08 - Serveur-multi-clients : Reception d'un bloc de données puis deconnexion sur ESC

En rendant autonome la fonction 'client()' et en la transformant en thread, le serveur devient multiclient. En effet, après connexion d'un client, au lieu de rester bloqué sur la réception, le serveur se bloque à nouveau sur l'attente de connexion (accept()). Ca permet de recevoir et de traiter plusieurs demandes de connexions en même temps. Le serveur ne s'arrête jamais.


VII-D-9-a. Pseudo-code

STRUCTURE CLIENT
BEGIN
   thread:PTHREAD
   sock:SOCKET
END

THREAD client(data:GENERIC):GENERIC
BEGIN
   cli := CLIENT(data)
   WITH cli
   BEGIN
      REPEAT
         recv(sock, data)
         send(sock, "OK")
      UNTIL data[0] = ESC
      closesocket(sock)
   END
   client := NIL
END

FUNCTION clients(sock:SOCKET)
BEGIN
   DO
      csock := accept(sock, csin)
      pthread_create(cli.thread, client, cli.csock)
   FOREVER
END

BEGIN
   sock := socket()
   bind(sock, ALL_IP, 23)
   listen(sock, 5)
   clients(sock)
   closesocket(sock)
END
Ce fonctionnement reste simple, et il accepte plusieurs connexions à la fois. Les fonctions accept() et recv() sont bloquantes, mais elles sont maintenant dans des threads différents. Dès qu'un client est crée, la fonction 'clients' revient en attente de connexion.


VII-D-9-b. Code

/* 08s.c */
#ifdef __cplusplus
#error Be sure you are using a C compiler...
#endif

#if defined (WIN32)

#include <winsock2.h>

#elif defined (linux) || defined (_POSIX_VERSION) || defined (_POSIX2_C_VERSION)\
 || defined (_XOPEN_VERSION)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>             /* close */

#define INVALID_SOCKET -1
#define SOCKET_ERROR -1

#define closesocket(s) close (s)
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;

#else
#error not defined for this platform
#endif

#include <stdio.h>
#include <stdlib.h>

#include <pthread.h>

/* macros ============================================================== */

#define TELNET 23

/* we want to listen to the TELNET port */
#define PORT TELNET

#define ESC 27

/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */

struct cli
{
   pthread_t thread;
   SOCKADDR_IN sin;
   int recsize;
   SOCKET sock;
   int err;
};

/* private data ======================================================== */
/* private functions =================================================== */

/* thread client function */
static void *client (void *p_data)
{
   struct cli *p_cli = p_data;
   if (p_cli != NULL)
   {
      int end = 0;
      do
      {
         /* wait for the receive of a data block */
         unsigned char data[128];
         int sock_err = recv (p_cli->sock, data, (sizeof data - 1), 0);

         if (sock_err != SOCKET_ERROR)
         {
            size_t nb_rec = sock_err;
            if (nb_rec > 0)
            {
               /* convert to string */
               data[nb_rec] = 0;
               printf ("%u byte%s received:\n'%s'\n",
                       (unsigned) nb_rec, nb_rec > 1 ? "s" : "", data);
               fflush (stdout);

               if (data[0] == ESC)
               {
                  end = 1;
               }
               else
               {
                  /* send some YES-TO-ALL answer */
                  char const response[] = "OK\n";
                  send (p_cli->sock, response, strlen (response), 0);
               }
            }
            else
            {
               puts ("client is disconnected");
               end = 1;
            }
         }
         else
         {
            perror ("socket.recv");
            p_cli->err = 1;
            end = 1;
         }
      }
      while (!end);
      shutdown (p_cli->sock, 2);
      printf ("closing client socket %d...\n", p_cli->sock);
      closesocket (p_cli->sock), p_cli->sock = INVALID_SOCKET;

      /* the memory is now under the control of the thread */
      free (p_cli), p_cli = NULL;

   }

   return NULL;
}

static int clients (SOCKET sock)
{
   int err = 0;
   int end = 0;
   do
   {
      /* wait for a client connection */
      printf ("waiting for a client connection on port %d...\n", PORT);

      {
         /* create a new client context */
         struct cli *p_cli = malloc (sizeof *p_cli);
         if (p_cli != NULL)
         {
            p_cli->recsize = (int) sizeof p_cli->sin;
            p_cli->sock =
               accept (sock, (SOCKADDR *) &p_cli->sin, &p_cli->recsize);

            if (p_cli->sock != INVALID_SOCKET)
            {
               printf
                  ("client connected with socket %d from %s:%d\n",
                   p_cli->sock, inet_ntoa (p_cli->sin.sin_addr),
                   htons (p_cli->sin.sin_port));

               /* send ...*/
               pthread_create (&p_cli->thread, NULL, client, p_cli);
               /* ... and forget */
               p_cli = NULL;

            }
            else
            {
               perror ("socket.accept");
               err = 1;
            }
         }
         else
         {
            fprintf (stderr, "client creation failed : memory error\n");
         }
      }
   }
   while (!end);
   return err;
}

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static int app (void)
{
   int err = 0;

   /* open a socket in TCP/IP mode. */
   SOCKET sock = socket (AF_INET, SOCK_STREAM, 0);

   if (sock != INVALID_SOCKET)
   {
      printf ("socket %d is now opened in TCP/IP mode\n", sock);

      /* we want to listen on the TELNET port */
      {
         int sock_err;
         /* assign the listening port */
         SOCKADDR_IN sin = { 0 };

         /* automatic IP address */
         sin.sin_addr.s_addr = htonl (INADDR_ANY);

         /* protocol family (IP) */
         sin.sin_family = AF_INET;

         /* listening port */
         sin.sin_port = htons (PORT);

         /* bind */
         sock_err = bind (sock, (SOCKADDR *) &sin, sizeof sin);

         if (sock_err != SOCKET_ERROR)
         {
            /* start listening (server mode) */
            sock_err = listen (sock, 5);

            printf ("listening on port %d...\n", PORT);

            if (sock_err != SOCKET_ERROR)
            {
               err = clients (sock);
            }
            else
            {
               perror ("socket.listen");
               err = 1;
            }
         }
         else
         {
            perror ("socket.bind");
            err = 1;
         }

         printf ("closing socket %d...\n", sock);

         /* close the socket. */
         sock_err = closesocket (sock), sock = INVALID_SOCKET;

         printf ("the socket is now closed\n");

         if (sock_err)
         {
            perror ("socket.close");
            err = 1;
         }
      }
   }
   else
   {
      perror ("socket.open");
      err = 1;
   }

   return err;
}

/* entry point ========================================================= */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
int main (void)
{
   int ret;
#if defined (WIN32)
   WSADATA wsa_data;
   int err = WSAStartup (MAKEWORD (2, 2), &wsa_data);

   if (!err)
   {
      puts ("WIN: winsock2: OK");
#else
      int err;
#endif

      err = app ();

#if defined (WIN32)
   WSACleanup ();
   }
#endif

   if (err)
   {
      ret = EXIT_FAILURE;
   }
   else
   {
      ret = EXIT_SUCCESS;
   }

   system ("pause");

   return ret;
}

VII-D-10. Conclusion

Les base de la réalisation d'un mini serveur Telnet sont jetées. Il suffit maintenant de réconstituer les lignes de commandes, de les transmettre à un interpréteur de commande etc.

Exemple de serveur multiclient simple avec client qui demande l'heure courante (now) ou quitte (quit) :


VII-E. Transmettre du texte par sockets

Un socket permet de transmettre des données 'brutes' sous la forme de blocs d'octets caractérisés dans le code par une adresse et une longueur (les paramètres de send(), par exemple).

Des informations de type texte peuvent évidemment se ramener à une structure adresse, longueur. Mais il convient de le faire proprement et en respectant certaines conventions telles que

  • Le jeu de caractères est ASCII
  • Pas de 0 en ligne
  • Les chaines sont terminées par une marque de fin de ligne telle que CR, LF, CRLF ou autre, définie par protocole utilisé.
Le respect de ces conventions permet d'élaborer des fonctions d'émission et reception de texte correctes et efficaces.

warning Attention : la technique utilisée dans ce qui suit n'est valide que sous Windows (winsock2).

VII-E-1. Emission de petits blocs (cas simple)

Par 'petit blocs', on entend des chaines dont la longueur est inférieure à une trame (quelques centaines d'octets). Le principe est donc de passer l'adresse du premier élément du tableau de char qui contient la chaine, et sa longueur mesurée ici avec strlen().

La fonction send() retourne le nombre d'octets effectivement transmis

char *text = "Hello world\n";
   int n = send (sock, text, strlen (text), 0);
   if (n >= 0)
   {
      /* debug */
      printf ("%d octet%s sent\n", n, n != 1 ? "s" : "");
   }

VII-E-2. Reception de petits blocs

La réception doit tenir compte du fait qu'en C, les chaines sont terminées par un 0. Il faut donc, non seulement placer ce 0 "à la main" (puisqu'il n'est pas transmis), mais en plus prévoir de la place pour lui dans la chaine de reception.

La fonction recv() retourne le nombre d'octets effectivement reçus. Cette valeur est <= à la taille passée en paramètre. Elle sert à positionner le 0. Une valeur < 0 signale une erreur. 0 signifie 'rien reçu' dans le cas d'un socket non blocant. (ne devrait pas exister si le socket est blocant).

char text[128]; /* taille arbitraire */
   int n = recv (sock, text, sizeof text - 1, 0);
   if (n > 0)
   {
      text[n] = 0;

      /* debug */
      printf ("received : '%s'\n", text);
   }

VII-E-3. Emission de gros blocs (cas général)

Pour émettre un gros bloc, il faut utiliser une technique de découpage si on ne veut pas perdre de données. Etant donné qu'en général on ne sait pas quelle est la valeur maximale d'une trame sur la machine, le mieux est de concevoir du code auto-adaptatif. Pour cela, on utilise une information capitale qui est la valeur retournée par send(). En effet, la valeur retournée est le nombre d'octets effectivement émis.

Par exemple, si la trame maximale fait 1024 octets et qu'on demande à en émettre 2000, send() va retourner 1024. A nous de créer un algorithme qui tient compte de ce résultat et envoie la suite des données.

/* non teste */
   size_t const len = strlen (text);
   size_t sent = 0;

   while (sent != len)
   {
      int n = send (socket, text + sent, len - sent, 0);

      if (n >=0)
      {
         sent += n;
      }
      else
      {
         /* erreur de transmission */
      }
   }
Cette technique est générale et couvre évidemment le cas des petits blocs. Il serait intéressant de placer ce code dans une fonction.


VII-E-4. Réception de gros blocs

La réception assez simple. Il suffit de stocker les blocs reçus et de les passer à une fonction de traitement des données.

Etant donné que l'émission peut avoir découpé les informations en trames plus petites que les informations à transmettre, il faut veiller à ce que les données soient 'réassemblées' sous la forme de lignes cohérentes, c'est à dire terminées par un '\n' si on cherche à interpéter les données reçues.

Les données reçues après un '\n' ne doivent pas être éliminées, mais sont le début de la igne suivante.

Un mécanisme de réconstitution des lignes doit donc être mis en place en se basant sur la marque de fin de ligne. Si le programme ne fait que router les donnée (par exemple, un serveur de connexion client/client ou une transmission de fichier texte), il pourra se contenter de retransmettre les données inchangées.


VII-F. Etude de cas : transmettre un fichier texte.


VII-F-1. Spécifications

Soit à réaliser un logiciel qui transmet un fichier nommé "fichier.txt" contenant un nombre non spécifié de lignes de textes correctement formées (CR, LF, CRLF ou LFCR).

Nota : l'accent est mis sur le protocole et l'integrité des données, et non sur les fonctionnalités qui sont réduites au strict nécessaire.


VII-F-2. Conception

Le logiciel est composé de deux programmes.

  • L'un tourne sur un serveur. Il reçoit la commande de transmission de la part d'un utilisateur et il fourni le fichier à transmettre. C'est le serveur.
  • L'autre tourne sur une machine utilisateur. Il émet la demande de transmission et reçoit le fichier qu'il stocke localement : c'est le client.

VII-F-2-a. Protocole
Afin de garantir l'intégrité du transfert, le protocole suivant est établi

  • Un message est une ligne de texte. La fin de ligne peut être CR, LF, CRLF ou LFCR.
  • Le messages peuvent être des commandes ou des réponses.
  • Le client a l'initiative du transert. C'est lui qui étalit la connexion avec le serveur et qui transmet la commande de récupération de fichier.
  • Le serveur répond alors "OK" ou "KO", Si la réponse est OK, le transfert peut commencer dès que le client passe une commande d'exécution. Le transfert se termine lorsque la dernière ligne a été transmise. La connexion est alors fermée par le serveur.

VII-F-2-a-i. Format des messages

message ::= <texte><EOL>
texte ::= sequence of printable characters (ASCII)
EOL ::= CR | LF | CRLF | LFCR

VII-F-2-a-ii. Commandes
Le client peut passer les commandes suivantes

  • get <fichier>
  • go

VII-F-2-a-iii. Réponses
Le serveur peut émettre les réponses suivantes

  • ok
  • ko

VII-F-2-a-iv. Exemple de dialogue

Client                                        Serveur
                ------ connexion ----------->
                ------ "get fichier.txt" --->
                <----- "ok" -----------------
fopen(..., "w")
                ------ "go" ---------------->
                                             fopen(..., "r")
                                             fgets() send()
                <----- donnees --------------
recv()
fputs()
                                             fopen(..., "r")
                                             fgets() send()
                <----- donnees --------------
recv()
fputs()
                <----- ... ------------------
                                             fclose()
                <----- deconnexion ----------
fclose()
Ce protocole basique devrait fonctionner, mais le fait de compter sur la déconnexion pour fermer le fichier en écriture est un peu risqué. Il y a plusieurs façons d'améliorer le protocole :

  • Transmettre le nombre de lignes attendues et fermer quand la dernière ligne a été reçue.
  • Acquitter chaque ligne par un "OK". La dernière est acquittée par un "EOF".

VII-G. Du bon usage de select()


VII-G-1. Introduction

La fonction select() suspend l'exécution de la tache courante. Celle-ci reprend si au moins une des conditions suivantes est vérifiée :

  • Réception sur un flux (sauf Windows) ou un socket.
  • Fin d'émission sur un flux (sauf Windows) ou un socket.
  • Réception d'un message d'erreur sur un flux (sauf Windows) ou un socket.
  • Echéance de temps.
L'analyse de la valeur retournée permet en partie d'identifier l'évènement. (-1 = erreur, 0 = echéance, autre = évènement flux)


VII-G-2. Programmation

Chacune des conditions est programmable individuellement. On utilise pour cela les paramètres de la fonction, qui, il faut le reconnaitre, paraissent un peu étranges au début. En fait tout cela est extrêmement simple et logique :

Le premier paramètre est le plus grand numéro de flux surveillé augmenté de 1.

Le deuxième paramètre est un pointeur sur un objet de type fd_set qui contient la liste des flux entrants surveillés (réception de données)

Le troisième paramètre est un pointeur sur un objet de type fd_set qui contient la liste des flux sortants surveillés (émission de données)

Le quatrième paramètre est un pointeur sur un objet de type fd_set qui contient la liste des flux entrants surveillés (réception d'un message hors bandes : supervision)

Le cinquième parametre est l'adresse d'un objet de type struct timeval qui contient la durée max de la suspension (timeout).


VII-G-3. Exemples d'utilisation

Ces exemples, un peu scolaires, montrent une possiblité à la fois. Lorsqu'un paramètre n'est pas utilisé, on lui donne la valeur 0 ou NULL.


VII-G-3-a. Suspension temporisée
Il suffit de regler la valeur de la temporisation. Attention, pour être portable, celle-ci doit être mise à jour à chaque fois, car il est possible que les valeurs de la structure soient altérées par select().

for (;;)
   {
      /* set the timer to 1.5 second */
      struct timeval timeout;

      timeout.tv_sec = 1; /* 1 s */
      timeout.tv_usec = 5 * 100 * 1000; /* 500 ms */

      int err = select (0, NULL, NULL, NULL, &timeout);

      switch (err)
      {
      case 0:
         /* timeout */
         puts ("timeout");
         break;

      case -1:
         /* error */
         puts ("error");
         break;

      default:
         /* stream event */

         /* - data has been received */

         /* - data has been sent */

         /* - an error has been received */

      }
   }

VII-G-3-b. Surveillance d'un flux en réception
Cet exemple est purement scolaire, car il n'a pas d'intérêt fonctionnel, étant donné que les fonctions recv() et recvfrom() sont blocantes par défaut. Mais il permet de montrer la syntaxe sur un cas simple. Les macros FD_ZERO() et FD_SET() facilitent la manipulation de l'objet fd_set.

for (;;)
   {
      fd_set readfs;

      FD_ZERO (&readfs);      /* clears readfs */
      FD_SET (sock, &readfs); /* adds a stream */

      int err = select (sock + 1, &readfs, NULL, NULL, NULL);

      switch (err)
      {
      case 0:
         /* timeout */
         break;

      case -1:
         /* error */
         puts ("error");
         break;

      default:
         /* stream event */

         /* - data has been received */

         if (FD_ISSET (sock, &readfs))
         {
            char data[128];
            int n = recv (sock, data, sizeof data, 0);

            if (n > 0)
            {
               /* process the received data */
            }
         }

         /* - data has been sent */

         /* - an error has been received */

      }
   }

VII-G-3-c. Surveillance de deux flux en réception
Cet exemple est réel. Il permet de suspendre l'exécution en un endroit précis et unique du logiciel et donc de surveiller la réception de n flux (ici, 2). FD_SETSIZE retourne une valeur correcte mais non optimisée pour le premier paramètre de select().

for (;;)
   {
      fd_set readfs;

      FD_ZERO (&readfs);      /* clears readfs */
      FD_SET (sock_a, &readfs); /* adds a stream */
      FD_SET (sock_b, &readfs); /* adds another stream */

      int err = select (FD_SETSIZE, &readfs, NULL, NULL, NULL);

      switch (err)
      {
      case 0:
         /* timeout */
         break;

      case -1:
         /* error */
         puts ("error");
         break;

      default:
         /* stream event */

         /* - data has been received */

         if (FD_ISSET (sock_a, &readfs))
         {
            char data[128];
            int n = recv (sock_a, data, sizeof data, 0);

            if (n > 0)
            {
               /* process the received data */
            }
         }

         if (FD_ISSET (sock_b, &readfs))
         {
            char data[128];
            int n = recv (sock_b, data, sizeof data, 0);

            if (n > 0)
            {
               /* process the received data */
            }
         }

         /* - data has been sent */

         /* - an error has been received */

      }
   }
info L'usage de FD_SETSIZE pour un serveur surveillant quelques sockets peut s'avérer pénalisant. Dans ce cas, il est préférable de s'en tenir à la définition, c'est à dire la valeur max des sockets surveillés augmentée de 1 :

int max_sock = MAX (sock_a, sock_b);

   select (max_sock + 1, /* ... */);

VII-G-4. Conclusion

Les bases sont posées. Il ne reste plus maintenant qu'à faire jouer son imagination pour combiner les possibilités en fonction des besoins. Au cours de la conception, ne pas oublier les possibilités offertes par les threads.


VII-H. La récupération de l'IP à partir du nom du serveur

A chaque serveur est attribué une ou plusieurs IP publiques sur le réseau mondial Internet. Pour communiquer avec tel ou tel serveur, l'usage des sockets implique de connaitre une de ces IP publiques. On utilise pour ça la fonction gethostbyname() et les éléments de la structure récupérée (h_*).

/* 10.c */

#ifdef __cplusplus
#error Be sure you are using a C compiler...
#endif

#if defined (WIN32)

#include <winsock2.h>

#elif defined (linux) || defined (_POSIX_VERSION) || defined (_POSIX2_C_VERSION)\
 || defined (_XOPEN_VERSION)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>             /* close() */
#include <netdb.h>              /* gethostbyname() */

#define INVALID_SOCKET -1
#define SOCKET_ERROR -1

#define closesocket(s) close (s)
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;

#else
#error not defined for this platform
#endif

#include <stdio.h>
#include <stdlib.h>

/* macros ============================================================== */
/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* private data ======================================================== */
/* private functions =================================================== */
/* entry point ========================================================= */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
int main (void)
{
   int ret;

#if defined (WIN32)
   WSADATA wsa_data;
   int err = WSAStartup (MAKEWORD (2, 2), &wsa_data);

   if (!err)
   {
      puts ("WIN: winsock2: OK");
	#else
	   int err;
	#endif

	   {
	      struct hostent *p = gethostbyname ("www.google.com");

	      if (p != NULL)
	      {
	         struct hostent host = *p;
	/* on est tranquille, on a maintenant une copie stable de l'info. */

	         printf ("IP de %s\n", host.h_name);

	         {
	            int i = 0;
	            char *ip;

	            while ((ip = host.h_addr_list[i]) != NULL)
	            {
	               int j;
	               for (j = 0; j < host.h_length; j++)
	               {
	                  if (j > 0)
	                  {
	                     putchar ('.');
	                  }
	                  printf ("%u", (unsigned) (unsigned char) ip[j]);
	               }
	               putchar ('\n');

	               i++;
	            }
	         }

	      }
	   }

	#if defined (WIN32)
	   WSACleanup ();
	}
	#endif

	if (err)
	{
	   ret = EXIT_FAILURE;
	}
	else
	{
	   ret = EXIT_SUCCESS;
	}

	return ret;
}
Ce qui donne :

WIN: winsock2: OK
IP de www.l.google.com
216.239.59.103
216.239.59.104
216.239.59.147
216.239.59.99

Press ENTER to continue.
On constate que le véritable nom de serveur de google est "www.l.google.com" et que, vu de chez moi, il possède 4 IP publiques.


VII-I. Ebauche d'une bibliothèque sockets portable (psock)


VII-I-1. Interfaces communes



VII-I-2. Sockets UNix (sun)



VII-I-3. Sockets IP (Internet Protocol)



VII-J. Ressources

  • Le site d' IBM
  • Le tutoriel rigolo mais bien fourni de Brian "Beej" Hall (en français)
  • L'excellent tutoriel de Benjamin Roux de Developpez.com.
  • L'excellent logiciel libre multi-plateformes d'analyse de réseau WireShark (ex-EtherReal)
 

Valid XHTML 1.1!Valid CSS!

Copyright © 2008 Emmanuel Delahaye. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.