Niveau :      
Résumé : cmsg_type = SCM_RIGHTS

Ne vous-êtes vous jamais posé la question de comment fait apache pour lancer des processus à l'avance tout en continuant de recevoir les connexions sur le processus père ? Les cours de programmation sur les sockets ne proposent que deux façons de coder un serveur. Vous écoutez (listen) sur un processus et à chaque nouvelle connexion d'un client (accept) vous obtenez une nouvelle socket. Ensuite, soit vous créez (fork) un nouveau processus qui communiquera uniquement avec ce client sur la socket ainsi créée, soit vous gérez toutes les sockets dans le même processus (merci select).

Bien, je vous propose de continuer à faire gérer les connexions par des processus fils, tout en lançant ceux-ci à l'avance. Pour cela, il suffit de faire en sorte qu'un processus puisse communiquer un file descriptor (ou une socket) à un autre. La solution est relativement simple si on est habitué à la gestion des sockets en C. Elle se trouve dans man 7 PF_UNIX à la section "Ancillary Messages".

Les socket unix permettent de communiquer certaines informations non communicables par le réseau. Dans notre cas, nous allons communiquer des file descriptor. Il nous faut donc une socket unix pour communiquer entre les différents processus. Vous trouverez en attaché le source pour deux programme. Le premier (listener) écoute les connexions sur le port tcp 2000, dès qu'une connexion est ouverte, il communique le fd résultant au second (worker) qui va simplement afficher un petit message. Notez que la communication passe par une socket unix (/tmp/transmit) et que listener doit être lancé en premier.

Voici un aperçu des fonctions intéressantes :

Transmission du file descriptor

void fd_transmit(int unixfd, int fd)
{
 [ ... ]
        /* on transmet le minimum de données (1 octet) 
         * pour que le message soit pris en compte */
        iov.iov_base    = &one_char;
        iov.iov_len     = 1;
        msg.msg_iov     = &iov;
        msg.msg_iovlen  = 1;

        /* on spéficie un header spécial précisant qu'on transmet une socket */
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_len   = CONTROLLEN;
        cmsg->cmsg_type  = SCM_RIGHTS;
        *(int *)CMSG_DATA(cmsg) = fd;

        msg.msg_control = cmsg;
        msg.msg_controllen = CONTROLLEN;

        /* ce type de message doit être envoyé avec sendmsg */
        rval = sendmsg(unixfd, &msg, 0);
[ ... ]
}

Réception du file descriptor

int fd_receive(int unixfd)
{
[ ... ]
        /* on se prépare à recevoir un octet */
        iov.iov_base    = buf;
        iov.iov_len     = sizeof(buf);
        msg.msg_iov     = &iov;
        msg.msg_iovlen  = 1;

        /* on se prépare à recevoir un fd */
        msg.msg_control = cmsg;
        msg.msg_controllen = CONTROLLEN;

        /* c'est par cette fonction qu'on peut recevoir ce genre de message */
        rval = recvmsg(unixfd, &msg, 0);
[ ... ]

        /* le fd est juste derrière le cmsgheader */
        fd = *(int *)CMSG_DATA(cmsg);
        return fd;
}

Résultat

$ gcc -o listener listener.c
$ gcc -o worker worker.c

Puis dans 3 terminaux différents :

# C'est lui qui écoute sur le port 2000
$ ./listener

# C'est lui qui imprimera le message
$ ./worker

# Un client quelconque
$ nc localhost 2000
> Bonjour monde

PS : Si vous avez bien lu le manuel, vous noterez qu'il est aussi possible de vérifier un utilisateur à travers une socket unix en utilisant SCM_CREDENTIALS à la place de SCM_RIGHTS.

PPS : Au fait si vous voulez troller, dites-moi s'il y a trop ou pas assez de commentaires dans mon code ...