Les concepts fondamentaux (sockets, adresses, communication par datagrammes et flots, client-serveur etc.) sont les mêmes que pour les sockets locaux (voir sockets). Les particularités viennent des adresses : comment fabriquer une adresse à partir d'un nom de machine (résolution) et d'un numéro de port, comment retrouver le nom d'une machine à partir d'une adresse (résolution inverse) etc.
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
Pour communiquer, les applications doivent créer des sockets
(prises bidirectionnelles) par la fonction socket()
et les
relier entre elles. On peut ensuite utiliser ces sockets comme des
fichiers ordinaires (par read
, write
, ...) ou par
des opérations spécifiques ( send
, sendto
,
recv
, recvfrom
, ...).
Pour désigner un socket sur une machine il faut une adresse de
socket. Comme il existe différents types de sockets, les opérations
sur les adresses concerne un type d'adresse général abstrait
(struct sockaddr
) qui recouvre tous les types concrets
particuliers.
Pour TCP-IP (Inet), les adresses de sockets sont déterminées par un
numéro IP, et un numéro de port. Pour IPv4, on utilise
des struct sockaddr_in
, qui possèdent 3 champs importants :
sin_family
, la famille d'adresses, valant AF_INET
sin_addr
, pour l'adresse IP. sin_port
, pour le numéro de portAttention, les octets de l'adresse IP et le numéro de port sont stockés dans l'ordre réseau (big-endian), qui n'est pas forcément celui de la machine hôte sur laquelle s'exécute le programme. Voir plus loin les fonctions de conversion hôte/réseau.
Pour IPv6, on utilise
des struct sockaddr_in6
, avec
sin6_family
, valant AF_INET6
sin6_addr
, l'adresse IP sur 6 octetssin6_port
, pour le numéro de port.Comme pour les sockets locaux, on crée les sockets par socket()
et on « nomme la prise » par bind()
.
Dans le cas le plus fréquent, on dispose du nom de la machine destinataire
(par exemple la chaîne de caractères "www.elysee.fr"
),
et du numéro de port (un entier).
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
gethostbyname()
retourne un pointeur vers une structure
hostent
qui contient diverses informations sur la machine en
question, en particulier une adresse h_addr
Une machine peut avoir plusieurs adressesque l'on mettra dans
sin_addr
.
Le champ sin_port
est un entier court en ordre réseau, pour
y mettre un entier ordinaire il faut le convertir par htons()
Host TO Network Short.
Voir exemple client-echo.c
.
Pour une adresse sur la machine locale
ne pas confondre avec la notion de socket du domaine local vue en sockets, on utilise
INADDR_ANY
(0.0.0.0
). Le
socket est alors ouvert (avec le même numéro de port) sur toutes les
adresses IP de toutes les interfaces de la machine. INADDR_LOOPBACK
correspondant à l'adresse locale
127.0.0.1
(alias localhost
). Le socket n'est alors
accessible que depuis la machine elle-même. INADDR_BROADCAST
(255.255.255.255
)
L'utilisation de l'adresse de diffusion est soumise à restrictions, voir manuel
Voir exemple serveur-echo.c
.
Pour IPV6
AF_INET6
, famille de protocoles
PF_INET6
IN6ADD_LOOPBACK_INIT
(::1
)
IN6ADD_ANY_INIT
La fonction getsockname()
permet de retrouver l'adresse
associée à un socket.
#include <sys/socket.h>
int getsockname(int s ,
struct sockaddr *name ,
socklen_t * namelen )
Le numéro de port est dans le champ sin_port
, pour le convertir
en entier normal, utiliser ntohs()
(Network TO Host short).
L'adresse IP peut être convertie en chaîne de caractères en notation
décimale pointée (exemple "147.210.94.194"
) par inet_ntoa()
(network to ascii),
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
C'est ce qui est utilisé dans serveur-echo.c
pour déterminer et
afficher la provenance des requêtes.
On peut également tenter une résolution inverse, c'est-à-dire
de retrouver le nom à partir de l'adresse, en passant par
gethostbyaddr
, qui retourne un pointeur vers une structure hostent
,
dont le champ h_name
désigne le nom officiel de la machine.
Cette résolution n'aboutit pas toujours, parce que tous les numéros IP ne correspondent pas à des machines déclarées.
#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyaddr(const char *addr, int len, int type);
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */
Un socket peut être fermé par close()
ou par shutdown()
.
int shutdown(int fd, int how);
Un socket est bidirectionnel, le paramètre how
indique quelle(s)
moitié(s) on ferme : 0
pour la sortie, 1
pour l'entrée,
2
pour les deux (équivaut à close()
).
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
Cette fonction construit un socket et retourne un numéro de
descripteur. Pour une liaison par datagrammes sur IPv4, utilisez la
famille AF_INET
, le type SOCK_DGRAM
et le protocole
par défaut 0
.
Retourne -1
en cas d'échec.
La fonction connect
met en relation un socket (de cette
machine) avec un autre socket désigné, qui sera le « correspondant par
défaut » pour la suite des opérations.
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,
const struct sockaddr *serv_addr,
socklen_t addrlen);
Sur un socket connecté (voir ci-dessus), on peut expédier
des datagrammes (contenus dans un tampon
t
de longueur n
) par write(sockfd,t,n)
.
La fonction send()
int send(int s, const void *msg, size_t len, int flags);
permet d'indiquer des flags, par exemple
MSG_DONTWAIT
pour une écriture non bloquante.
Enfin, sendto()
envoie un datagramme à une adresse
spécifiée, sur un socket connecté ou non.
int sendto(int s, const void *msg, size_t len, int flags,
const struct sockaddr *to, socklen_t tolen);
Inversement, la réception peut se faire par un simple
read()
, par un recv()
(avec des flags), ou
par un recvfrom
, qui permet de récupérer l'adresse
from
de l'émetteur.
int recv(int s, void *buf, size_t len, int flags);
int recvfrom(int s, void *buf, size_t len, int flags,
struct sockaddr *from, socklen_t *fromlen);
Principe:
Usage:
serveur-echo
numéro-de-port client-echo
nom-serveur
numéro-de-port « message à expédier »
1 /*
2 client-echo.c
3 Envoi de datagrammes
4 Exemple de client qui
5 - ouvre un socket
6 - envoie des datagrammes sur ce socket (lignes de textes
7 de l'entrée standard)
8 - attend une réponse
9 - affiche la réponse
10 */
11 #include <unistd.h>
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <signal.h>
16 #include <stdio.h>
17 #include <netdb.h>
18 #include <string.h>
19 #include <stdlib.h>
20 #define TAILLE_TAMPON 1000
21 static int fd;
22 void ErreurFatale(char message[])
23 {
24 printf("CLIENT> Erreur fatale\n");
25 perror(message);
26 exit(EXIT_FAILURE);
27 }
28 int main(int argc, char *argv[])
29 {
30 struct sockaddr_in adresse_socket_serveur;
31 struct hostent *hote;
32 int taille_adresse_socket_serveur;
33 char *nom_serveur;
34 int numero_port_serveur;
35 char *requete, reponse[TAILLE_TAMPON];
36 int longueur_requete, longueur_reponse;
37 /* 1. réception des paramètres de la ligne de commande */
38 if (argc != 4) {
39 printf("Usage: %s hote port message\n", argv[0]);
40 exit(EXIT_FAILURE);
41 };
42 nom_serveur = argv[1];
43 numero_port_serveur = atoi(argv[2]);
44 requete = argv[3];
45 /* 2. Initialisation du socket */
46 /* 2.1 création du socket en mode datagramme */
47 fd = socket(AF_INET, SOCK_DGRAM, 0);
48 if (fd < 0)
49 ErreurFatale("Creation socket");
50 /* 2.2 recherche de la machine serveur */
51 hote = gethostbyname(nom_serveur);
52 if (hote == NULL)
53 ErreurFatale("Recherche serveur");
54 /* 2.3 Remplissage adresse serveur */
55 adresse_socket_serveur.sin_family = AF_INET;
56 adresse_socket_serveur.sin_port = htons(numero_port_serveur);
57 adresse_socket_serveur.sin_addr =
58 *(struct in_addr *) hote->h_addr;
59 taille_adresse_socket_serveur = sizeof adresse_socket_serveur;
60 longueur_requete = strlen(requete) + 1;
61 /* 3. Envoi de la requête */
62 printf("REQUETE> %s\n", requete);
63 if (sendto(fd, requete, longueur_requete, 0, /* flags */
64 (struct sockaddr *) &adresse_socket_serveur,
65 taille_adresse_socket_serveur)
66 < 0)
67 ErreurFatale("Envoi requete");
68 /* 4. Lecture de la réponse */
69 longueur_reponse = recvfrom(fd,
70 reponse,
71 TAILLE_TAMPON, 0, NULL, 0);
72 if (longueur_reponse < 0)
73 ErreurFatale("Attente réponse");
74 printf("REPONSE> %s\n", reponse);
75 close(fd);
76 printf("CLIENT> Fin.\n");
77 exit(0);
78 }
Exercice : faire en sorte que le client réexpédie sa requête si il ne reçoit pas la réponse dans un délai fixé. Fixer une limite au nombre de tentatives.
1 /*
2 serveur-echo.c - Réception de datagrammes
3 Exemple de serveur qui
4 - ouvre un socket sur un port en mode non-connecté
5 - affiche les messages (chaînes de caractères)
6 qu'il reçoit par ce socket.
7 - envoie une réponse
8 */
9 #include <unistd.h>
10 #include <sys/types.h>
11 #include <sys/socket.h>
12 #include <netinet/in.h>
13 #include <signal.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <arpa/inet.h>
17 #include <ctype.h>
18 #define TAILLE_TAMPON 1000
19 static int fd;
20 void ErreurFatale(char message[])
21 {
22 printf("SERVEUR> Erreur fatale\n");
23 perror(message);
24 exit(EXIT_FAILURE);
25 }
26 void ArretServeur(int signal)
27 {
28 close(fd);
29 printf("SERVEUR> Arrêt du serveur (signal %d)\n", signal);
30 exit(EXIT_SUCCESS);
31 }
32 int main(int argc, char *argv[])
33 {
34 struct sockaddr_in adresse_serveur;
35 size_t taille_adresse_serveur;
36 int numero_port_serveur;
37 char *src, *dst;
38 struct sigaction a;
39 /* 1. réception des paramètres de la ligne de commande */
40 if (argc != 2) {
41 printf("usage: %s port\n", argv[0]);
42 exit(1);
43 };
44 numero_port_serveur = atoi(argv[1]);
45 /* 2. Si une interruption se produit, arrêt du serveur */
46 /* signal(SIGINT,ArretServeur); */
47 a.sa_handler = ArretServeur;
48 sigemptyset(&a.sa_mask);
49 a.sa_flags = 0;
50 sigaction(SIGINT, &a, NULL);
51 /* 3. Initialisation du socket de réception */
52 /* 3.1 Création du socket en mode non-connecté
53 (datagrammes) */
54 fd = socket(AF_INET, SOCK_DGRAM, 0);
55 if (fd < 0)
56 ErreurFatale("socket");
57 /* 3.2 Remplissage de l'adresse de réception
58 (protocole Internet TCP-IP, réception acceptée sur toutes
59 les adresses IP du serveur, numéro de port indiqué)
60 */
61 adresse_serveur.sin_family = AF_INET;
62 adresse_serveur.sin_addr.s_addr = INADDR_ANY;
63 adresse_serveur.sin_port = htons(numero_port_serveur);
64 /* 3.3 Association du socket au port de réception */
65 taille_adresse_serveur = sizeof adresse_serveur;
66 if (bind(fd,
67 (struct sockaddr *) &adresse_serveur,
68 taille_adresse_serveur) < 0)
69 ErreurFatale("bind");
70 printf("SERVEUR> Le serveur écoute le port %d\n",
71 numero_port_serveur);
72 while (1) {
73 struct sockaddr_in adresse_client;
74 int taille_adresse_client;
75 char tampon_requete[TAILLE_TAMPON],
76 tampon_reponse[TAILLE_TAMPON];
77 int lg_requete, lg_reponse;
78 /* 4. Attente d'un datagramme (requête) */
79 taille_adresse_client = sizeof(adresse_client);
80 lg_requete = recvfrom(fd, tampon_requete, TAILLE_TAMPON, 0, /* flags */
81 (struct sockaddr *)
82 &adresse_client,
83 (socklen_t *) &
84 taille_adresse_client);
85 if (lg_requete < 0)
86 ErreurFatale("recfrom");
87 /* 5. Affichage message avec sa provenance et sa longueur */
88 printf("%s:%d [%d]\t: %s\n",
89 inet_ntoa(adresse_client.sin_addr),
90 ntohs(adresse_client.sin_port),
91 lg_requete, tampon_requete);
92 /* 6. Fabrication d'une réponse */
93 src = tampon_requete;
94 dst = tampon_reponse;
95 while ((*dst++ = toupper(*src++)) != '\0');
96 lg_reponse = strlen(tampon_reponse) + 1;
97 /* 7. Envoi de la réponse */
98 if (sendto(fd,
99 tampon_reponse,
100 lg_reponse,
101 0,
102 (struct sockaddr *) &adresse_client,
103 taille_adresse_client)
104 < 0)
105 ErreurFatale("Envoi de la réponse");
106 };
107 }
La création d'un socket pour TCP se fait ainsi
int fd;
..
fd=socket(AF_INET,SOCK_STREAM,0);
Le socket d'un client TCP doit être relié (par connect()
) à celui
du serveur, et il est utilisé ensuite par des read()
et des
write()
, ou des entrées-sorties de haut niveau fprintf()
,
fscanf()
, etc. si on a défini des flots par fdopen()
.
1 /* client-web.c */
2 /*
3 Interrogation d'un serveur web
4 Usage:
5 client-web serveur port adresse-document
6 retourne le contenu du document d'adresse
7 http://serveur:port/adresse-document
8 Exemple:
9 client-web www.info.prive 80 /index.html
10 Fonctionnement:
11 - ouverture d'une connexion TCP vers serveur:port
12 - envoi de la requête GET adresse-document HTTP/1.0[cr][lf][cr][lf]
13 - affichage de la réponse
14 */
15 #include <unistd.h>
16 #include <sys/types.h>
17 #include <sys/socket.h>
18 #include <netinet/in.h>
19 #include <signal.h>
20 #include <stdio.h>
21 #include <netdb.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #define CRLF "\r\n"
25 #define TAILLETAMPON 1000
26 /* -- connexion vers un serveur TCP --------------------------- */
27 int connexion_tcp(char nom_serveur[], int port_serveur)
28 {
29 struct sockaddr_in addr_serveur;
30 struct hostent *serveur;
31 int fd;
32 fd = socket(AF_INET, SOCK_STREAM, 0); /* création prise */
33 if (fd < 0) {
34 perror("socket");
35 exit(EXIT_FAILURE);
36 }
37 serveur = gethostbyname(nom_serveur); /* recherche adresse serveur */
38 if (serveur == NULL) {
39 perror("gethostbyname");
40 exit(EXIT_FAILURE);
41 }
42 addr_serveur.sin_family = AF_INET;
43 addr_serveur.sin_port = htons(port_serveur);
44 addr_serveur.sin_addr = *(struct in_addr *) serveur->h_addr;
45 if (connect(fd, /* connexion au serveur */
46 (struct sockaddr *) &addr_serveur,
47 sizeof addr_serveur)
48 < 0) {
49 perror("connect");
50 exit(EXIT_FAILURE);
51 }
52 return (fd);
53 }
54 /* ------------------------------------------------------ */
55 void demander_document(int fd, char adresse_document[])
56 {
57 char requete[TAILLETAMPON];
58 int longueur;
59 /* constitution de la requête, suivie d'une ligne vide */
60 longueur = snprintf(requete, TAILLETAMPON,
61 "GET %s HTTP/1.0" CRLF CRLF,
62 adresse_document);
63 write(fd, requete, longueur); /* envoi */
64 }
65 /* ------------------------------------------------------ */
66 void afficher_reponse(int fd)
67 {
68 char tampon[TAILLETAMPON];
69 int longueur;
70 while (1) {
71 longueur = read(fd, tampon, TAILLETAMPON); /* lecture par bloc */
72 if (longueur <= 0)
73 break;
74 write(1, tampon, longueur); /* copie sur sortie standard */
75 };
76 }
77 /* -- main --------------------------------------------------- */
78 int main(int argc, char *argv[])
79 {
80 char *nom_serveur, *adresse_document;
81 int port_serveur;
82 int fd;
83 if (argc != 4) {
84 printf("Usage: %s serveur port adresse-document\n",
85 argv[0]);
86 exit(EXIT_FAILURE);
87 };
88 nom_serveur = argv[1];
89 port_serveur = atoi(argv[2]);
90 adresse_document = argv[3];
91 fd = connexion_tcp(nom_serveur, port_serveur);
92 demander_document(fd, adresse_document);
93 afficher_reponse(fd);
94 close(fd);
95 exit(EXIT_SUCCESS);
96 }
Remarque: souvent il est plus commode de créer des flots de haut niveau
au dessus du socket (voir fdopen()
) que de manipuler des read
et des write
. Voir dans l'exemple suivant.
Un serveur TCP doit traiter des connexions venant de plusieurs clients.
Après avoir créé et nommé le socket,
le serveur spécifie qu'il
accepte les communications entrantes par listen()
, et se met
effectivement en attente
d'une connexion de client par accept()
.
#include <sys/types.h>
#include <sys/socket.h>
int listen(int s, int backlog);
int accept(int s, struct sockaddr *addr,
socklen_t *addrlen);
Le paramètre backlog
indique la taille maximale de la file des
connexions en attente. Sous Linux la limite est donnée par
la constante SOMAXCONN
(qui vaut 128), sur d'autres systèmes elle
est limitée à 5.
La fonction accept()
renvoie un autre socket, qui servira
à la communication avec le client.
L'adresse du client peut être obtenue par les paramètres
addr
et addrlen
.
En général les serveurs TCP doivent traiter plusieurs connexions
simultanément. La solution habituelle est de lancer, après l'appel à
accept()
un processus fils (par fork()
)qui traite la
communication avec un seul client. Ceci induit une gestion des processus,
donc des signaux liés à la terminaison des processus fils.
Dans ce qui suit nous présentons un serveur Web rudimentaire, capable de fournir des pages Web construites à partir des fichiers d'un répertoire. Nous donnons deux implémentations possibles, à l'aide de processus lourds et légers.
Cette version suit l'approche traditionnelle. Pseudo-code:
ouvrir socket serveur (socket/bind/listen)
répéter indéfiniment
| attendre l'arrivée d'un client (accept)
| créer un processus (fork) et lui déléguer
| la communication avec le client
fin-répeter
1 /* ------------------------------------------------
2 Serveur TCP
3 serveur web, qui renvoie les fichiers (textes)
4 d'un répertoire sous forme de pages HTML
5 usage : serveur-web port repertoire
6 exemple: serveur-web 8000 /usr/doc/exemples
7 --------------------------------------------------*/
8 #include <unistd.h>
9 #include <sys/types.h>
10 #include <sys/errno.h>
11 #include <sys/socket.h>
12 #include <sys/wait.h>
13 #include <sys/stat.h>
14 #include <netinet/in.h>
15 #include <arpa/inet.h>
16 #include <signal.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include "declarations.h"
20 void fin_serveur(int sig);
21 void fin_fils(int sig);
22 int fdServeur; /* variable globale, pour partager
23 avec traitement signal fin_serveur */
24 void demarrer_serveur(int numero_port, char repertoire[])
25 {
26 int numero_client = 0;
27 int fdClient;
28 struct sigaction action_fin, action_fin_fils;
29 fdServeur = serveur_tcp(numero_port);
30 /* positionnement des signaux SIGINT et SIGCHLD */
31 action_fin.sa_handler = fin_serveur;
32 sigemptyset(&action_fin.sa_mask);
33 action_fin.sa_flags = 0;
34 sigaction(SIGINT, &action_fin, NULL);
35 action_fin_fils.sa_handler = fin_fils;
36 sigemptyset(&action_fin_fils.sa_mask);
37 action_fin_fils.sa_flags = SA_NOCLDSTOP;
38 sigaction(SIGCHLD, &action_fin_fils, NULL);
39 printf("> Serveur " VERSION
40 " (port=%d, répertoire de documents=\"%s\")\n",
41 numero_port, repertoire);
42 while (1) {
43 struct sockaddr_in a;
44 size_t l = sizeof a;
45 fdClient = attendre_client(fdServeur);
46 getsockname(fdClient, (struct sockaddr *) &a, &l);
47 numero_client++;
48 printf("> client %d [%s]\n", numero_client,
49 inet_ntoa(a.sin_addr));
50 if (fork() == 0) {
51 /* le processus fils ferme le socket serveur
52 et s'occupe du client */
53 close(0);
54 close(1);
55 close(2);
56 close(fdServeur);
57 dialogue_client(fdClient, repertoire);
58 close(fdClient);
59 exit(EXIT_SUCCESS);
60 }
61 /* le processus père n'a plus besoin du socket client.
62 Il le ferme et repart dans la boucle */
63 close(fdClient);
64 }
65 }
66 /* -------------------------------------------------------------
67 Traitement des signaux
68 --------------------------------------------------------------- */
69 void fin_serveur(int sig)
70 {
71 printf("=> fin du serveur\n");
72 shutdown(fdServeur, 2); /* utile ? */
73 close(fdServeur);
74 exit(EXIT_SUCCESS);
75 }
76 void fin_fils(int sig)
77 {
78 /* cette fonction est appelée chaque fois qu'un signal SIGCHLD
79 indique la fin d'un processus fils _au moins_. */
80 while (waitpid(-1, NULL, WNOHANG) > 0) {
81 /* attente des fils arrêtés, tant qu'il y en a */
82 };
83 }
84 /* -------------------------------------------------------------*/
85 void usage(char prog[])
86 {
87 printf("Usage : %s [options\n\n", prog);
88 printf("Options :"
89 "-h\tcemessage\n"
90 "-p port\tport du serveur [%d]\n"
91 "-d dir \trépertoire des documents [%s]\n",
92 PORT_PAR_DEFAUT, REPERTOIRE_PAR_DEFAUT);
93 }
94 /* -------------------------------------------------------------*/
95 int main(int argc, char *argv[])
96 {
97 int port = PORT_PAR_DEFAUT;
98 char *repertoire = REPERTOIRE_PAR_DEFAUT; /* la racine
99 des documents */
100 char c;
101 while ((c = getopt(argc, argv, "hp:d:")) != -1)
102 switch (c) {
103 case 'h':
104 usage(argv[0]);
105 exit(EXIT_SUCCESS);
106 break;
107 case 'p':
108 port = atoi(optarg);
109 break;
110 case 'd':
111 repertoire = optarg;
112 break;
113 case '?':
114 fprintf(stderr,
115 "Option inconnue -%c. -h pour aide.\n",
116 optopt);
117 break;
118 };
119 demarrer_serveur(port, repertoire);
120 exit(EXIT_SUCCESS);
121 }
Les processus légers permettent une autre approche : on crée préalablement un « pool » de processus que l'on bloque. Lorsqu'un client se présente, on confie la communication à un processus inoccupé.
ouvrir socket serveur (socket/bind/listen)
créer un pool de processus
répéter indéfiniment
| attendre l'arrivée d'un client (accept)
| trouver un processus libre, et lui
| confier la communication avec le client
fin-répeter
1 /* serveur-web.c */
2 /* ------------------------------------------------
3 Serveur TCP
4 serveur web, qui renvoie les fichiers (textes)
5 d'un répertoire sous forme de pages HTML
6 usage : serveur-web port repertoire
7 exemple: serveur-web 8000 /usr/doc/exemples
8 Version basée sur les threads. Au lieu de créer
9 un processus par connexion, on gère un pool de tâches
10 - au démarrage du serveur les tâches sont créees,
11 et bloquées par un verrou
12 - quand un client se connecte, la connexion est
13 confiée à une tâche inactive, qui est débloquée
14 pour l'occasion.
15 --------------------------------------------------*/
16 #include <pthread.h>
17 #include <unistd.h>
18 #include <sys/types.h>
19 #include <sys/errno.h>
20 #include <sys/socket.h>
21 #include <sys/wait.h>
22 #include <sys/stat.h>
23 #include <netinet/in.h>
24 #include <arpa/inet.h>
25 #include <signal.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include "declarations.h"
29 void fin_serveur(int sig);
30 #define NB_TACHES 5
31 struct tache {
32 pthread_t id; /* identificateur de thread */
33 pthread_mutex_t verrou;
34 int actif; /* 1 => le client est occupe */
35 int fd; /* socket du client */
36 char *repertoire;
37 };
38 struct tache taches[NB_TACHES];
39 int fdServeur; /* variable globale, pour partager
40 avec traitement signal fin_serveur */
41 /* -------------------------------------------------------
42 travail_tache: contient le code de l'action
43 lancée pour chaque tâche
44 ------------------------------------------------------- */
45 void *travail_tache(void *data)
46 {
47 struct tache *t = data; /* les données spécifiques
48 de la tâche */
49 while (1) {
50 pthread_mutex_lock(&t->verrou);
51 dialogue_client(t->fd, t->repertoire);
52 close(t->fd);
53 t->actif = 0;
54 };
55 return NULL;
56 }
57 /* ------------------------------------------------------- */
58 void creer_taches(char repertoire[])
59 {
60 int k;
61 for (k = 0; k < NB_TACHES; k++) {
62 struct tache *t = taches + k;
63 t->actif = 0;
64 t->repertoire = repertoire;
65 pthread_mutex_init(&t->verrou, NULL);
66 pthread_mutex_lock(&t->verrou);
67 pthread_create(&t->id, NULL, travail_tache,
68 (void *) t);
69 }
70 }
71 /* -----------------------------------------------------
72 demarrer_serveur: crée le socket serveur
73 et lance des processus pour chaque client
74 ----------------------------------------------------- */
75 int demarrer_serveur(int numero_port, char repertoire[])
76 {
77 int numero_client = 0;
78 int fdClient;
79 struct sigaction action_fin;
80 printf("> Serveur " VERSION "+threads "
81 "(port=%d, répertoire de documents=\"%s\")\n",
82 numero_port, repertoire);
83 /* positionnement du signaux SIGINT */
84 action_fin.sa_handler = fin_serveur;
85 sigemptyset(&action_fin.sa_mask);
86 action_fin.sa_flags = 0;
87 sigaction(SIGINT, &action_fin, NULL);
88 /* création du socket serveur et du pool de tâches */
89 fdServeur = serveur_tcp(numero_port);
90 creer_taches(repertoire);
91 /* boucle du serveur */
92 while (1) {
93 struct sockaddr_in a;
94 size_t l = sizeof a;
95 int k;
96 fdClient = attendre_client(fdServeur);
97 getsockname(fdClient, (struct sockaddr *) &a, &l);
98 numero_client++;
99 /* recherche d'une tâche inoccupée */
100 for (k = 0; k < NB_TACHES; k++)
101 if (taches[k].actif == 0)
102 break;
103 if (k == NB_TACHES) { /* pas de tâche libre ? */
104 printf
105 ("> client %d [%s] rejeté (surcharge)\n",
106 numero_client, inet_ntoa(a.sin_addr));
107 close(fdClient);
108 } else {
109 /* affectation du travail et déblocage de la tâche */
110 printf
111 ("> client %d [%s] traité par tâche %d\n",
112 numero_client, inet_ntoa(a.sin_addr), k);
113 taches[k].fd = fdClient;
114 taches[k].actif = 1;
115 pthread_mutex_unlock(&taches[k].verrou);
116 }
117 }
118 }
119 /* -------------------------------------------------------------
120 Traitement des signaux
121 --------------------------------------------------------------- */
122 void fin_serveur(int sig)
123 {
124 printf("=> fin du serveur\n");
125 shutdown(fdServeur, 2); /* utile ? */
126 close(fdServeur);
127 exit(EXIT_SUCCESS);
128 }
129 /* -------------------------------------------------------------*/
130 void usage(char prog[])
131 {
132 printf("Usage : %s [options\n\n", prog);
133 printf("Options :"
134 "-h\tcemessage\n"
135 "-p port\tport du serveur [%d]\n"
136 "-d dir \trépertoire des documents [%s]\n",
137 PORT_PAR_DEFAUT, REPERTOIRE_PAR_DEFAUT);
138 }
139 /* -------------------------------------------------------------*/
140 int main(int argc, char *argv[])
141 {
142 int port = PORT_PAR_DEFAUT;
143 char *repertoire = REPERTOIRE_PAR_DEFAUT; /* la racine
144 des documents */
145 char c;
146 while ((c = getopt(argc, argv, "hp:d:")) != -1)
147 switch (c) {
148 case 'h':
149 usage(argv[0]);
150 exit(EXIT_SUCCESS);
151 break;
152 case 'p':
153 port = atoi(optarg);
154 break;
155 case 'd':
156 repertoire = optarg;
157 break;
158 case '?':
159 fprintf(stderr,
160 "Option inconnue -%c. -h pour aide.\n",
161 optopt);
162 break;
163 };
164 demarrer_serveur(port, repertoire);
165 exit(EXIT_SUCCESS);
166 }
Les déclarations de constantes et les entêtes des fonctions communes :
1 /* Serveur Web - declarations.h */
2 #define CRLF "\r\n"
3 #define VERSION "MegaSoft 0.0.7 pour Unix"
4 #define PORT_PAR_DEFAUT 8000
5 #define REPERTOIRE_PAR_DEFAUT "/tmp"
6 #define FATAL(err) { perror((char *) err); exit(1);}
7 /* -- Prototypes des fonctions ----------------------- */
8 extern void dialogue_client(int fdClient, char repertoire[]);
9 extern void envoyer_document(FILE * out, char nom_document[],
10 char repertoire[]);
11 extern void document_non_trouve(FILE * out, char nom_document[]);
12 extern void requete_invalide(FILE * out);
13 extern int serveur_tcp(int numero_port);
14 extern int attendre_client(int fdServeur);
les fonctions réseau :
serveur_tcp()
: création du socket du serveur TCP.attendre_client()
1 /*
2 Projet serveur Web - reseau.c
3 Fonctions réseau
4 */
5 #include <sys/types.h>
6 #include <sys/errno.h>
7 #include <sys/socket.h>
8 #include <sys/wait.h>
9 #include <sys/stat.h>
10 #include <netinet/in.h>
11 #include <signal.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include "declarations.h"
15 /* ------------------------------------------------------------
16 Fonctions réseau
17 ------------------------------------------------------------ */
18 int serveur_tcp(int numero_port)
19 {
20 int fd;
21 /* démarre un service TCP sur le port indiqué */
22 struct sockaddr_in addr_serveur;
23 size_t lg_addr_serveur = sizeof addr_serveur;
24 /* création de la prise */
25 fd = socket(AF_INET, SOCK_STREAM, 0);
26 if (fd < 0)
27 FATAL("socket");
28 /* nommage de la prise */
29 addr_serveur.sin_family = AF_INET;
30 addr_serveur.sin_addr.s_addr = INADDR_ANY;
31 addr_serveur.sin_port = htons(numero_port);
32 if (bind
33 (fd, (struct sockaddr *) &addr_serveur,
34 lg_addr_serveur) < 0)
35 FATAL("bind");
36 /* ouverture du service */
37 listen(fd, 4);
38 return (fd);
39 }
40 int attendre_client(int fdServeur)
41 {
42 int fdClient;
43 /* A cause des signaux SIGCHLD, la fonction accept()
44 peut etre interrompue quand un fils se termine.
45 Dans ce cas, on relance accept().
46 */
47 while ((fdClient = accept(fdServeur, NULL, NULL)) < 0) {
48 if (errno != EINTR)
49 FATAL("Fin anormale de accept().");
50 };
51 return (fdClient);
52 }
les fonction de dialogue avec le client:
dialogue_client()
: lecture et traitement de la requête d'un clientenvoyer_document()
,document_non_trouve()
,requete_invalide()
.
1 /*
2 traitement-client.c
3 projet serveur WEB
4 Communication avec un client
5 */
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <unistd.h>
10 #include "declarations.h"
11 void dialogue_client(int fdClient, char repertoire[])
12 {
13 FILE *in, *out;
14 char verbe[100], nom_document[100];
15 int fd2;
16 /* Ouverture de fichiers de haut niveau */
17 in = fdopen(fdClient, "r");
18 /* note : si on attache out au même descripteur que in,
19 la fermeture de l'un entraine la fermeture de l'autre */
20 fd2 = dup(fdClient);
21 out = fdopen(fd2, "w");
22 /* lecture de la requête, du genre
23 "GET quelquechose ..." */
24 fscanf(in, "%100s %100s", verbe, nom_document);
25 fclose(in);
26 if (strcmp(verbe, "GET") == 0)
27 envoyer_document(out, nom_document, repertoire);
28 else
29 requete_invalide(out);
30 fflush(out); /* utile ? */
31 fclose(out);
32 }
33 void envoyer_document(FILE * out, char nom_document[],
34 char repertoire[])
35 {
36 char nom_fichier[100];
37 FILE *fichier;
38 char ligne[100];
39 sprintf(nom_fichier, "%s%s", repertoire, nom_document);
40 if (strstr(nom_fichier, "/../") != NULL) {
41 /* tentative d'accès hors du répertoire ! */
42 document_non_trouve(out, nom_document);
43 return;
44 };
45 fichier = fopen(nom_fichier, "r");
46 if (fichier == NULL) {
47 document_non_trouve(out, nom_document);
48 return;
49 };
50 fprintf(out,
51 "HTTP/1.1 200 OK" CRLF
52 "Server: " VERSION CRLF
53 "Content-Type: text/html; charset=iso-8859-1" CRLF
54 CRLF);
55 fprintf(out,
56 "<html><head><title>Fichier %s</title></head>"
57 "<body bgcolor=\"white\"><h1>Fichier %s</h1>" CRLF
58 "<center><table><tr><td bgcolor=\"yellow\"><listin
59 CRLF, nom_document, nom_fichier);
60 /* le corps du fichier */
61 while (fgets(ligne, 100, fichier) > 0) {
62 char *p;
63 for (p = ligne; *p != '\0'; p++) {
64 switch (*p) {
65 case '<':
66 fputs("<", out);
67 break;
68 case '>':
69 fputs(">", out);
70 break;
71 case '&':
72 fputs("&", out);
73 break;
74 case '\n':
75 fputs(CRLF, out);
76 break;
77 default:
78 fputc(*p, out);
79 };
80 };
81 };
82 /* balises de fin */
83 fputs("</listing></table></center></body></html>" CRLF,
84 }
85 void document_non_trouve(FILE * out, char nom_document[])
86 {
87 /* envoi de la réponse : entête */
88 fprintf(out,
89 "HTTP/1.1 404 Not Found" CRLF
90 "Server: MegaSoft 0.0.7 (CP/M)" CRLF
91 "Content-Type: text/html; charset=iso-8859-1" CRLF
92 CRLF);
93 /* corps de la réponse */
94 fprintf(out,
95 "<HTML><HEAD>" CRLF
96 "<TITLE>404 Not Found</TITLE>" CRLF
97 "</HEAD><BODY BGCOLOR=\"yellow\">" CRLF
98 "<H1>Pas trouvé !</H1>" CRLF
99 "Le document <font color=\"red\"><tt>%s</tt></font> "
100 "demandé<br>n'est pas disponible.<P>" CRLF
101 "<hr> Le webmaster"
102 "</BODY></HTML>" CRLF, nom_document);
103 fflush(out);
104 }
105 void requete_invalide(FILE * out)
106 {
107 fprintf(out,
108 "<HTML><HEAD>" CRLF
109 "<TITLE>400 Bad Request</TITLE>" CRLF
110 "</HEAD><BODY BGCOLOR=\"yellow\">" CRLF
111 "<H1>Bad Request</H1>"
112 "Vous avez envoyé une requête que "
113 "ce serveur ne comprend pas." CRLF
114 "<hr> Le webmaster" "</BODY></HTML>" CRLF);
115 fflush(out);
116 }
Exercice : modifier traitement-client
pour
qu'il traite le cas des répertoires. Il devra alors afficher
le nom des objets de ce répertoire, leur type, leur taille
et un lien.