Fonction à utiliser pour un serveur
(en mode connecté)
Remarque pour utiliser les fonctions suivantes, il est impératif d'avoir ouvert un socket au préalable !
Pour un processus serveur, après la phase de création du socket, il y a une seconde phase permettant l'utilisation de ce socket:
Il faudra dans cette phase:
- Attacher un socket à une adresse et à un port local - préparation à l'écoute/paramétrage du socket
- Passer en mode écoute du socket (on indiquera le nombre de client pouvant communiquer simultanément le processus serveur)
- Attendre sagement qu'un processus client se connecte
Attacher un socket à une adresse et à un port local - préparation à l'écoute/paramétrage du socket
Cette fonction est utilisée par le processus serveur juste après avoir créé un socket:il permet l'assignation d'une adresse au socket.
Lorsque l'on crée un socket, et que l'on désire que son programme puisse écouter ce qui vient de l'extérieur (cas des serveurs), il est nécessaire de rattaché le socket à une adresse locale et un port. Pour déterminer cette adresse, le processus serveur utilise l’appel bind :
bind (socket, adresse-locale, longueur_adresse)
En entrée:
socket est l'identifiant retourné lors de l'ouverture du socket
adresse Spécifie l'adresse d'une structure qui sera utilisée par le programme pour communiquer
La structure standard permet de représenter une adresse :
struct sockaddr {
u_char sa_len;/* longueur effective de l'adresse */
u_char sa_family; /* famille de protocole */
char sa_data[14]; /* Adresse */
}
- sa_len est un octet permettant de définir la longueur réelle
de l'adresse déclarée dans sa_data
- sa_family indique la famille de protocole (AF_INET pour TCP/IP)
- sa_data est une chaîne de 14 caractères (au maximum) contenant
l'adresse
Dans sa_data,
attention à l'ordre Network Byte Order
Dans ce qui suit, je vais donc vous expliquer comment renseigner sockaddr pour les adresses TCP/IP:
L’adresse est une structure de type sockaddr_in dont la spécification se trouve dans sys/socket.h.
struct sockaddr_in {
short sin_family; /* famille de protocole (AF_INET) */
u_short sin_port; /* numéro de port
*/
struct in_addr sin_addr; /* adresse internet */
char sin_zero[8]; /* inutilisé, initialisé à zéro
*/
}
- sin_family représente le type de famille et est similaire à
sa_family: devra être AF_INET
- sin_port indique le port à
contacter
- sin_addr indique l'adresse de l'hôte
- sin_zero[8] a initialiser avec des zéros
Les informations
sin_port rt sin_addr sont des informations utilisées au niveau du réseau,
elles doivent donc respecter Network Byte Order.
sin_port à
0, indique qu'il faut choisir le premier port
disponible
sin_addr positionné à la valeur générique INADDR_ANY. Cette valeur permet de définir un socket sur toutes les adresses d'une machine, c'est-à-dire que le programme utilisateur sera en mesure de récupérer des paquets réseaux à destination d'une des différentes adresses du système..
En principe, il faudrait respecter l'ordre Network Byte Order, étant donné qu'il s'agit d'une constante du système, ce serait plus propre. Mais cette valeur étant à 0, nombreux sont ceux qui ne le font pas...mais il ne faudrait pas que la valeur change !
Généralement,
pour passer une adresse IP, vous utiliserez 4 séries de nombres séparés
par des points (pour l'IPv4). Evidemment, ce n'est pas ce que va attendre sin_addr.
Cependant, il existe une fonction qui va permettre de convertir une chaîne de caractères au format sin_addr: inet_addr() ou mieux, inet_aton() expliquée ci-après.
De même, il devient de plus en plus fréquent d'utiliser un nom DNS (càd un nom humainement compréhensible) plutôt qu'une adresse ip. Là aussi, il y a une fonction qui permettra de convertir ce nom DNS en une adresse IP.
longueur_adresse indique la longueur de adresse-locale (utilisez sizeof())
Une fois cette structure renseignée, il suffira de passer cette structure complétée à la fonction bind. Sauf que celle-ci demande une structure de type sockaddr, il faudra donc faire un cast:
Si vous avez une structure sockaddr_in nommée sin et idsock, l'identifiant de votre socket, il suffira de faire:
bind(idsock, (SOCKADDR*)&sin, sizeof(sin));
Initialiser le socket dans un état d'écoute pour n clients
Il s'agit d'initialiser le socket dans un état d'écoute.
Cette fonction n'est utile que pour le mode connecté (TCP).
Il pourra ainsi détecter la connexion de clients. Le nombre maximum de clients pouvant être mis en attente étant indiqué dans la fonction suivante:
int listen(int socket, int backlog)
En entrée:
socket, l'identifiant de votre socket...
backlog, étant le nombre maximum de clients pouvant être mis en attente sur votre serveur.
En sortie:
SOCKET_ERROR si erreur.
Permettre une connexion en acceptant un appel
La fonction accept() permet la connexion en acceptant un appel. En fait, elle va lire une file d'attente contenant tous les clients ayant fait une demande de connexion (un connect()) qui est non encore traité. Pour cette nouvelle connexion, un nouvel identifiant de socket est retourné et devra être utiliser par la suite pour permettre une communication avec ce client (voir chapitre suivante).
L'identifiant que vous avez avec le socket que vous avez créé restera donc disponible pour écouter s'il y a des nouvelles connexions...
int accept(int socket,struct sockaddr * addrclt,socklen_t* addrlen)
En entrée:
socket : Toujours votre identifiant de socket
addrclt : Tampon qui recevra le contexte d'adressage du client.
addrlen : Adresse d'un entier (pour le moment socklen_t est en fait un entier) qui sera alimenté au préalable à la taille de addrclt
En sortie:
La fonction accept() retourne un identificateur du socket de réponse. Si une erreur intervient la fonction accept() retourne la valeur INVALID_SOCKET.
Exemple:
socklen_t taille = sizeof(csin);
csock = accept(sock, (SOCKADDR*)&csin, &taille);
La fonction
accept() est une fonction bloquante. Elle ne rendra la main que lorsqu'un client
demandera la connexion !
En autre, l'interception d'un signal SIGINT via la commande signal () en C provoque la relance de accept. Utilisez dans ce cas sigaction () pour affiner le paramétrage pour l'interception du signal.
Etant donné
que vous avez accepté une connexion pour un client, mais qu'il y a peut-être
d'autres clients qui veulent aussi communiqué, vous allez devoir refaire
d'autres accept qui est une fonction bloquante !
Par conséquent, pour pouvoir communiquer avec votre client et traiter les autres, vous devrez utiliser des fonctions permettant de paralleliser vos traitements:
Sous Linux, vous avez deux possibilités: soit en créant un processus fils par la commande fork() ou soit en utilisant les threads.
Dans
le cas du fork, il reste à résoudre un problème de "zombies". En effet, la
fonction accept est une fonction bloquante. Le processus parent ne peux donc
pas intercepter immédiatement la mort d'un fils (avec waitpid). Sans réponse,
ces fils ayant mis fin à leur jour resteront en mode "zombie" jusqu'à ce que
le père soit enfin libéré de la fonction accept pour lire les signaux de ses
fils.
Sous Windows: en utilisant les threads.