Gérer plus que __FD_SETSIZE fd avec select

Avec utilisation obligatoire de select

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Dans le cadre d'un projet scolaire, je dois utiliser la fonction select sur un serveur pour gérer des clients. Pour avoir des points bonus, je me suis porté volontaire pour gérer le "C10K Problem" avec select.

"The C10K Problem" est une problématique qui consiste à gérer 10 000 clients simultanés. Le define __FD_SETSIZE vaut 1024, ce qui implique que select ne peux pas gérer plus que 1024 fd simultanéments. Dans le man de select, il est spécifié que ce sont les macros FD_* qui ne gèrent pas ce problème, qui induit à segmentation fault.

J'ai eu plusieurs idées pour gérer le problème, mais toutes échoues. Avez vous déjà eu ce genre de challenge, savez vous quels solutions pourraient être envisageables ?

Cordialement, Krostar.

J'ai eu plusieurs idées pour gérer le problème, mais toutes échoues.

Krostar

Pourrais-tu extrapoler là-dessus? Qu'est-ce que tu as déjà tenté? Pourquoi ça n'a pas fonctionné?

Ma première idée, si tu dois obligatoirement utiliser select, serait de simplement étaler les descripteurs sur 10 threads. Un seul appel à select ne peut peut-être pas gérer plus de 1024 descripteurs, mais rien n'empêche de faire 10 appels à select en parallèle (sous Linux du moins). Ça ne sera peut-être pas très performant, mais je crois que ça peut fonctionner.

Évidemment, pour que ça marche, il faut rehausser le limite de fichiers ouverts du processus serveur (avec setrlimit dans le programme, ou ulimit dans le shell).

Va pas y'avoir des problèmes si du code compilé dans la lib C dépend de la valeur antérieure FD_SETSIZE ? Par exemple, tu passes de 1024 à 2048 mais imagine que select fasse un check comme quoi l'index est inférieur à FD_SETSIZE (soit 1024 pour lui), ca coince.

Google me sort ca : https://stackoverflow.com/questions/7976388/increasing-limit-of-fd-setsize-and-select La réponse acceptée propose de faire une série de select à fenêtre tournante sur un tableau contenant le nombre d'éléments que tu veux (10 000 dans ton cas)

+0 -0

Va pas y'avoir des problèmes si du code compilé dans la lib C dépend de la valeur antérieure FD_SETSIZE ?

Davidbrcz

Oui, en effet. Il semble par contre que ce ne soit pas le cas de glibc (sauf sous Hurd). Les seules vérifications faites sur la valeur sont dans des fichiers inclus, et donc redéfinir la valeur de __FD_SETSIZE (et non FD_SETSIZE) après avoir inclut sys/select.h fonctionnerait peut-être (si en plus on augmente la limite de descripteurs ouverts du processus).

Cela dit, ça me semble une mauvaise idée de dépendre d'un détail d'implémentation de glibc. Une implémentation plus stricte pourrait très bien faire les vérifications directement dans son code compilé.

J'étais curieux d'essayer, et il semble que l'on peut faire un select sur plus de 1024 descripteurs en redéfinissant __FD_SETSIZE. C'est laid, mais voici un petit example qui fonctionne sur ma machine au bureau (CentOS 6, glibc 2.12, GCC 4.4.7):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#define _GNU_SOURCE

#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <sys/select.h>
#include <sys/types.h>

#define __FD_SETSIZE 2048
#define NUM_FDS 2000

int
main(void)
{
  int i, fds[NUM_FDS];

  struct rlimit lim;
  lim.rlim_cur = __FD_SETSIZE;
  lim.rlim_max = __FD_SETSIZE;
  setrlimit(RLIMIT_NOFILE, &lim);

  for (i = 0; i < NUM_FDS; i++) {
      char *name;
      asprintf(&name, "test%d", i);
      mkfifo(name, S_IRUSR | S_IWUSR);
      fds[i] = open(name, O_RDONLY | O_NONBLOCK);
      free(name);
  }

  fd_set rset;
  FD_ZERO(&rset);

  for (i = 0; i < NUM_FDS; i++)
      FD_SET(fds[i], &rset);

  select(NUM_FDS, &rset, NULL, NULL, NULL);
  for (i = 0; i < NUM_FDS; i++) {
      if (FD_ISSET(fds[i], &rset))
          printf("Data available on FD %d.\n", fds[i]);
  }

  return EXIT_SUCCESS;
}

Et un petit test qui montre que ça fonctionne:

1
2
3
4
5
6
7
8
$ gcc test.c
$ ./a.out &
[1] 7725
$ echo test > test1344
$ Data available on FD 1347.

[1]+  Done             ./a.out
$

Par contre sur ma machine, seuls les processus privilégiés peuvent augmenter leur limite de descripteurs ouverts à plus de 4096. Donc cette solution ne marcherait pas pour un select sur 10000 descripteurs (sauf si le serveur roule avec la capabilité CAP_SYS_RESOURCE).

J'ai résolu le problème peu après avoir posé la question, je reviens seulement maintenant pour en parler :

En effet, redéfinir __FD_SETSIZE permet de ne pas segve à cause de select avec plus de 1024 fd. Par contre, il faut (selon moi) procéder comme suit :

1
2
3
4
5
6
7
#include <sys/types.h> /* fichier qui défini __FD_SETSIZE */
#undef __FD_SETSIZE
#define __FD_SETSIZE 11264 /* redefine à +de10K */
/* includes demandés par select */ 
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>

et sinon, j'avais pensé divisé par 1024 chaques fd que je transmet aux macros FD_xxx et quand je les récupère je les remultiplies par le nombre de 1024 que j'avais enlevé, sauf que select utilise les macros donc ça peut pas marcher.

+0 -0

Il te faut voir aussi au niveau de ton système à combien est réglé le nombre max de file descriptors par processus.

et sinon, j'avais pensé divisé par 1024 chaques fd que je transmet aux macros FD_xxx et quand je les récupère je les remultiplies par le nombre de 1024 que j'avais enlevé, sauf que select utilise les macros donc ça peut pas marcher.

Krostar

Et surtout tu perds environ 3 chiffres.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte