Communiquer avec un programme en arrière-plan

Via la console en ligne de commande

a marqué ce sujet comme résolu.

Bonjour à tous !

Je voudrais réaliser un petit programme de type serveur en C++. Mon objectif est de pouvoir démarrer ce programme sur une machine GNU/Linux. Le programme s’exécuterait indéfiniment, même si l’utilisateur qui l’a lancé a ensuite fermé sa console ou sa session. Le programme pourrait recevoir des commandes via la console pendant son fonctionnement (par exemple si l’administrateur veut l’arrêter). À la suite de certaines commandes, le programme pourrait afficher en retour des informations dans la console.

Par exemple, OpenSSH sous Ubuntu peut-être démarré avec la commande sudo /etc/init.d/ssh start. En cours de fonctionnement, l’utilisateur peut afficher son statut avec la commande sudo /etc/init.d/ssh status et peut l’arrêter avec sudo /etc/init.d/ssh stop. Je voudrais obtenir quelque chose de similaire.

Mes questions sont les suivantes :

  • Comment faire pour que le programme ne s’arrête pas lorsque l’utilisateur ferme sa session ? Le programme doit être lancé en arrière-plan (ou en tant que tâche de fond) ? Je sais qu’il est possible de faire cela avec nohup. Comment rendre cela transparent aux yeux de l’admin (comme avec OpenSSH) ?
  • Une fois que le programme lancé, comment réceptionner des commandes (et leurs paramètres) depuis la console ? Je sais déjà comment récupérer les arguments de la commande au lancement du programme (int argc, const char * argv[] du main en C++) mais je ne sais pas comment faire cela pendant le fonctionnement.

Je n’ai pas de connaissances en programmation système ni sur le fonctionnement interne de Linux. Par exemple, je ne sais pas ce que sont les threads. Je ne sais pas comment Linux gère les "processus" au sein de la machine et ce qui s’y passe lorsqu’un binaire est exécuté. Je n’ai pas de définition précise du foreground et du background. Si vous avez un cours ou un tutoriel sur le sujet, je suis preneur !

Mon objectif est-il réalisable ? Quelles sont les solutions techniques mises en places par OpenSSH (et autres programmes "professionnels") ?

Merci d’avance pour vos réponses, Cordialement,

Croal

Vu ce que tu décris et pour faire propre, pour moi il faut que ton programme soit lancé en tant que démon sur le système, et donc installé en tant que tel par l’administrateur du système.

