On va partir de l'exemple présenté dans le cours sur l'utilisation du select(). Mais, dans le sujet de Travaux Pratiques, en plus des fonctions read() et write(), on a une troisième fonction bloquante, sleep(), à gérer dans l'automate. Il va falloir supprimer l'usage de la fonction sleep() et déporter la gestion du temps d'attente via le paramètre timeout du select dans la procédure srv.
Dans les variables globales, on définit un troisième fd_set que l'on appelera fdwas et qui servira à mémoriser l'ensemble des sockets en attente d'une pause de 200000µs.
Quand on sort du select(), après épuisement de la pause, la valeur retournée est négative ou zéro. On déclenchera l'état suivant pour tous les sockets en pause.
void srv(int fd_srv) { FD_ZERO(&fdres); FD_SET(fd_srv,&fdres); FD_ZERO(&fdwrs); FD_ZERO(&fdwas); while(1) { fd_set fds_read,fds_write; int n,i,fd_con; struct sockaddr_in adr_cli; socklen_t adr_cli_len; struct timeval timeout; timeout.tv_sec=0; timeout.tv_usec=200000; memcpy(&fds_read,&fdres,sizeof(fd_set)); memcpy(&fds_write,&fdwrs,sizeof(fd_set)); n=select(FD_SETSIZE,&fds_read,&fds_write,NULL,&timeout); if (n > 0) for (i = 0 ; i < FD_SETSIZE ; i++) { if (FD_ISSET(i,&fds_read)) { if (i==fd_srv) { adr_cli_len = sizeof(adr_cli); fd_con = accept(fd_srv, (struct sockaddr *) &adr_cli, &adr_cli_len); if (fd_con >= 0) state0(fd_con); } else state[i](i); } if (FD_ISSET(i,&fds_write)) state[i](i); } else for (i = 0 ; i < FD_SETSIZE ; i++) if (FD_ISSET(i,&fdwas)) state[i](i); } }
Pour gérer les changements d'état, on modifie donc légèrement les procédures will_read(), will_write() et will_close() puis on y ajoute une procédure will_wait().
void will_read(int fd, void (*st)(int)) { FD_SET(fd,&fdres); FD_CLR(fd,&fdwrs); FD_CLR(fd,&fdwas); state[fd]=st; } void will_write(int fd, void (*st)(int)) { FD_SET(fd,&fdwrs); FD_CLR(fd,&fdres); FD_CLR(fd,&fdwas); state[fd]=st; } void will_wait(int fd, void (*st)(int)) { FD_CLR(fd,&fdres); FD_CLR(fd,&fdwrs); FD_SET(fd,&fdwas); state[fd]=st; } void will_close(int fd, void (*st)(int)) { FD_CLR(fd,&fdres); FD_CLR(fd,&fdwrs); FD_CLR(fd,&fdwas); state[fd]=NULL; st(fd); close(fd); }
Il ne reste plus qu'à construire le diagramme d'états puis programmer chaque procédure de gestion de l'état correspondant.
Les variables utilisées par les procédures de l'automate sont regroupées dans une structure composée ainsi :
struct tdata { int len; int pos; int fin; char *input; } *data[FD_SETSIZE];
void state0(int fd) { printf("Connexion acceptée !\n"); data[fd]=malloc(sizeof(struct tdata)); data[fd]->input=NULL; data[fd]->len=0; data[fd]->pos=0; data[fd]->fin=0; will_read(fd,&state1); }
void state1(int fd) { int n,i,j; char buf[10],*tmp; n=read(fd,buf,sizeof(buf)); if (n==0) { will_close(fd,&state4); return; } i=data[fd]->len; data[fd]->input=realloc(data[fd]->input,i+n); memcpy(data[fd]->input+i,buf,n); data[fd]->len+=n; for(j=data[fd]->len-1;j>=i;j--) if (data[fd]->input[j]=='\r' || data[fd]->input[j]=='\n') break; if (j<i) { will_read(fd,&state1); return; } data[fd]->fin=j+1; will_write(fd,&state2); }
void state2(int fd) { write(fd,data[fd]->input+data[fd]->pos,1); data[fd]->pos++; if (data[fd]->pos < data[fd]->fin) { will_wait(fd,&state3); return; } memcpy(data[fd]->input,data[fd]->input+data[fd]->fin,data[fd]->len-data[fd]->fin); data[fd]->len-=data[fd]->fin; data[fd]->fin=0; data[fd]->pos=0; will_read(fd,&state1); }
void state3(int fd) { will_write(fd,&state2); }
void state4(int fd) { free(data[fd]->input); free(data[fd]); close(fd); printf("Connexion fermée !\n"); }
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <netdb.h> #include <sys/socket.h> #include <sys/select.h> #include <string.h> // pour les fonctions bzero, strcmp, strtok #include <arpa/inet.h> // pour la fonction inet_addr fd_set fdres,fdwrs,fdwas; void (*state[FD_SETSIZE])(int); void will_read(int fd, void (*st)(int)) { FD_SET(fd,&fdres); FD_CLR(fd,&fdwrs); FD_CLR(fd,&fdwas); state[fd]=st; } void will_write(int fd, void (*st)(int)) { FD_SET(fd,&fdwrs); FD_CLR(fd,&fdres); FD_CLR(fd,&fdwas); state[fd]=st; } void will_wait(int fd, void (*st)(int)) { FD_CLR(fd,&fdres); FD_CLR(fd,&fdwrs); FD_SET(fd,&fdwas); state[fd]=st; } void will_close(int fd, void (*st)(int)) { FD_CLR(fd,&fdres); FD_CLR(fd,&fdwrs); FD_CLR(fd,&fdwas); state[fd]=NULL; st(fd); close(fd); } void state0(int); void state1(int); void state2(int); void state3(int); void state4(int); struct tdata { int len; // longueur du buffer d'entrée int pos; // index d'écriture int fin; // index de fin d'écriture char *input; } *data[FD_SETSIZE]; void state0(int fd) { printf("Connexion acceptée !\n"); data[fd]=malloc(sizeof(struct tdata)); data[fd]->input=NULL; data[fd]->len=0; data[fd]->pos=0; data[fd]->fin=0; will_read(fd,&state1); } void state1(int fd) { int n,i,j; char buf[10],*tmp; n=read(fd,buf,sizeof(buf)); if (n==0) { will_close(fd,&state4); return; } // Ajout des données lues dans le buffer d'entrée i=data[fd]->len; data[fd]->input=realloc(data[fd]->input,i+n); memcpy(data[fd]->input+i,buf,n); data[fd]->len+=n; for(j=data[fd]->len-1;j>=i;j--) if (data[fd]->input[j]=='\r' || data[fd]->input[j]=='\n') break; if (j<i) { will_read(fd,&state1); return; } data[fd]->fin=j+1; will_write(fd,&state2); } void state2(int fd) { write(fd,data[fd]->input+data[fd]->pos,1); data[fd]->pos++; if (data[fd]->pos < data[fd]->fin) { will_wait(fd,&state3); return; } memcpy(data[fd]->input,data[fd]->input+data[fd]->fin,data[fd]->len-data[fd]->fin); data[fd]->len-=data[fd]->fin; data[fd]->fin=0; data[fd]->pos=0; will_read(fd,&state1); } void state3(int fd) { will_write(fd,&state2); } void state4(int fd) { free(data[fd]->input); free(data[fd]); close(fd); printf("Connexion fermée !\n"); } void srv(int fd_srv) { FD_ZERO(&fdres); FD_SET(fd_srv,&fdres); FD_ZERO(&fdwrs); FD_ZERO(&fdwas); while(1) { fd_set fds_read,fds_write; int n,i,fd_con; struct sockaddr_in adr_cli; socklen_t adr_cli_len; struct timeval timeout; timeout.tv_sec=0; timeout.tv_usec=200000; memcpy(&fds_read,&fdres,sizeof(fd_set)); memcpy(&fds_write,&fdwrs,sizeof(fd_set)); n=select(FD_SETSIZE,&fds_read,&fds_write,NULL,&timeout); if (n > 0) for (i = 0 ; i < FD_SETSIZE ; i++ ) { if (FD_ISSET(i,&fds_read)) { if (i==fd_srv) { adr_cli_len = sizeof(adr_cli); fd_con = accept(fd_srv, (struct sockaddr *) &adr_cli, &adr_cli_len); if (fd_con >= 0) state0(fd_con); } else state[i](i); } if (FD_ISSET(i,&fds_write)) state[i](i); } else for (i = 0 ; i < FD_SETSIZE ; i++ ) if (FD_ISSET(i,&fdwas)) state[i](i); } } int main() { int fd_srv; struct sockaddr_in adr_srv; int err; fd_srv = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); if (fd_srv < 0) { printf("Erreur de création de la socket !\n"); exit(1); } bzero(&adr_srv, sizeof(adr_srv)); adr_srv.sin_family = AF_INET; err = inet_aton("0.0.0.0", &adr_srv.sin_addr); if (err == 0) { printf("Adresse IPv4 invalide !\n"); exit(1); } adr_srv.sin_port = htons(8080); err = bind(fd_srv, (struct sockaddr *) &adr_srv, sizeof(adr_srv)); if (err != 0) { printf("Erreur d'accès au port serveur !\n"); exit(1); } err = listen(fd_srv, 5); if (err != 0) { printf("Erreur de création de la file d'attente !\n"); exit(1); } printf("Serveur lancé !\n"); srv(fd_srv); return 0; }