Champ Valeur
Auteur·e Élise
Édition 2024-04-03
Durée 2 sessions
Taille des équipes 1~4 personnes
login des auteurs·rices dans author.txt
Rendu via git, dépôt $YEAR_lab_poll, droits en lecture à delivery_collector
Compilation lignes de compilation dans justfile : exo1, exo2
Binaires N/A

Notation

La réalisation de ce TP ne donne pas une note propre. Cependant, il contribuera à votre note finale de projet sur Clavardage si vous ne le réussissez pas.

Introduction

Quand on utilise des appels comme accept et read, on ne sait pas à l’avance s’ils vont bloquer. Sauf que si on a un programme qui doit pouvoir gérer plusieurs clients en même temps et de potentielles nouvelles connexion : bloquer sur un appel ça signifie ne rien pouvoir faire tant qu’il ne renvoie pas.

Pour exemple : mettons nous avons 2 clients connectés, on veut s’avoir si le premier à dit quelque chose donc on read sur sa socket. Il ne dit rien, donc l’appel bloque. Alors que nous sommes bloqués, le second client nous envoie un message. Problème : on est toujours bloqué par le premier write, donc on ne peut pas encore traiter le message. Tant que le client 1 n’envoie rien ou qu’il ne se déconnecte pas le programme ne pourra rien faire. Tout ce que le client 2 nous enverra, nous ne pourrons rien en faire. Le noyau garde une file d’attente des messages reçus sur le réseau qui n’ont pas encore été lus, mais au bout d’un moment, il y a un risque de pertes de données.

La question est donc : est-ce qu’il existe un moyen de savoir s’il y a quelque chose à lire sur un descripteur de fichier ?

La réponse : oui, il s’agit des appels systèmes poll et select. On va apprendre à se servir de poll car l’utilisation de select n’est pas recommandée pour les nouveaux projets, mais ça reste intéressant de connaitre son existence. Vous rencontrerez surement des “Selectors” à l’avenir dans d’autres langages de programmation ou dans des bibliothèques.

Poll

poll.h défini le prototype et la structure suivants :

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
	int   fd;         /* file descriptor */
	short events;     /* requested events */
	short revents;    /* returned events */
};

Il y est aussi notamment défini les bitflags :

  • POLLIN il y a quelque chose à lire
  • POLLOUT il est possible d’écrire
  • POLLHUP le pair à raccroché, s’est déconnecté (HUP = hang up)
  • POLLERR il y a eu une erreur

On peut se servir de ses bitflags pour demander à poll de nous prévenir dans le cas où il y a quelque chose à lire ou s’il est possible d’écrire et lui nous préviendra si l’une de ses choses ou les deux est possible ou s’il y a eu une déconnexion ou une erreur.

La façon de s’y prendre est la suivante :

Déjà on travaille avec un tableau de struct pollfds :

struct pollfd *fds;

fds = malloc(sizeof(struct pollfd) * 2);
fds[0].fd = // [votre socket d'accept]
fds[0].events = POLLIN; // on veut être prévenus quand quelque chose arrive en entrée
fds[1].fd = // [votre premier client déjà accepté]
fds[1].events = POLLIN;

On peut passer le tableau et sa taille à poll :

int res_poll;

res_poll = poll(fds, 2, -1); // dernier arg à -1, regardez ce que ça veut dire dans le man

Après cet appel, poll va avoir enregistré les événements qu’on lui a demandé de surveiller, s’il se sont produits, ou les éventuelles erreurs/déconnexions reçues.

On peut les tester ainsi :

if (fds[0].revents & POLLIN) {
	// il y a une connexion à accepter
}
if (fds[1].revents & POLLIN) {
	// le client 1 nous parle
}
// etc

Il ne vous restera qu’à implémenter ce qu’il se passe ensuite dans le cas où :

  • un client chercher à se connecter, le rajouter à votre fds :
    • et donc penser à l’avance si vous allez gérer votre tableau à la façon d’un vector ou d’une pool ;
  • un client vous parle ;
  • un client s’est déconnecté :
    • et penser à comment le retirer de vos fds.

Exercice : mini_netcat

Reprenez l’exercice connect, du premier TP et faites en sorte qu’à tout instant il soit possible de recevoir un message du serveur ou d’en écrire un sur l’entrée standard. La lecture sur STDIN ne doit pas bloquer. La lecture sur la socket ne doit pas bloquer.

Exercice : multi echo

Écrivez un programme qui fait office de serveur. Il prend en argument :

  • le port sur lequel bind et écouter ;
  • un nombre maximum de clients à gérer.

Le serveur doit pouvoir accepter de nouvelles connexions à tout instant. Lorsqu’un client envoie un message, il doit lui être renvoyé.

Lorsqu’un client se déconnecte, il doit être retiré de la liste de clients et ne compte plus dans la limite de client admissibles.