Les files de messages IPC
Définition
Les files de messages (ou queues de messages) permettent l'échanges d'informations dans une architecture client-seveur locale. Ce qui veut dire que l’on n’est pas obligé de connaître le processus pour créer un tube (contrairement au tube FIFO). Le moyen de communication vers le serveur passant par une clef unique d’identification.
Qui dit file de messages dit gestion d'une file d'attente. Le système ira plus loin en permettant de gérer des types de messages. Ainsi un programme pourra se limiter à la lecture d'un certain type de message.
Ce qu'il faut
Les files de messages passent par une structure struct msqid_ds
Les includes suivant seront nécessaire:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
Création et ouverture d’une file de messages
int msgget (key_t key , int mode);
En entrée:
key est une clef unique d'identification, vous passez par une clef unique d’identification, dans ce cas, la file de messages ne sera créée que si IPC_CREAT est positionné dans mode et qu'il n'existe déjà pas une file de message pour cette clef. Ou par IPC_PRIVATE qui garantie la création d'un canal IPC avec une key unique.
mode : Les modes classiques d'accès aux i-nodes: rwxrwxrwx (Ex. 0666 pour octal).
-IPC_CREAT un canal est créé s'il n’existe
pas déjà.
-IPC_EXCL associé au mode IPC_CREAT, il y aura
échec de création si un canal IPC existe déjà.
Il est possible de combiner les valeurs en utilisant le ou logique (le |) Ex.:0666 | IPC_CREAT | IPC_EXCL.
Cette fonction retourne -1 en cas d’erreur, sinon l’identifiant de la file.
En cas d'erreur, errno contient un code d'erreur:
Code | Désignation |
EACCES | Une file de messages existe associée à la clé key, mais le processus appelant n'a pas de permissions pour accéder à cette file et n'a pas la capacité CAP_IPC_OWNER |
EEXIST | Une file de messages existe associée à la clé key et msgflg indique à la fois IPC_CREAT et IPC_EXCL. |
ENOENT | Aucune file de messages n'existe associée à la clé key et msgflg ne contient pas IPC_CREAT. |
ENOMEM | Le système doit créer une file de message mais n'a pas assez de mémoire pour les nouvelles structures de données. |
ENOSPC | Le nombre maximum de files de messages sur le système (MSGMNI) est atteint. |
Consultation, modification ou suppression d’une file de messages
La fonction msgctl autorise la consultation, modification, la fermeture ou la
suppression d’une liste de messages.
Pour la consultation:
int msgctl (int msqid, IPC_STAT, struct msqid_ds *buf);
En entrée:
msqid qui est l'identifiant de la file de message,
IPC_STAT pour lire les informations concernant la file de message
buf pointeur sur une structure de type msqid_ds
En sortie:
La fonction renvoie 0 et buf est alimentée avec les valeurs de la file de messages si tout est ok, sinon une erreur est générée dans errno et la fonction renvoie -1 (voir plus loin les codes possibles).
Pour la suppression:
Dans ce cas, il faut passer la commande IPC_RMID et l'identifiant de la file de messages à supprimer. Le buffer ne servant évidemment pas dans ce cas...
int msgctl (int msqid, IPC_RMID, struct msqid_ds *0);
En entrée:
msqid qui est l'identifiant de la file de message,
IPC_RMID pour supprimer la file de message
buf pointeur sur une structure de type msqid_ds positionnée à 0, car il n'y a rien à passer ou à recevoir sur la structure de la file !
En sortie:
La fonction renvoie 0 et buf est alimentée avec les valeurs de la file de messages si tout est ok, sinon une erreur est générée dans errno et la fonction renvoie -1 (voir plus loin les codes possibles).
Pour la modification:
int msgctl (int msqid, IPC_SET, struct msqid_ds *buf);
En entrée:
msqid qui est l'identifiant de la file de message,
IPC_SET pour modifier la file de message
buf pointeur sur une structure de type msqid_ds
Mettre à jour le champ msg_ctime. Les champs suivants de la structure
peuvent être mis à jour : msg_qbytes, msg_perm.uid, msg_perm.gid
et (les 9 bits poids faible de) msg_perm.mode.
En sortie:
La fonction renvoie 0 et buf est alimentée avec les valeurs de la file de messages si tout est ok, sinon une erreur est générée dans errno et la fonction renvoie -1 (voir plus loin les codes possibles).
L'UID du processus modifiant la file de message doit être celui du propriétaire (msg_perm.uid), le créateur (msg_perm.cuid) de la file de messages ou l'appelant doit être privilégié. Des privilèges appropriés (sous Linux, la capacité CAP_IPC_RESOURCE) sont nécessaires pour augmenter la valeur de msg_qbytes au-dessus de la constante système MSGMNB.
Autes commandes:
Il existe sous
Linux 3 autres commandes qui peuvent être utilisées:
- IPC_INFO.
- MSG_INFO
- MSG_STAT
Les codes erreurs possibles sont :
Code | Désignation |
EACCES | Suite à IPC_STAT (ou MSG_STAT), le processus appelant n'a pas de permissions pour accéder à cette file et n'a pas la capacité CAP_IPC_OWNER |
EFAULT | Suite à IPC_SET ou IPC_STAT, buf pointe hors de la zone d'adressage accessible ! |
EIDRM | La file de messages a déjà été supprimée. |
EINVAL | L'identifiant dans msqid est incorrecte |
EPERM | L'argument cmd réclame l'opération IPC_SET ou IPC_RMID mais
l'UID effectif du processus appelant n'est pas le créateur (comme indiqué dans msg_perm.cuid) ou le propriétaire (comme indiqué dans msg_perm.uid) de la file de messages, et le processus n'est pas privilégié (sous Linux, il n'a pas la capacité CAP_SYS_ADMIN). |
Envoyer un message
Cette fonction va permettre d'envoyer des messages dans la file des messages. Le processus appelant doit allouer une structure comme celle-ci :
int msgsnd (int msqid, struct msgbuf * msgp, size_t msgsz, int msgflg)
struct msgbuf {
long mtype; /* type de message ( > 0 ) */
char mtext[1]; /* contenu du message */
};
mtype doit avoir une valeur supérieure à zéro ! Sinon, cela ne marchera pas !
Cette méthode est très très proche de la solution de gestions des évènements sous X Window (file d'évènements)et ce n'est sûrement pas un hazard !)
En fait, rien ne vous empêchera de créer une structure pour mtext vous permettant ainsi d'envoyer différentes informations structurées (position x, y en int avec le nombre de boutons appuyés en int et pourquoi nom de la machine en char par exemple...). Voir une seconde structure mtext avec des informations complètements différentes de la première structure. Dans ce cas, pour différencier la structure à utiliser, vous n'aurez cas indiquer un type de messages différent dans mtype, une par type de structure mtext. Ainsi, le programme appelant sera relire le message grâce au type de message reçu. Car il lui sera possible d'utiliser la bonne structure ! (cf. union en C pour appliquer cette solution et un exemple de code à la fin !).
En entrée:
msgid pour l'identifiant de la file de message.
msgp qui et de type structure msgbuf et contient le type et le message à envoyer.
size qui est la taille de mtext.
flags pour préciser le comportement de l'appel si le message à envoyer nécessite plus d'octets que ce qui est positionné dans le msg_qbytes de msqid_ds de la file. En indiquant IPC_NOWAIT le message ne sera pas envoyé et l'appel système échouera en retournant EAGAIN dans errno. Sinon, le processus sera suspendu jusqu'à ce que la condition de blocage soit levée (dans ce cas le message sera envoyé et l'appel système réussira), ou que la file soit supprimée (dans ce cas l'appel système échouera et errno contiendra EIDRM), ou que le processus reçoive un signal à intercepter (l'appel système échouera et errno contiendra EINTR).
En sortie:
Si la valeur est nulle, l'appel réussit et la structure de file de messages sera mise à jour ainsi :
msg_lspid contient le PID du processus appelant.
msg_qnum est incrémenté de 1.
msg_stime est rempli avec l'heure actuelle.
Sinon, la valeur est à -1 et errno est positionnée:
Code | Désignation |
EACCES | Le processus appelant n'a pas de permissions pour accéder à cette file et n'a pas la capacité CAP_IPC_OWNER |
EFAULT | msgp pointe hors de la zone d'adressage accessible ! |
EINVAL | msqid ou mtype ou msgsz sont invalides. |
EAGAIN | Le message n'a pas été envoyé car la limite msg_qbyte est dépassée et flag IPC_NOWAIT positionné |
EIDRM | La file de message a été supprimée |
ENOMEM | Le système n'a pas assez de mémoire. |
EINTR | Un signal est arrivé avant d'avoir pu écrire quoi que ce soit. |
Recevoir un message
Lecture de messages. Le message sera evidemment retiré de la file des messages.
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
En entrée:
msqid est toujours l'identifiant de la file de message.
msgp pointeur sur une structure de type struct msgbuf
{
long mtype; /* type de message ( > 0 ) */
char mtext[1]; /* contenu du message */
};
msgsz Taille maximum du message mtext. Si le message est plus long que cette taille et que MSG_NOERROR dans msgflg, le message sera tronqué, sinon il y aura une erreur (E2BIG).
msgtyp correspond au type du message que vous voulez récupérer. Si le type est 0, le premier message de la queue est renvoyé, s'il est positif, le premier message de même type est renvoyé. S'il est négatif, le premier message dont le type est inférieur ou égal à la valeur absolue de msgtyp est retourné.
msgflg Flag ou drapeau en utilisant le ou binaire pour combiner les différentes valeurs (| en C)
Valeur | Désignation |
IPC_NOWAIT | Rend la main au programme si aucun message ne répond aux critères "type de message". L'appel va échouer et errno sera positionné à ENOMSG |
MSG_EXCEPT | Utilisé avec msgtyp supérieur à 0 pour lire les messages
de type différent de msgtyp |
MSG_NOERROR | Tronquer les messages si ceux-ci ont une taille supérieure à msgsz |
Si aucun message
du type requis n'est disponible et drapeau IPC_NOWAIT non positionné
dans msgflg, le processus appelant est bloqué jusqu'à ce qu'un
des événements suivants se produit:
- Arrivée d'un message du type désiré.
- La file de messages est supprimée. L'appel système échoue et errno contient EIDRM.
- Le processus appelant reçoit un signal
à intercepter. L'appel système échoue et errno est renseignée
avec EINTR.
En sortie
En cas d'échec l'appel système renvoient -1 et errno est renseignée.
Si l'appel système réussit, la structure décrivant la file de messages est mise à jour comme suit :
msg_lrpid est rempli avec le PID du processus appelant.
msg_qnum est décrémenté de 1
msg_rtime est rempli avec l'heure actuelle.
msgrcv() renvoie le nombre d'octets copiés dans mtext.
Code | Désignation |
EACCES | Le processus appelant n'a pas de permissions pour accéder à cette file et n'a pas la capacité CAP_IPC_OWNER |
EFAULT | msgp pointe hors de la zone d'adressage accessible ! |
EINVAL | msqid ou msgsz sont invalides. |
EAGAIN | Aucun message n'est disponible dans la file et flag IPC_NOWAIT positionné |
EIDRM | La file de message a été supprimée |
E2BIG | Message plus long en taille que msgsz et flag MSG_NOERROR non positionné. |
ENOMSG | IPC_NOWAIT a été indiqué dans msgflg et aucun message répondant aux critères n'a été trouvé |
EINTR | Un signal est arrivé avant d'avoir pu lire quoi que ce soit. |
Exemple de creation d'une file de messages
Ce premier exemple est très simple, il s'agit d'envoyer 10 textes...
Le code utilise ftok pour déterminer une clef unique d'indentification. Le fichier utilisé étant le nom du fichier exécutable devant résulter de votre compilation (cc msg.c -o msg) nommé msg.
Lancez le code dans une première console. Il va créer une file de messages et y injecter 10 messages. Il va alors attendre la lecture des 10 messages avant de mettre fin à ses jours.
Lancez le code dans une seconde console. Celui-ci va détecter l'existance de la file et se limitera donc à afficher les 10 messages avant de rendre la main. Il s'agit ici d'une solution simple, puisqu'il n'y a qu'un type de message.
Ce second exemple est un peu plus complexe car il y a gestion de plusieurs types de messages.
Le struct msgbuf sera défini de la manière suivante:
typedef union _FILEMSG {
long mtype;
MesAny many;
MesTexts mtexts;
MesEntiers mentiers;
} FMSG ;
FMSG msgbuf
Il s'agit donc de l'union de différentes structures des messages qui pourront être envoyé.
- Un simple texte
- Deux entiers
La structure MesAny étant un peu particulière (et pas forcement utile dans cet exemple restant tout de même simple). Elle a pour but de recevoir toutes les informations communes aux différents types de messages (par exemple type de message, identifiant de l'émetteur du message, ...). La structure existe afin de simplifier le code, il ne sera en effet pas nécessaire de connaître tout de suite la structure à utiliser suivant le type de message pour lire ces informations communes (fortement inspiré de la gestion des évènements sous XWindow).
On notera aussi la sortie de mtype afin de pouvoir lire aussi directement le type de message.
Le code est ensuite assez similaire au premier exemple, à part l'ajout de l'aiguillage pour interpréter les messages reçus ou pour les envoyer...
Il utilisera donc aussi ftok pour déterminer une clef unique d'indentification. Le fichier utilisé étant le nom du fichier exécutable devant résulter de votre compilation (cc msg1.c -o msg1) nommé msg1.