Skip to content

Commit c15cbef

Browse files
committed
Amélioration multiplexage réseau, regex et Cap'n Proto + corrections documentation
1 parent 2a2052a commit c15cbef

18 files changed

+312
-312
lines changed

22-networking/03-multiplexage-io.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ Considérons ce scénario concret :
1212
// Serveur naïf : bloquant, un seul client à la fois
1313
int client_fd = accept(server_fd, nullptr, nullptr); // Bloque jusqu'à une connexion
1414

15-
char buffer[4096];
16-
ssize_t n = recv(client_fd, buffer, sizeof(buffer), 0); // Bloque jusqu'à réception
15+
char buffer[4096];
16+
ssize_t n = recv(client_fd, buffer, sizeof(buffer), 0); // Bloque jusqu'à réception
1717
// Pendant ce recv(), AUCUN autre client ne peut être servi
1818
```
1919

22-networking/03.1-select-poll.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ Les `fd_set` se manipulent exclusivement via quatre macros :
4545
```cpp
4646
fd_set fds;
4747
48-
FD_ZERO(&fds); // Initialise l'ensemble à vide (obligatoire)
49-
FD_SET(fd, &fds); // Ajoute fd à l'ensemble
50-
FD_CLR(fd, &fds); // Retire fd de l'ensemble
51-
FD_ISSET(fd, &fds); // Teste si fd est dans l'ensemble (après select)
48+
FD_ZERO(&fds); // Initialise l'ensemble à vide (obligatoire)
49+
FD_SET(fd, &fds); // Ajoute fd à l'ensemble
50+
FD_CLR(fd, &fds); // Retire fd de l'ensemble
51+
FD_ISSET(fd, &fds); // Teste si fd est dans l'ensemble (après select)
5252
```
5353

5454
> 🔥 **Piège critique** : `select` **modifie** les `fd_set` passés en paramètre. Après l'appel, seuls les descripteurs effectivement prêts restent dans l'ensemble. Il faut donc **reconstruire les ensembles à chaque itération** de la boucle événementielle.
@@ -218,12 +218,12 @@ La séparation `events` / `revents` est une amélioration majeure par rapport à
218218
### Constantes d'événements
219219

220220
```cpp
221-
POLLIN // Données disponibles en lecture
222-
POLLOUT // Écriture possible sans blocage
223-
POLLPRI // Données urgentes (out-of-band)
224-
POLLERR // Erreur sur le descripteur (revents uniquement)
225-
POLLHUP // Déconnexion du pair (revents uniquement)
226-
POLLNVAL // Descripteur invalide (revents uniquement)
221+
POLLIN // Données disponibles en lecture
222+
POLLOUT // Écriture possible sans blocage
223+
POLLPRI // Données urgentes (out-of-band)
224+
POLLERR // Erreur sur le descripteur (revents uniquement)
225+
POLLHUP // Déconnexion du pair (revents uniquement)
226+
POLLNVAL // Descripteur invalide (revents uniquement)
227227
```
228228

229229
`POLLERR`, `POLLHUP` et `POLLNVAL` sont toujours surveillés implicitement — inutile de les spécifier dans `events`, ils apparaissent dans `revents` si la condition se produit.

22-networking/03.2-epoll.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ Crée une instance epoll et retourne un descripteur de fichier qui la représent
6060
Le seul flag utile est `EPOLL_CLOEXEC`, qui positionne le flag close-on-exec pour éviter que le descripteur soit hérité par des processus fils créés avec `exec`. C'est une bonne pratique systématique :
6161
6262
```cpp
63-
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
64-
if (epoll_fd == -1) {
63+
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
64+
if (epoll_fd == -1) {
6565
std::print(stderr, "epoll_create1 : {}\n", strerror(errno));
6666
return 1;
6767
}
@@ -140,8 +140,8 @@ Commençons par le mode par défaut, level-triggered (LT), qui est le plus simpl
140140
#include <cstring>
141141
#include <print>
142142

143-
constexpr int max_events = 64;
144-
constexpr size_t buf_size = 4096;
143+
constexpr int max_events = 64;
144+
constexpr size_t buf_size = 4096;
145145

146146
bool set_nonblocking(int fd) {
147147
int flags = fcntl(fd, F_GETFL, 0);
@@ -286,10 +286,10 @@ Plusieurs points méritent d'être soulignés dans cet exemple :
286286
Le mode edge-triggered (ET) s'active en ajoutant le flag `EPOLLET` lors de l'enregistrement :
287287
288288
```cpp
289-
epoll_event ev{};
290-
ev.events = EPOLLIN | EPOLLET; // Edge-triggered
291-
ev.data.fd = client_fd;
292-
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);
289+
epoll_event ev{};
290+
ev.events = EPOLLIN | EPOLLET; // Edge-triggered
291+
ev.data.fd = client_fd;
292+
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);
293293
```
294294

295295
En mode ET, le noyau ne vous notifie qu'**une seule fois** lors de la transition de « pas de données disponibles » à « données disponibles ». Si vous ne lisez pas toutes les données lors de cette notification, vous ne serez pas renotifié — même si des données restent dans le buffer — jusqu'à ce que de *nouvelles* données arrivent.
@@ -402,10 +402,10 @@ struct Connection {
402402
// À l'accept d'une nouvelle connexion :
403403
auto* conn = new Connection{.fd = client_fd};
404404
405-
epoll_event ev{};
406-
ev.events = EPOLLIN;
407-
ev.data.ptr = conn; // Le noyau retournera ce pointeur tel quel
408-
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);
405+
epoll_event ev{};
406+
ev.events = EPOLLIN;
407+
ev.data.ptr = conn; // Le noyau retournera ce pointeur tel quel
408+
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);
409409
410410
// Dans la boucle événementielle :
411411
for (int i = 0; i < n; ++i) {
@@ -417,8 +417,8 @@ for (int i = 0; i < n; ++i) {
417417
}
418418
419419
// À la déconnexion :
420-
close(conn->fd);
421-
delete conn;
420+
close(conn->fd);
421+
delete conn;
422422
```
423423

424424
> ⚠️ `data` est une **union** : `fd`, `ptr`, `u32` et `u64` partagent le même espace mémoire. Si vous utilisez `data.ptr`, vous ne pouvez pas simultanément lire `data.fd` — il faut stocker le descripteur dans votre structure `Connection`.

22-networking/03.3.1-architecture-sq-cq.md

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,9 @@ Les SQ et CQ sont des **files circulaires** (ring buffers) basées sur deux comp
211211
Producteur : APPLICATION Consommateur : NOYAU
212212
──────────────────────── ────────────────────
213213

214-
L'application écrit des Le noyau lit les indices
215-
indices dans sq_array[tail] depuis sq_array[head]
216-
puis incrémente tail. puis incrémente head.
214+
L'application écrit des Le noyau lit les indices
215+
indices dans sq_array[tail] depuis sq_array[head]
216+
puis incrémente tail. puis incrémente head.
217217

218218
head tail
219219
│ │
@@ -266,11 +266,11 @@ Ce protocole garantit que le consommateur ne lit jamais une entrée partiellemen
266266
unsigned idx = sq_tail & sq_mask; // Masque = taille_ring - 1 (puissance de 2)
267267
268268
// 2. Remplir le SQE
269-
sqe_array[idx].opcode = IORING_OP_RECV;
270-
sqe_array[idx].fd = client_fd;
271-
sqe_array[idx].addr = reinterpret_cast<__u64>(buffer);
272-
sqe_array[idx].len = buffer_size;
273-
sqe_array[idx].user_data = my_context_id;
269+
sqe_array[idx].opcode = IORING_OP_RECV;
270+
sqe_array[idx].fd = client_fd;
271+
sqe_array[idx].addr = reinterpret_cast<__u64>(buffer);
272+
sqe_array[idx].len = buffer_size;
273+
sqe_array[idx].user_data = my_context_id;
274274
275275
// 3. Publier l'index dans la SQ
276276
sq_array[sq_tail & sq_mask] = idx;
@@ -292,9 +292,9 @@ if (head == *cq_tail_ptr) {
292292
}
293293

294294
// 3. Lire le CQE
295-
struct io_uring_cqe *cqe = &cq_array[head & cq_mask];
296-
uint64_t context = cqe->user_data;
297-
int32_t result = cqe->res;
295+
struct io_uring_cqe *cqe = &cq_array[head & cq_mask];
296+
uint64_t context = cqe->user_data;
297+
int32_t result = cqe->res;
298298

299299
// 4. Avancer head (consommer le CQE)
300300
io_uring_smp_store_release(cq_head_ptr, head + 1);
@@ -331,23 +331,23 @@ En pratique, dimensionnez la CQ suffisamment grande pour ne jamais déborder dan
331331
L'application prépare ses SQE, les pousse dans la SQ, puis appelle `io_uring_enter` pour notifier le noyau. C'est un appel système, mais un seul peut soumettre un lot arbitrairement grand de SQE — le coût est **amorti sur le nombre d'opérations**.
332332
333333
```
334-
App: préparer SQE[0], SQE[1], SQE[2]
335-
App: io_uring_enter(to_submit=3, min_complete=1) ← 1 seul appel système
334+
App: préparer SQE[0], SQE[1], SQE[2]
335+
App: io_uring_enter(to_submit=3, min_complete=1) ← 1 seul appel système
336336
pour 3 opérations + attente
337-
Kernel: exécute les 3 opérations
338-
Kernel: dépose 2 CQE (2 sont terminées)
339-
App: récolte les 2 CQE
337+
Kernel: exécute les 3 opérations
338+
Kernel: dépose 2 CQE (2 sont terminées)
339+
App: récolte les 2 CQE
340340
```
341341
342342
### Mode SQPOLL : zéro appel système
343343
344344
Avec le flag `IORING_SETUP_SQPOLL`, le noyau crée un **kernel thread dédié** qui scrute la SQ en permanence. L'application n'a qu'à écrire dans la SQ — pas besoin d'appeler `io_uring_enter` pour soumettre.
345345
346346
```
347-
App: préparer SQE[0] ← simple écriture mémoire
348-
App: préparer SQE[1] ← simple écriture mémoire
349-
Kernel thread: détecte les nouvelles SQE, les exécute
350-
App: récolte les CQE ← simple lecture mémoire
347+
App: préparer SQE[0] ← simple écriture mémoire
348+
App: préparer SQE[1] ← simple écriture mémoire
349+
Kernel thread: détecte les nouvelles SQE, les exécute
350+
App: récolte les CQE ← simple lecture mémoire
351351
```
352352
353353
Le kernel thread se met en veille après un délai d'inactivité configurable (`sq_thread_idle` dans `params`). Si l'application détecte que le thread dort (via un flag dans la SQ), elle doit appeler `io_uring_enter` pour le réveiller.
@@ -365,17 +365,17 @@ Le flag `IOSQE_IO_LINK` permet de créer des **chaînes de dépendances** entre
365365
Un cas d'usage classique : « lire depuis un fichier, puis envoyer sur un socket » :
366366
367367
```
368-
SQE[0]: IORING_OP_READ (fichier → buffer) flags = IOSQE_IO_LINK
369-
SQE[1]: IORING_OP_SEND (buffer → socket) flags = 0 (fin de chaîne)
368+
SQE[0]: IORING_OP_READ (fichier → buffer) flags = IOSQE_IO_LINK
369+
SQE[1]: IORING_OP_SEND (buffer → socket) flags = 0 (fin de chaîne)
370370
```
371371
372372
Si la lecture échoue, l'envoi n'est jamais tenté. Sans le chaînage, il faudrait attendre le CQE de la lecture, vérifier le résultat, puis soumettre l'envoi — ce qui double la latence.
373373
374374
On peut aussi ajouter un **timeout** à une chaîne :
375375
376376
```
377-
SQE[0]: IORING_OP_RECV flags = IOSQE_IO_LINK
378-
SQE[1]: IORING_OP_LINK_TIMEOUT (5 secondes)
377+
SQE[0]: IORING_OP_RECV flags = IOSQE_IO_LINK
378+
SQE[1]: IORING_OP_LINK_TIMEOUT (5 secondes)
379379
```
380380
381381
Si le `recv` ne se termine pas dans les 5 secondes, il est annulé. Ce pattern remplace les timers manuels courants avec `epoll`.

22-networking/03.3.2-liburing.md

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ Ce que liburing gère pour vous :
2424

2525
```bash
2626
# Ubuntu 22.04+
27-
sudo apt update
28-
sudo apt install liburing-dev
27+
sudo apt update
28+
sudo apt install liburing-dev
2929
```
3030

3131
Le paquet installe les headers (`<liburing.h>`) et la librairie statique/dynamique. Vérifiez la version installée :
@@ -41,37 +41,37 @@ Sur Ubuntu 24.04 LTS, le paquet fournit liburing 2.5+, ce qui est suffisant pour
4141
Pour accéder aux toutes dernières fonctionnalités (notamment le support des opcodes récents) :
4242

4343
```bash
44-
git clone https://github.com/axboe/liburing.git
45-
cd liburing
44+
git clone https://github.com/axboe/liburing.git
45+
cd liburing
4646
./configure --prefix=/usr/local
47-
make -j$(nproc)
48-
sudo make install
47+
make -j$(nproc)
48+
sudo make install
4949
```
5050

5151
### Intégration CMake
5252

5353
```cmake
5454
# Recherche du paquet système
55-
find_package(PkgConfig REQUIRED)
56-
pkg_check_modules(URING REQUIRED liburing)
55+
find_package(PkgConfig REQUIRED)
56+
pkg_check_modules(URING REQUIRED liburing)
5757
58-
add_executable(my_server main.cpp)
59-
target_link_libraries(my_server PRIVATE ${URING_LIBRARIES})
60-
target_include_directories(my_server PRIVATE ${URING_INCLUDE_DIRS})
58+
add_executable(my_server main.cpp)
59+
target_link_libraries(my_server PRIVATE ${URING_LIBRARIES})
60+
target_include_directories(my_server PRIVATE ${URING_INCLUDE_DIRS})
6161
```
6262

6363
Alternative minimaliste si liburing est installée dans un chemin standard :
6464

6565
```cmake
66-
add_executable(my_server main.cpp)
67-
target_link_libraries(my_server PRIVATE uring)
66+
add_executable(my_server main.cpp)
67+
target_link_libraries(my_server PRIVATE uring)
6868
```
6969

7070
### Avec FetchContent (sans dépendance système)
7171

7272
```cmake
73-
include(FetchContent)
74-
FetchContent_Declare(
73+
include(FetchContent)
74+
FetchContent_Declare(
7575
liburing
7676
GIT_REPOSITORY https://github.com/axboe/liburing.git
7777
GIT_TAG liburing-2.7 # Adapter à la version souhaitée
@@ -280,9 +280,9 @@ int get_fd(uint64_t user_data) {
280280
281281
// ── Buffers de lecture (un par connexion possible) ──
282282
// Simplification : tableau statique indicé par fd.
283-
constexpr int max_conns = 4096;
284-
constexpr size_t buf_size = 4096;
285-
char buffers[max_conns][buf_size];
283+
constexpr int max_conns = 4096;
284+
constexpr size_t buf_size = 4096;
285+
char buffers[max_conns][buf_size];
286286
287287
// ── Helpers pour préparer les opérations ──
288288
@@ -546,8 +546,8 @@ Les fonctions liburing suivent deux conventions selon la catégorie :
546546

547547
```cpp
548548
// Convention liburing : le code d'erreur est dans la valeur de retour
549-
int ret = io_uring_submit(&ring);
550-
if (ret < 0) {
549+
int ret = io_uring_submit(&ring);
550+
if (ret < 0) {
551551
std::print(stderr, "submit : {}\n", strerror(-ret)); // Notez le -ret
552552
}
553553

@@ -560,8 +560,8 @@ if (cqe->res < 0) {
560560
**`io_uring_get_sqe`** retourne `nullptr` si la SQ est pleine. Ce cas peut survenir si vous préparez plus de SQE que la taille de la SQ sans soumettre entre-temps. La réponse est soit d'augmenter la taille du ring, soit de soumettre plus fréquemment :
561561

562562
```cpp
563-
io_uring_sqe *sqe = io_uring_get_sqe(&ring);
564-
if (!sqe) {
563+
io_uring_sqe *sqe = io_uring_get_sqe(&ring);
564+
if (!sqe) {
565565
// SQ pleine : soumettre les SQE en attente pour libérer de la place
566566
io_uring_submit(&ring);
567567
sqe = io_uring_get_sqe(&ring);
@@ -607,8 +607,8 @@ liburing est une bibliothèque C. En C++ idiomatique, on souhaite une gestion RA
607607
#include <cstring>
608608
#include <utility>
609609

610-
class IoUring {
611-
public:
610+
class IoUring {
611+
public:
612612
explicit IoUring(unsigned entries, unsigned flags = 0) {
613613
if (int ret = io_uring_queue_init(entries, &ring_, flags); ret < 0) {
614614
throw std::runtime_error{

22-networking/03.3.3-cas-usage.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ void prep_timeout(io_uring* ring, __kernel_timespec* ts, unsigned count) {
325325
326326
// Usage :
327327
__kernel_timespec ts{.tv_sec = 5, .tv_nsec = 0};
328-
prep_timeout(&ring, &ts, 0); // count=0 : ne pas compter les CQE, juste le temps
329-
io_uring_submit(&ring);
328+
prep_timeout(&ring, &ts, 0); // count=0 : ne pas compter les CQE, juste le temps
329+
io_uring_submit(&ring);
330330
331331
// Le CQE du timeout aura res == -ETIME si le délai a expiré,
332332
// ou res == 0 si le count a été atteint avant.
@@ -445,8 +445,8 @@ uint64_t encode(OpType type, Request* req) {
445445
(reinterpret_cast<uint64_t>(req) & 0x00FFFFFFFFFFFFFF);
446446
}
447447
448-
OpType get_type(uint64_t ud) { return static_cast<OpType>(ud >> 56); }
449-
Request* get_req(uint64_t ud) {
448+
OpType get_type(uint64_t ud) { return static_cast<OpType>(ud >> 56); }
449+
Request* get_req(uint64_t ud) {
450450
uint64_t addr = ud & 0x00FFFFFFFFFFFFFF;
451451
return reinterpret_cast<Request*>(addr);
452452
}

22-networking/exemples/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ io_uring cleanup OK
108108
- **Execution** : `./ex03_iouring_file /etc/hostname`
109109
- **Sortie attendue** :
110110
```
111-
Soumission de 1 lectures asynchrones (N octets)
112-
Lecture terminee : N octets lus, 0 erreurs
111+
Soumission de 1 lectures asynchrones (N octets)
112+
Lecture terminee : N octets lus, 0 erreurs
113113
```
114114
- **Prerequis** : `sudo apt install liburing-dev`
115115

0 commit comments

Comments
 (0)