E/S bloquantes ou non bloquantes
Comprenez l’importance des boucles d’événements.
E/S bloquantes ou non bloquantes est une leçon C Academy gratuite sur CoddyKit. Ceci est la leçon 1 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage C Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours C Academy comprend 4 leçons au total.
Signification du mode bloquant
Un appel système bloquant suspend le thread appelant jusqu'à ce que l'opération puisse continuer. Lorsque vous appelez recv() sur une socket sans données, le noyau met votre thread en attente jusqu'à l'arrivée des octets.
C'est simple à comprendre : une connexion, un thread, un code exécuté en ligne droite. Le coût apparaît lorsque vous devez servir des milliers de clients simultanément.
ssize_t n = recv(fd, buf, sizeof buf, 0);
/* thread sleeps here until data or error */
if (n > 0) handle(buf, n);Le problème du passage à l'échelle
Avec les entrées-sorties bloquantes, un client bloqué immobilise tout le thread. La solution classique consiste à utiliser un thread — ou un processus — par connexion.
Cela fonctionne jusqu'à un certain point, mais 10 000 threads signifient 10 000 piles, de nombreux changements de contexte et une surcharge du planificateur. C'est le célèbre problème C10k, qui a incité les serveurs à adopter des architectures pilotées par les événements.
Mode non bloquant
Une socket non bloquante ne se met jamais en sommeil. Si un appel ne peut pas se terminer immédiatement, il renvoie aussitôt -1 et définit errno sur EAGAIN ou EWOULDBLOCK.
Votre code doit alors réessayer plus tard. Un seul thread peut ainsi gérer de nombreuses sockets sans jamais rester bloqué sur l'une d'elles.
ssize_t n = recv(fd, buf, sizeof buf, 0);
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
/* no data right now, try again later */
}Configurer O_NONBLOCK avec fcntl
Vous rendez un descripteur non bloquant en ajoutant l'indicateur O_NONBLOCK avec fcntl(). Lisez toujours d'abord les indicateurs actuels, puis combinez-les avec OR et le bit voulu, afin de ne pas écraser les autres réglages.
Ce même utilitaire s'utilise aussi bien avec les sockets d'écoute, les sockets clientes acceptées qu'avec les tubes.
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}Gérer les lectures partielles
Les entrées-sorties non bloquantes rendent les opérations partielles normales. Un appel recv() peut renvoyer moins d'octets que demandé, et send() peut n'accepter qu'une partie de votre tampon.
Vous devez suivre la quantité déjà envoyée ou reçue et reprendre à partir de là. Ne supposez jamais qu'un seul appel transfère tous les octets.
size_t sent = 0;
while (sent < len) {
ssize_t w = send(fd, buf + sent, len - sent, 0);
if (w < 0) { if (errno == EAGAIN) break; else return -1; }
sent += w;
}L'attente active est une mauvaise idée
La manière naïve d'utiliser des sockets non bloquantes consiste à parcourir toutes les sockets en réessayant constamment. Cette attente active consomme 100 % du CPU même lorsqu'il ne se passe rien.
Ce que nous voulons vraiment, c'est demander au noyau : « indiquez-moi quels descripteurs sont prêts et laissez-moi dormir jusque-là ». C'est exactement ce que fournit la notification de disponibilité.
Notification de disponibilité
Le multiplexage des entrées-sorties permet à un thread d'attendre simultanément de nombreux descripteurs et de se réveiller dès qu'au moins l'un d'eux est prêt. Le noyau se charge de la surveillance.
Les interfaces classiques sont select() et poll(). Elles fonctionnent, mais rescannent chaque descripteur à chaque appel, ce qui devient coûteux à grande échelle.
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
select(fd + 1, &rfds, NULL, NULL, NULL);Pourquoi select et poll ne passent pas à l'échelle
select() et poll() sont tous deux en O(n) : chaque appel transmet l'ensemble des descripteurs au noyau, qui les parcourt tous, puis vous les parcourez encore pour trouver ceux qui sont prêts.
select() est également limité par FD_SETSIZE (souvent 1024). Pour des milliers de connexions, cette surcharge devient prédominante.
Voici epoll
epoll est la solution évolutive de Linux. Vous enregistrez une fois votre intérêt pour un descripteur, puis le noyau conserve une structure de données interne qui suit leur disponibilité.
Chaque attente ne renvoie que les descripteurs réellement prêts ; le coût dépend donc des connexions actives, et non du nombre total de connexions. Il est ainsi approximativement en O(1) par événement prêt.
int epfd = epoll_create1(0);
/* register fds once, then wait for ready events */Non bloquant et epoll
epoll et les sockets non bloquantes forment une équipe. epoll vous indique qu'un descripteur est prêt ; les appels non bloquants vous permettent de le vider sans jamais vous mettre en sommeil.
Vous devez toujours définir O_NONBLOCK sur les sockets transmises à epoll. Sinon, un réveil intempestif ou une lecture partielle pourrait bloquer votre thread unique de boucle d'événements.
set_nonblocking(conn_fd);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn_fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);Le modèle mental
Imaginez le serveur comme une boucle : bloquez dans epoll_wait(), récupérez une courte liste de descripteurs prêts, effectuez un traitement non bloquant sur chacun, puis recommencez.
Le thread dort lorsqu'il n'y a rien à faire et ne se réveille que pour du travail réel. Un seul thread peut maintenant servir efficacement des dizaines de milliers de connexions.
Vérification rapide
Vérifiez votre compréhension des sockets non bloquantes.
Récapitulatif
Les entrées-sorties bloquantes sont simples, mais mobilisent un thread par connexion, ce qui ne passe pas à l'échelle. Les entrées-sorties non bloquantes renvoient immédiatement EAGAIN au lieu de mettre le thread en sommeil.
Interroger les sockets en boucle serrée gaspille le CPU ; nous utilisons donc la notification de disponibilité. select/poll sont en O(n), tandis que epoll peut gérer plusieurs milliers de connexions. Nous allons maintenant configurer epoll.
Questions Fréquemment Posées
La leçon « E/S bloquantes ou non bloquantes » est-elle gratuite ?
Oui — le texte complet de « E/S bloquantes ou non bloquantes » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours C Academy, passe à CoddyKit PRO. Le cours C Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « E/S bloquantes ou non bloquantes » ?
Comprenez l’importance des boucles d’événements. Tu pratiques C Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer C Academy ?
Aucune expérience préalable n'est requise. C Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 1 sur 4.
Combien de temps prend la leçon « E/S bloquantes ou non bloquantes » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon C Academy ?
Oui. Chaque leçon C Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- E/S bloquantes ou non bloquantes
- Configurer epoll
- La boucle d’événements
- Un serveur d’écho simple