La méthode en question, est tout simplement d'utiliser la (magnifique) fonction select(), permettant de savoir si des données sont en attente de lecture dans un flux. Ainsi, dans la boucle principale, nul besoin d'utiliser une fonction bloquante si il n'y  a rien en attente. Celle-ci ne sera appelée que si l'on est sûr qu'on à quelque chose à lire. On peut donc, dans un seul processus, gérer la fonction accept(), qui ne sera appellée que si un client est en attente, puis ensuite stocker les sockets client dans un tableau, et parcourir ce dernier afin de savoir si on doit prendre le temps de s'arrêter faire une lecture (autrement dit, s'il y à quelque chose envoyé par un client à lire).  Il suffit de répéter ce processus dans une boucle, et le tour est joué. Pas de blocage, pas de problème de partage de données, pas de problème de fils fantômes ou autre joyeusetés offertes par les threads ou fork.

Pour cet exemple, je donne directement le code. Il faut donc évidemment savoir comment fonctionne un dialogue avec les socket, et avoir des notions sur select(). Le net est rempli d'explications la dessus, nul besoin de répéter.

Ce code gère 5 clients, et affiche les données envoyées par ceux-ci sur la console du serveur. C'est basique, mais ça permet de voir le principe, et d'améliorer ensuite pour produire quelque chose de plus utile. N'hésitez pas à poser des questions dans les commentaires si quelque chose vous échappe.

Le fichier server.c fournit en pièce jointe vous évitera un copié/collé, et sachez aussi que ce code a été validé par le magnifique Thaeron !

Le code :

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>

#define MYPORT 9999 
#define BACKLOG 5  
#define MAXCLIENTS 5
#define MAXDATASIZE 100

int main(void)
{
   int sockfd,new_fd,numbytes,highest = 0,i;
   int clients[MAXCLIENTS];
   char buffer[MAXDATASIZE] ;

   struct sockaddr_in my_addr,their_addr;
   socklen_t sin_size;
   struct timeval tv;
   fd_set readfds;

   if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
     perror("socket");
     exit(-1);
   }
   my_addr.sin_family = AF_INET;       
   my_addr.sin_port = htons(MYPORT);  
   my_addr.sin_addr.s_addr = INADDR_ANY;
   bzero(&(my_addr.sin_zero), 8);

   if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
      perror("bind");
      exit(-1);
   }
   if (listen(sockfd, BACKLOG) == -1) {
      perror("listen");
      exit(-1);
   }
   bzero(clients,sizeof(clients));
   highest = sockfd ;
   while(1) {
      sin_size = sizeof(struct sockaddr_in);     
      tv.tv_sec = 0;
      tv.tv_usec = 250000;
      FD_ZERO(&readfds);
      for ( i = 0 ; i < MAXCLIENTS ; i ++ ) {
         if ( clients[i] != 0 ) {
            FD_SET(clients[i],&readfds);
         }
      }
      FD_SET(sockfd,&readfds);  
      if (select(highest+1, &readfds, NULL, NULL, &tv) >=0 ) {
         if (FD_ISSET(sockfd, &readfds)) {
            if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
               perror("accept");
               continue;
            }
            for( i = 0 ; i < MAXCLIENTS ; i ++ ) {
               if ( clients[i] == 0 ) {
                  clients[i] = new_fd ;
                  break;
               }
            }
            if ( i != MAXCLIENTS ) {
               if ( new_fd > highest ) {
                  highest = clients[i] ;
               }
               printf("Connexion received from %s (slot %i) ",inet_ntoa(their_addr.sin_addr),i);
               send(new_fd,"Welcome ! ",10,MSG_NOSIGNAL);
            }    
            else {
               send(new_fd, "No room for you ! ",18,MSG_NOSIGNAL);
               close(new_fd);  
            }
         }
         for ( i = 0 ; i < MAXCLIENTS ; i ++ ) {
            if ( FD_ISSET(clients[i],&readfds) ) {
               if ( (numbytes=recv(clients[i],buffer,MAXDATASIZE,0)) <= 0 ) {
                  printf("Connexion lost from slot %i ",i); 
                  close(clients[i]);
                  clients[i] = 0 ;
               }
               else {
                  buffer[numbytes] = '\\0';
                  printf("Received from slot %i : %s",i,buffer);
               }
            }
         }
      }
      else {
         perror("select");
         continue;
      }
   }
   return 0;
}

Pour compiler ce code, en console, dans le dossier contenant le fichier server.c :

$ gcc -o server server.c

Ensuite pour lancer le serveur :

$ ./server

Enfin pour s'y connecter :

$ telnet 127.0.0.1 9999

(Si vous vous connectez sur le serveur local évidemment). Le port peut être modifié dans le code.

Voilà une solution peu souvent présentée, qui apporte pourtant certains avantages. Lisez cette page pour une utilisation plus poussée.

C'est cette technique qui sera utilisée dans le module de prise de main à distance pour mon bot IRC trustyRC.


Fabien (eponyme)