Sur un Linux récent, ça se fait généralement au moyen d’un script systemd (qui remplace les /etc/init.d/** que tu cites).

Hello,

Pour la communication en cours de fonctionnement, le terme que tu peux chercher est IPC = inter-process communication.

Les façons classiques de faire de l’IPC sont les sockets, les pipes et les signaux, et sinon chaque OS propose aussi d’autres possibilités spécifiques (par exemple, sockets UNIX pour linux, ou DDE ou communication par message queue sous windows) Je te laisse te renseigner sur ces derniers si ça t’intéresse. Ne les ayant que peu utilisé personnellement, je ne pourrai pas plus t’aider.

Si ce que tu cherches à faire est juste un start/stop, les signaux sont suffisants. Tu peux commencer par chercher comment intercepter Ctrl+C a.k.a. SIGINT, ça se fait couramment.

Si tu as besoin de faire quelque chose de plus évolué, et en particulier si tu veux que ton serveur puisse aussi retourner des informations, je te conseille les sockets TCP. Pour ce faire, tu peux utiliser l’API BSD C classique disponible de base sur tous les linux (aussi disponible sous windows avec quelques changements mineurs), mais ce n’est pas le plus facile à prendre en main. Je pourrais te recommander boost asio comme bibliothèque pour s’occuper des sockets, mais c’est un peu un monstre (surtout si tu n’utilises pas déjà boost). Il doit y en exister des beaucoup plus simples pour débuter. A noter que si tu utilises un framework graphique (QT, wxWidgets, etc.), ces frameworks incluent souvent de quoi faire de la programmation réseau basique.

+1 -0

Tu peux faire la façon propre que propose SpaceFox. Avec systemd c’est assez simple, même si ça peut paraître intimidant au premier abord.

Sinon, solution très simple, tu peux lancer ton programme dans une instance de screen ou de tmux. Pour un petit projet perso c’est pas grave de faire comme ça, mais il ne s’agit pas d’une solution de mise en production. (même si on sait tous que ça se fait en entreprise X/ )

+1 -0

Merci encore pour vos réponses !

Du coup j’ai cherché dans les directions que vous m’avez conseillées. Effectivement, je pense que SpaceFox a raison : je vais devoir utiliser un daemon pour la mise en production de mon serveur. Je réserve screen pour les essais.

Si j’ai bien compris, le daemon est un processus qui s’exécute en arrière-plan de la machine avec la particularité de n’être rattaché à aucun utilisateur et aucune console. Ce qui me permet de lancer mon serveur, puis de le laisser fonctionner tout seul.

Pour créer le daemon 3 possibilités s’offrent à moi :

  • intégrer une fonction daemonize() à mon code source, qui sera appelée au début du main. Exemple ici et ici. Grosso modo, cette fonction fork le processus exécuté au démarrage du serveur, puis détruit le processus parent. Ainsi, le processus enfant (qui vient d’être créé) se retrouve "orphelin", donc il est rattaché à init (PID 1) et passe en arrière-plan. Avec ça, il suffit seulement à l’administrateur d’exécuter le programme et celui-ci se place automatique en tâche de fond.
  • créer un script bash pour le lancement du serveur, et le placer dans le répertoire /etc/init.d/. La procédure est expliquée ici et ici, ou encore de manière plus ou moins claire… L’avantage de faire cela est que le daemon est lancé automatique au démarrage de la machine et à la commande sudo service NOM_DAEMON start.
  • utiliser la commande Linux daemon : ici, ici et ici. Mais pour le coup je n’ai pas compris comment ça fonctionne ! Voici pourtant un exemple d’utilisation sur StackExchange.

Qu’est-ce que vous pensez qui soit le mieux ? Je partirais bien sur la deuxième possibilité (script bash) mais je ne comprends ce que fait exactement le script dans les exemples que j’ai pu lire (or j’ai besoin de comprendre pour être convaincu par la solution). En conséquence, pour le moment, je me tourne plutôt vers la première possibilité (la fonction).

Enfin, pour ce qui est de communiquer avec le programme en cours de fonctionnement via la ligne de commande, QuentinC me propose d’utiliser quelque chose appelé inter-process communication (IPC). Le mieux me semble-t-il est de réaliser un deuxième programme, normal, qui servirait d’interface avec notre daemon. Apparemment pour cela il existe les pipes (d’après ce site et celui-ci). Mais là encore mes connaissances sur Linux me font défaut…

Que pensez-vous de tout cela ?

Je tenterais la possibilité n°4 (parce que je l’ai déjà fait et que c’est super simple) : ton programme est un programme normal, mais il est lancé en tant que service par systemd.

En fait, c’est la variante « moderne » (utilisée dans la plupart des distributions Linux actuelles, pour être exact) de ton point 2, et c’est sensiblement plus simple à faire.

Enfin, pour ce qui est de communiquer avec le programme en cours de fonctionnement via la ligne de commande, QuentinC me propose d’utiliser quelque chose appelé inter-process communication (IPC). Le mieux me semble-t-il est de réaliser un deuxième programme, normal, qui servirait d’interface avec notre daemon. Apparemment pour cela il existe les pipes (d’après ce site et celui-ci). Mais là encore mes connaissances sur Linux me font défaut…

Si tu n’es pas sûr avec les pipes, et si les signaux sont insuffisants, utilise plutôt des sockets TCP. C’est plus simple et ça a aussi l’avantage d’être utilisable sur à peu près tous les systèmes sans grande modification (dont windows en particulier). C’est toujours intéressant de garder cette possibilité, si un jour tu décidais de faire un portage.

Petite note: il faut être lancé en root pour utiliser les ports 0 à 1023. Je n’ai jamais vraiment compris pourquoi cette limitation par ailleurs. Je te conseille un port > 10000 pour limiter les chances de collision.

+0 -0

Pourquoi forcément TCP ?

Sous Linux, utiliser un socket de type Unix a quand même des avantages :

  • L’interface est strictement la même que les sockets TCP ou UDP. La seule chose qui change c’est une constante que tu passes à la création du socket (AF_UNIX au lieu de AF_INET).
  • Tu ne mobilises pas un port de la machine (et tu ne l’exposes pas non plus à l’extérieur) et as encore moins besoin de réfléchir aux interfaces réseau, ou aux règles de firewall, ou aux autres softs qui mobilisent des ports sur la machine : vu de l’extérieur du programme, ton socket est un fichier dans l’arborescence du système et il obéit donc aux mêmes règles pour en protéger finement l’accès (chmod, etc.).
  • C’est ce que font par défaut la plupart les daemons connus sous Linux. Le premier qui me vient à l’esprit est Docker, et le fait qu’il passe par un socket Unix est d’ailleurs ce qui permet de faire de la magie noire (créer un conteneur depuis un conteneur… simplement en montant le socket de l’hôte dans un volume).

Si ton soft est uniquement voué à tourner sous Linux ou une variante de BSD (MacOS), c’est le choix que je ferais naturellement. En fait j’irais même plus loin : si tu développes sous un de ces OS, c’est ce que je ferais par défaut, quitte à rajouter un petit peu de code pour supporter à la fois les sockets unix et TCP le jour où tu voudras rendre ton démon accessible depuis une machine différente (le client et le serveur ne tournent pas sur la même machine), ou faire tourner ton démon sous Windows, ou dans un conteneur.

+2 -0
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