Prise non bloquante accepter sans spinlock en C

Dupliquer possible:
Le thread de réveil est bloqué lors de l’appel accept ()

J’écris un petit serveur qui écoute les connexions (les accepte et les transmet aux threads de travail) jusqu’à ce qu’un signal d’arrêt personnalisé soit envoyé.

Si j’utilise des sockets de blocage, ma boucle d’acceptation principale ne peut pas être interrompue lors de l’envoi du signal d’arrêt personnalisé. Cependant, je souhaite éviter d’avoir une boucle occupation-attente / spinlock avec un socket non bloquant.

Ce que je veux, c’est que ma boucle d’acceptation principale se bloque jusqu’à ce qu’une connexion soit reçue ou que le signal d’arrêt soit envoyé.

Est-ce possible en C sous Linux?

Merci beaucoup.

Si je comprends bien, vous êtes prêt à utiliser tout type de “signal”, pas nécessairement un signal POSIX. En réalité, les signaux POSIX sont un mauvais choix car vérifier si vous en avez reçu un dans une boucle présente des conditions de concurrence inévitables.

Ce que vous devez utiliser, c’est tout ce qui peut être contrôlé à l’aide d’un descripteur de fichier. Il pourrait être:

  • Un tuyau. Votre boucle d’acceptation surveille l’extrémité de lecture du tuyau. Pour réactiver la boucle, un autre thread écrit quelque chose dans la fin de l’écriture (peu importe).
  • Une autre prise. De même, un autre thread réveille votre boucle d’acceptation en écrivant à l’autre extrémité du socket.
  • Un fichier dans le système de fichiers que vous surveillez en utilisant inotify.
  • Un appareil qui reçoit des données lorsque la boucle doit être interrompue.
  • etc…

Les entrées suivantes dans la liste des exemples ne sont généralement pas pratiques. Ils sont juste pour illustrer qu’il peut s’agir de n’importe quel type d’object, à condition qu’il possède un descripteur de fichier contrôlable. Le moyen le plus simple, le moins cher et le plus populaire est le tuyau.

Si vous utilisez déjà des sockets non bloquants, vous avez certainement déjà une sorte de boucle de scrutation pour vérifier quand ils sont prêts à accepter les connexions. Je vais supposer que vous utilisez poll() pour le faire.

Avant de commencer votre boucle, configurez un tuyau comme ceci:

 pipe(&pipefd[0]); read_end = pipefd[0]; write_end = pipefd[1]; fcntl(read_end, F_SETFL, O_NONBLOCK); 

Le paramètre fcntl doit définir l’extrémité de lecture du tuyau en mode non bloquant. Vous allez utiliser cette extrémité du tuyau dans votre appel de poll avec votre socket, il doit donc être non bloquant.

Ajoutez simplement l’extrémité de lecture du tube à la liste des descripteurs de mosaïque que vous surveillez dans votre boucle d’acceptation:

 for (;;) { /* accept loop, loop forever */ /* Monitor your socket. You should already have this in your code */ pollfd[0].fd = your_socket; pollfd[1].events = POLLIN; /* Add a new entry to monitor the read end of the pipe */ pollfd[1].fd = read_end; pollfd[1].events = POLLIN; /* Now call poll, as before, but monitor 2 file descriptors instead of just 1 */ n = poll(&pollfd[0], 2, -1); /* Check if your socket is ready to accept. This part, you are already doing. */ if (pollfd[0].revents) { newsocket = accept(your_socket, etc....) do_somehting_with(new_socket); } /* New part: check if anyone has written something for you into your pipe */ if (pollfd[1].revents) { break; /* stop the loop */ } } 

L’exemple utilise poll mais vous utilisez peut-être plutôt select (ou même epoll ). select a une interface différente mais l’idée est la même.

Cette technique est connue sous le nom d’astuce auto-pipe .

Si vous ne voulez pas passer une socket de déblocage à accpet (), alors envoyer un signal (comme décrit par man signal au thread accept() est le seul moyen d’interrompre un blocage accept() (en attente de connexions).

Comme l’ont déjà souligné certains commentateurs, la plupart des appels système (y compris accept() ) seront interrompus lorsque leur thread aura reçu un signal.

Si accept() renvoie -1 et que errno EINTR le EINTR d’ EINTR l’appel à accept() été interrompu par un signal.

Pour envoyer un signal à un processus, utilisez kill(, ) .

Si vous êtes dans un environnement multi-thread, bloquez le signal que vous utiliserez ( SIGUSR1 ou SIGUSR2 typique) pour tous les autres threads, à l’exception du thread qui appelle accept() , car il est indéterminé quel thread d’un processus gère signal envoyé à un processus (“adressé” via le pid du processus)

Alternativement, vous pouvez utiliser pthread_kill(, ) pour envoyer un signal à un thread spécifique uniquement.