Programmation Avancée en C


socket_TCP_server_nonblockingIO.c

00001 #include <stdio.h> 
00002 #include <stdlib.h> 
00003 #include <unistd.h>
00004 #include <netinet/in.h>
00005 #include <sys/socket.h>
00006 #include <sys/time.h>
00007 #include <string.h>    
00008 #include <arpa/inet.h>
00009 #include <stdbool.h>
00010 #include <sys/ioctl.h>
00011 #include <errno.h>
00012 
00013 #define MAX_SIZE 256
00014 #define NUMPORT  6666    // Le port où les utilisateurs se connecteront 
00015 #define BACKLOG  14      // Nombre maxi de connexions acceptées en file d'attente
00016 
00017 void error(char * msg);      // cf. ligne /*@\ref{network::socket_TCP_client::error} du listing~\ref{network::socket_TCP_client}@*/
00018 
00019 // ...
00020 #undef  max
00021 #define max(x,y) ((x) > (y) ? (x) : (y)) // Calcul du maximum
00022         /* Certains systèmes ne fournissent pas la macro FD_COPY */
00023 #ifndef FD_COPY
00024 #  define FD_COPY(p, q)   memcpy((q), (p), sizeof(fd_set))
00025 #endif
00026 
00027 extern int errno;
00028 void traitement(int sockfd, bool * close_conn) {
00029     char requete[MAX_SIZE], reponse[MAX_SIZE] = "Nonnnnnnnnn !";
00030     size_t  taille_rep = strlen(reponse)+1;    
00031 
00032     ssize_t n = recv(sockfd, requete, MAX_SIZE-1, 0); // Réception
00033     if (n < 0) {
00034         if (errno != EWOULDBLOCK) { perror("[recv]"); *close_conn = true; }
00035         return;
00036     }
00037     if (n == 0) { *close_conn = true; return; }
00038     requete[MAX_SIZE-1] = '\0'; // Précaution pour l'affichage
00039     printf(" - Requête reçue: <%s> (%ld octets reçus)\n", requete, (long) n);
00040 
00041     size_t taille_reste = taille_rep;
00042     while (taille_reste > 0) {
00043             n = send(sockfd, reponse, taille_reste, 0); // Émission d'un bout de la réponse.
00044             if (n == -1) {
00045                     perror("[send]");
00046                     *close_conn = true;
00047                     return;
00048             } else
00049                     taille_reste -= n;
00050     }
00051 }
00052 int main() 
00053 {
00054     int listenfd, connfd, i, max_fd = 0;
00055     struct sockaddr_in serv_addr, cl_addr; // IP du serveur et du client
00056     socklen_t len = sizeof(struct sockaddr_in);
00057     struct timeval timeout = { 1*60, 0 };  // timeout de 1 minute
00058     bool end_server = false;               // sentinelle de fin 
00059     
00060     memset(&serv_addr, 0, sizeof(serv_addr));  // initialisation @IP du serveur
00061     serv_addr.sin_family      = AF_INET;
00062     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
00063     serv_addr.sin_port        = htons(NUMPORT);
00064 
00065         /*** Création de la socket, attachement et mise en écoute ***/ 
00066     if ((listenfd = socket(PF_INET,SOCK_STREAM,0)) < 0) error("[socket]");
00067     const int on = 1;
00068     if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) 
00069         error("[setsockopt(SO_REUSEADDR)]");
00070     // On en fait une socket non bloquante; 
00071     if (ioctl(listenfd, FIONBIO, &on) < 0)                       error("[ioctl]"); /*@\label{network::socket_TCP_server_nonblockingIO::ioctl}@*/
00072     if (bind(listenfd, (struct sockaddr *) &serv_addr, len) < 0) error("[bind]");
00073     if (listen(listenfd, BACKLOG) < 0)                           error("[listen]");     
00074     printf("Serveur: en écoute à l'adresse %s sur le port %d\n",
00075            inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
00076         /*** Initialisation des ensembles de descripteurs ***/
00077     fd_set master_set, working_set; 
00078     FD_ZERO(&master_set);    FD_ZERO(&working_set);
00079     FD_SET(listenfd, &master_set);
00080     max_fd = max(max_fd, listenfd);
00081 
00082     do { 
00083         FD_COPY(&master_set, &working_set); // recopie de l'ensemble master_set
00084             /*** Appel à select avec une attente jusqu'au timeout  ***/
00085         int r = select(max_fd+1, &working_set, NULL, NULL, &timeout);
00086         if (r < 0)  error("[select]");
00087         if (r == 0) { // timeout atteint
00088             fprintf(stderr, "Timeout sur select() atteint: fin du programme\n");
00089             break;
00090         }
00091         // Ici, r > 0 descripteurs sont prêt. Il faut déterminer lesquels
00092         for (i=0; (i <= max_fd) && (r > 0); i++) {
00093             if (FD_ISSET(i, &working_set)) {
00094                 r--;  // on a trouvé un descripteur prêt en lecture
00095                 if (i == listenfd) {
00096                     printf("La socket d'écoute %d est prête en lecture...\n",i);
00097                     do { /*** On accepte tout connexion entrante ***/
00098                         connfd = accept(listenfd,(struct sockaddr *)&cl_addr, &len);
00099                         if (connfd < 0) {
00100                             if (errno != EWOULDBLOCK) { // accept a vraiment raté
00101                                 perror("[accept]");
00102                                 end_server = true;
00103                             }
00104                             break;
00105                         }
00106                         printf("Connexion %d acceptée avec %s:%d\n", connfd, 
00107                                inet_ntoa(cl_addr.sin_addr), ntohs(cl_addr.sin_port));
00108                         FD_SET(connfd, &master_set); 
00109                         max_fd = max(max_fd, connfd);
00110                     } while (connfd != -1); 
00111                 } else { // i est donc le descripteur d'une connexion entrante
00112                     bool close_conn = false; 
00113                     traitement(i, &close_conn); // Traitement de la requête
00114                     if (close_conn == true) {   // Il faut fermer proprement 
00115                         close(i);
00116                         FD_CLR(i, &master_set);
00117                         if (i == max_fd)  // Mise à jour de max_fd
00118                             while(!FD_ISSET(max_fd, &master_set)) max_fd--;
00119                     }
00120                 } // Fin connexion entrante active
00121             } // Fin if (FD_ISSET(i, &working_set))
00122         } // Fin de la boucle for 
00123     } while (end_server == false);
00124 
00125     for (i=0; i < max_fd; i++) // Fermeture des sockets ouvertes
00126         if (FD_ISSET(i, &master_set)) close(i);
00127     return EXIT_SUCCESS;
00128 }
00129 
00130 void error(char * msg) {
00131     perror(msg);
00132     exit(EXIT_FAILURE);
00133 }