Utiliser GNet
Date de publication : 04/09/07 , Date de mise à jour : 03/10/07
IV. Codes exemples
IV-A. main.c
IV-A-1. Vérification des arguments
IV-A-2. Préparation du travail
IV-A-3. Un petit résumé
IV-B. ftpget.h
IV-C. ftpget.c
IV-C-1. La fonction usage
IV-C-2. La fonction ftpget
IV-D. Test
IV-E. Améliorations
IV-E-1. main.c
IV-E-2. ftpget.h
IV-E-3. ftpget.c
IV. Codes exemples
Dans cette partie, je vous propose pour exemple de réaliser un petit programme en ligne de commande (que j'appelerais ftpget) et qui permettera de récupérer un fichier dont l'URL est donnée en argument.
IV-A. main.c
Pour commencer, codons le fichier main.c, on commence avec une structure vide :
int main (int argc, char * * argv)
{
return EXIT_SUCCESS;
}
|
On peut déjà rajouter l'inclusion du fichier d'en tête :
# include "ftpget.h"
int main (int argc, char * * argv)
{
return EXIT_SUCCESS;
}
|
Le fichier d'en tête inclus provoque surement des commentaires donc éclaircissons tout de suite les choses, ce fichier n'éxiste pas encore et nous le créerons plus tard, mais je l'inclus dès le début pour ne pas avoir à revenir sur ce fichier.
Nous allons à présent déclarer les variables que nous utiliseront (pas toujours faciles de tout prévoir...) :
- une variable pour stocker le nom du serveur de type gchar.
- une variable pour stocker le port de type gchar (car c'est sous cette forme que le port est originellement récupéré).
- une variable pour stocker le port en lui-même de type gint (après la conversion en entier).
- une variable pour stocker le nom du fichier sur le serveur de type gchar.
- une variable pour des manipulation diverses (récupération d'argument, etc...) de type gchar.
Du côté du code on en est là... |
# include "ftpget.h"
int main (int argc, char * * argv)
{
gchar* serveur = NULL ;
gchar* portStr = NULL ;
gint port = 80 ;
gchar* filename = NULL ;
gchar* p = NULL ;
return EXIT_SUCCESS;
}
|
|
Attention, prennez l'habitude de toujours initialiser vos variables !
|
IV-A-1. Vérification des arguments
Il nous faut à présent vérifier les arguments donnés à notre programme :
Le nombre d'argument (variable argc) ne doit pas être différent de 2 (il faut prendre en compte le nom de notre programme qui est le numéro 1.
|
Sinon, on rappel à l'utilisateur la façon dont il doit utiliser notre programme, on peut être tenté d'écrire le rappel tel quel à grand coup de printf, mais il est fort possible, que nous soyons amenés à afficher plusieurs fois (et depuis des endroits très différents) ce même rappel, c'est pourquoi il vaudrait mieu le coder dans une fonction à part (que nous verrons lorsque nous écrirons le fichier ftpget.c), en attendant, contentons-nous de ceci :
if (argc ! = 2 )
{
usage ();
exit (EXIT_FAILURE);
}
|
|
Attention, n'oubliez pas de quitter le programme dans de telles circonstances sans quoi cela pourrait entraîner des bogues très fâcheux.
|
IV-A-2. Préparation du travail
Pour correctement répartir les tâches, il nous reste encore à préparer un peu le travail avant d'appeler la fonction ftpget (que nous verrons dans le fichier ftpget.c).
Cela passe par :
- Initialisation de GNet.
- Récupération des arguments.
- Extraction d'un préfixe éventuel.
- Récupération (purifiée) du nom du serveur.
- Récupération du numéro de port.
- Récupération du nom du fichier sur le serveur.
- Appel de la fonction ftpget.
Bon, au travail !
L'initialisation de GNet, ça devrait pas poser trop de problème :
La récupération des arguments non plus (nous allons utiliser notre fameuse variable p) :
L'extraction du préfixe devrait être un peu plus coriace...
L'idée est simple :
Si la comparaison entre "http://" et p limitée à (sizeof ("http://") - 1) caractères est juste (donc s'il y a un suffixe), on tronque purement est simplement la partie suffixe, il ne nous reste plus qu'à reporduire la même chose pour le suffixe "ftp://". Je vous épargne des peines, je vous donne le code :
if (strncmp (" http:// " , p, sizeof (" http:// " ) - 1 ) = = 0 )
{
p = & p[sizeof (" http:// " ) - 1 ];
}
if (strncmp (" ftp:// " , p, sizeof (" ftp:// " ) - 1 ) = = 0 )
{
p = & p[sizeof (" ftp:// " ) - 1 ];
}
|
La récupération purifié du nom d'hôte permet de séparer le nom du serveur du chemin sur le serveur, par exemple :
Dans http://www.developpez.com/test/test.txt:21 :
http:// est le préfixe.
www.developpez.com est le nom du serveur.
/test/test.txt est le chemin sur le serveur
:21 indique le numéro de port
|
Pour récupérer le nom purifié du serveur, on parcourt p et on s'arrête dès que l'on rencontre un "/" ou un ":", je vous donne le code :
serveur = p;
while (* p ! = 0 & & * p ! = ' : ' & & * p ! = ' / ' )
{
+ + p;
}
if ((p - serveur) = = 0 )
{
usage ();
exit (EXIT_FAILURE);
}
serveur = g_strndup (serveur, p - serveur);
|
La récupération du numéro de port est simple : si il y a un ":" c'est que le numéro de port est indiqué, sinon il reste à la valeur par défaut que nous n'avons pas oublié de lui mettre, c.à.d 80.
if (* p = = ' : ' )
{
portStr = + + p;
while (* p ! = 0 & & * p ! = ' / ' ) + + p;
if ((p - portStr) = = 0 ) { usage (); exit (EXIT_FAILURE); }
portStr = g_strndup (portStr, p - portStr);
port = atoi (portStr);
}
|
Pour récupérer le nom du fichier sur le serveur ce n'est pas bien compliqué non plus : s'il n'y a pas de / on récupére la racine, sinon on récupére le nom de fichier qui suit le premier "/" :
if (* p = = 0 )
filename = g_strdup (" / " );
else
filename = g_strdup (p);
|
Et enfin l'appel de la fonction ftpget, bien que nous n'avons pas défini le prototype, vous devez déjà l'imaginer, c'est celui-ci :
ftpget (serveur, port, filename);
|
Et voilà, on a terminé le fichier main.c, on va passer au fichier ftpget.h si vous le voulez bien.
IV-A-3. Un petit résumé
Je ne suis pas persuadé qu'un petit résumé soit superflu...
main.c |
# include "ftpget.h"
int main (int argc, char * * argv)
{
gchar* serveur = NULL ;
gchar* portStr = NULL ;
gint port = 80 ;
gchar* filename = NULL ;
gchar* p = NULL ;
gnet_init ();
if (argc ! = 2 )
{
usage ();
exit (EXIT_FAILURE);
}
p = argv[1 ];
if (strncmp (" http:// " , p, sizeof (" http:// " ) - 1 ) = = 0 )
{
p = & p[sizeof (" http:// " ) - 1 ];
}
serveur = p;
while (* p ! = 0 & & * p ! = ' : ' & & * p ! = ' / ' )
{
+ + p;
}
if ((p - serveur) = = 0 )
{
usage ();
exit (EXIT_FAILURE);
}
serveur = g_strndup (serveur, p - serveur);
if (* p = = ' : ' )
{
portStr = + + p;
while (* p ! = 0 & & * p ! = ' / ' )
{
+ + p;
}
if ((p - portStr) = = 0 )
{
usage ();
exit (EXIT_FAILURE);
}
portStr = g_strndup (portStr, p - portStr);
port = atoi (portStr);
}
if (* p = = 0 )
filename = g_strdup (" / " );
else
filename = g_strdup (p);
ftpget (serveur, port, filename);
g_free (serveur);
g_free (portStr);
g_free (filename);
return EXIT_SUCCESS;
}
|
IV-B. ftpget.h
Il nous faut maintenant coder le fichier ftpget.h qui doit contenir les prototypes des deux fonctions :
Pour ce qui est du prototype de la fonction usage, rien de plus simple :
Par contre réfléchissons un peu pour la fonction ftpget, voici les arguments dont nous aurons besoin :
- Le nom "purifié" du serveur.
- Le numéro de port (tant qu'a faire autant passer celui de type gint).
- Le nom du fichier sur le serveur.
Donc voici le prototype que l'on pourrait faire :
void ftpget (gchar* serveur, gint port, gchar* filename);
|
|
Attention, n'oubliez pas la vérification anti inclusion infinie !
|
Au final voilà le code du fichier ftpget.h :
ftpget.h |
# ifndef FTPGET_H_INCLUDED
# define FTPGET_H_INCLUDED
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <glib.h>
# include <gnet.h>
void usage (void );
void ftpget (gchar* serveur, gint port, gchar* filename);
# endif / / FTPGET_H_INCLUDED
|
IV-C. ftpget.c
C'est dans ce fichier que se trouvera la majeur partie du programme, mais ne vous inquiètez pas, nous avons suffisament décomposé le programme pour ne pas en faire une fonction illisible.
Commençons pas les inclusions du fichier d'en tête :
IV-C-1. La fonction usage
Pour ne pas être découragé tout de suite, commençons par la fonction usage qui ne fait qu'un très simple appel à la fonction printf :
La fonction usage |
void usage (void )
{
printf
(" Utilisation: ftpget url\nExemple : ftpget ftp-developpez.com/shugo/exemple/ftpget/try.txt\n " );
}
|
IV-C-2. La fonction ftpget
C'est LE gros bout du programme, mais on va y allait doucement, il ne devrait pas y avoir de problème.
Commençons par les déclarations de variables.
Il nous faut :
- Une variable de type GInetAddr.
- Une socket de type GTcpSocket.
- Un canal d'E/S de type GIOChannel.
- Une variable pour stocker la commande de type gchar.
- Un buffer de type gchar.
- Une variable pour tester les erreurs de type GIOError.
- Une variable pour contenir le nombre d'octet écrit.
Donc voici le code de déclaration de variables :
GInetAddr* iAddr;
GTcpSocket* socket;
GIOChannel* ioChannel;
gchar* commande;
gchar buffer[1024 ];
GIOError error;
guint n;
|
Ensuite, voici les principales étapes :
- Création de la GInetAddr.
- Création et connexion de la socket.
- Récupération du canal d'E/S.
- Préparation et envoie de la commande.
- Test d'état du canal.
- Lecture du fichier.
- Suppression de la socket.
Au travail !
Pour la création de la GInetAddr, rien de plus facile :
iAddr = gnet_inetaddr_new (serveur, port);
g_assert (iAddr ! = NULL );
|
|
Attention, pensez (comme ici) à vérifier que la création de variables se soient bien fait, par exemple à l'aide d'assertions.
|
La création et la connexion de la socket ne pose pas trop de problème normalement :
socket = gnet_tcp_socket_new (iAddr);
gnet_inetaddr_delete (iAddr);
g_assert (socket ! = NULL );
|
|
Attention, n'oubliez pas de supprimer la GInetAddr qui ne sert plus à rien après la création de la socket TCP.
|
Pour la récupération du canal d'Entrée/Sortie, rien d'infaisable non plus :
ioChannel = gnet_tcp_socket_get_io_channel (socket);
g_assert (ioChannel ! = NULL );
|
Un peu plus dur maintenant ! Il nous faut préparer la commande que nous allons envoyer (pour un résumé des commandes FTP consultez
cette page). Notre commande sera celle-ci :
Une simple concaténation devrait suffit, ensuite on écrit le tout dans la canal d'Entrée/Sortie, sans oublier de libérer la mémoire de la variable commande.
commande = g_strconcat (" GET " , filename, " \n " , NULL );
error = gnet_io_channel_writen (ioChannel, commande, strlen (commande), & n);
g_free (commande);
|
Il nous faut ensuite tester l'état du canal d'E/S :
if (error ! = G_IO_ERROR_NONE)
{
g_warning (" Erreur: %d\n " , error);
}
|
|
Prennez l'habitude de tester l'état des canaux d'E/S après chaque action effectuée avec.
|
Il nous faut maintenant lire la réponse du serveur toujours via le canal d'entrée sortie. C'est pas très compliqué, dans une boucle infinie (while (1)) on lit 1024 caractères du résultat et on les affiche, jusqu'à ce qu'il n'y ai plus rien à lire auquel ca on sort de la boucle while.
while (1 )
{
error = g_io_channel_read (ioChannel, buffer, sizeof (buffer), & n);
if (error ! = G_IO_ERROR_NONE)
{
g_warning (" Erreur: %d\n " , error);
break ;
}
if (n = = 0 )
break ;
fwrite (buffer, n, 1 , stdout);
}
|
Ne nous arrêtons pas en si bon chemin, n'oublions pas de supprimer la socket TCP :
gnet_tcp_socket_delete (socket);
|
Voilà nous avons finit, un petit résumé du fichier ftpget.c ?
# include "ftpget.h"
void usage (void )
{
printf
(" Utilisation: ftpget url\nExemple : ftpget ftp-developpez.com/shugo/exemple/ftpget/try.txt\n "
" Vous pouvez aussi récupérer le tout dans un fichier séparé en spécifiant une sortie (avec l'option "
" -o), exemple :\nftpget ftp-developpez.com/shugo/exemple/ftpget/try.txt -o test.txt\n " );
}
void ftpget (gchar* serveur, gint port, gchar* filename)
{
GInetAddr* iAddr;
GTcpSocket* socket;
GIOChannel* ioChannel;
gchar* commande;
gchar buffer[1024 ];
GIOError error;
guint n;
iAddr = gnet_inetaddr_new (serveur, port);
g_assert (iAddr ! = NULL );
socket = gnet_tcp_socket_new (iAddr);
gnet_inetaddr_delete (iAddr);
g_assert (socket ! = NULL );
ioChannel = gnet_tcp_socket_get_io_channel (socket);
g_assert (ioChannel ! = NULL );
commande = g_strconcat (" GET " , filename, " \n " , NULL );
error = gnet_io_channel_writen (ioChannel, commande, strlen (commande), & n);
g_free (commande);
if (error ! = G_IO_ERROR_NONE)
{
g_warning (" Erreur: %d\n " , error);
}
while (1 )
{
error = g_io_channel_read (ioChannel, buffer, sizeof (buffer), & n);
if (error ! = G_IO_ERROR_NONE)
{
g_warning (" Erreur: %d\n " , error);
break ;
}
if (n = = 0 )
break ;
fwrite (buffer, n, 1 , stdout);
}
gnet_tcp_socket_delete (socket);
}
|
IV-D. Test
Pour compiler le programme créé, tapez cette ligne (pour GCC, adaptez celon votre compilateur) :
gcc -o ftpget `pkg-config --cflags --libs gnet-2.0` ftpget.c `pkg-config --cflags --libs gnet-2.0` main.c
|
A présent vous pouvez récupérer un fichier sur internet, par exemple :
[shugo@myLinux~ :] ./ftpget ftp-developpez.com/shugo/exemple/ftpget/try.txt
Essai de ftpget.
|
Vous remarquerez que le fichier téléchargé s'affiche directement sur la sortie standard, c'est rarement le comportement voulu, c'est pour cela que nous allons revoir notre programme pour permettre à l'utilisateur de rediriger la sortie vers un fichier (je précise que c'est déjà faisable en utilisant > vers un fichier).
IV-E. Améliorations
Notre but dans cette partie va de permettre à l'utilisateur de rediriger la sortie vers un fichier avec l'option -o, et si cela n'est pas fait nous redirigons automatiquement la sortie vers le fichier ftpget-out.txt. Pour cela nous allons devoir revoir quelques points.
IV-E-1. main.c
Il y a tout d'abord quelques points à changer dans le fichier main.c, pour commencer, il nous faut changer la détection d'argument : le nombre d'argument doit être d'au moins 2, inférieur à 4, et s'il y en a 4 le 3 ème doit être -o, sans quoi on appel la fonction usage (), voici ce que donne le code :
if (argc < 2 )
{
usage ();
exit (EXIT_FAILURE);
}
if (argc > 4 )
{
usage ();
exit (EXIT_FAILURE);
}
if (argc = = 2 )
{
outFilename = g_strdup (" ftpget-out.txt " );
}
else if (g_strcasecmp (argv [2 ], " -o " ) = = 0 & & argc = = 4 )
{
outFilename = g_strdup (argv[3 ]);
}
else
{
usage ();
exit (EXIT_SUCCESS);
}
|
On doit également rajouter la variable outFilename qui est de type gchar*, qui est utilisé dans l'assignement.
|
Attention, n'oubliez pas de libérer la mémoire de outFilename !
|
Il nous faut régler un dernier détail avant d'en finir avec main.c, il nous faut rajouter l'argument outFilename dans l'appel à ftpget (on modifieras donc également son prototype). Voici la nouvelle version du fichier main.c :
main.c |
# include "ftpget.h"
int main (int argc, char * * argv)
{
gchar* serveur = NULL ;
gchar* portStr = NULL ;
gint port = 80 ;
gchar* filename = NULL , * outFilename = NULL ;
gchar* p = NULL ;
gnet_init ();
if (argc < 2 )
{
usage ();
exit (EXIT_FAILURE);
}
if (argc > 4 )
{
usage ();
exit (EXIT_FAILURE);
}
if (argc = = 2 )
{
outFilename = g_strdup (" ftpget-out.txt " );
}
else if (g_strcasecmp (argv [2 ], " -o " ) = = 0 & & argc = = 4 )
{
outFilename = g_strdup (argv[3 ]);
}
else
{
usage ();
exit (EXIT_SUCCESS);
}
p = argv[1 ];
if (strncmp (" http:// " , p, sizeof (" http:// " ) - 1 ) = = 0 )
{
p = & p[sizeof (" http:// " ) - 1 ];
}
serveur = p;
while (* p ! = 0 & & * p ! = ' : ' & & * p ! = ' / ' ) + + p;
if ((p - serveur) = = 0 ) { usage (); exit (EXIT_FAILURE); }
serveur = g_strndup (serveur, p - serveur);
if (* p = = ' : ' )
{
portStr = + + p;
while (* p ! = 0 & & * p ! = ' / ' ) + + p;
if ((p - portStr) = = 0 ) { usage (); exit (EXIT_FAILURE); }
portStr = g_strndup (portStr, p - portStr);
port = atoi (portStr);
}
if (* p = = 0 )
filename = g_strdup (" / " );
else
filename = g_strdup (p);
ftpget (serveur, port, filename, outFilename);
g_free (serveur);
g_free (portStr);
g_free (filename);
g_free (outFilename);
return EXIT_SUCCESS;
}
|
IV-E-2. ftpget.h
Ce fichier est une fois de plus le plus simple, il nous suffit de modifier le prototype de ftpget pour y ajouter outFilename, voici le fichier au complet :
ftpget.h |
# ifndef FTPGET_H_INCLUDED
# define FTPGET_H_INCLUDED
void usage (void );
void ftpget (gchar* serveur, gint port, gchar* filename, gchar* outFilename);
# endif / / FTPGET_H_INCLUDED
|
IV-E-3. ftpget.c
Commencons par modifier la fonction usage :
void usage (void )
{
printf
(" Utilisation: ftpget url\nExemple : ftpget ftp-developpez.com/shugo/exemple/ftpget/try.txt\n "
" Vous pouvez aussi récupérer le tout dans un fichier séparé en spécifiant une sortie (avec l'option "
" -o), exemple :\nftpget ftp-developpez.com/shugo/exemple/ftpget/try.txt -o test.txt\n " );
}
|
En ce qui concerne la fonction ftpget, voici la liste de ce qui doit être modifié :
- Ajout d'une variable de type FILE*.
- Ouverture du fichier outFilename.
- Ecriture de la sortie dans le fichier outFilename via la variable de type FILE* au lieu d'écrire dans la sortie standard.
- Fermeture du fichier.
Ajoutons donc notre variable de type FILE* :
On ouvre ensuite le fichier :
outFile = fopen (outFilename, " w " );
g_assert (outFile ! = NULL );
|
Et pour finir, on écrit dans le fichier (via le descripteur de fichier outFile) au lieu d'écrire sur la sortie standard, pour cela, on remplace cette ligne :
fwrite (buffer, n, 1 , stdout);
|
par celle-ci :
fwrite (buffer, n, 1 , outFile);
|
Et on oublie pas la fermeture du fichier :
Pour résumer voici le nouveau fichier ftpget.c :
ftpget.c |
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include <gnet.h>
# include "ftpget.h"
void usage (void )
{
printf
(" Utilisation: ftpget url\nExemple : ftpget ftp-developpez.com/shugo/exemple/ftpget/try.txt\n "
" Vous pouvez aussi récupérer le tout dans un fichier séparé en spécifiant une sortie (avec l'option "
" -o), exemple :\nftpget ftp-developpez.com/shugo/exemple/ftpget/try.txt -o test.txt\n " );
}
void ftpget (gchar* serveur, gint port, gchar* filename, gchar* outFilename)
{
GInetAddr* iAddr;
GTcpSocket* socket;
GIOChannel* ioChannel;
gchar* commande;
gchar buffer[1024 ];
GIOError error;
guint n;
FILE* outFile;
iAddr = gnet_inetaddr_new (serveur, port);
g_assert (iAddr ! = NULL );
socket = gnet_tcp_socket_new (iAddr);
gnet_inetaddr_delete (iAddr);
g_assert (socket ! = NULL );
ioChannel = gnet_tcp_socket_get_io_channel (socket);
g_assert (ioChannel ! = NULL );
commande = g_strconcat (" GET " , filename, " \n " , NULL );
error = gnet_io_channel_writen (ioChannel, commande, strlen (commande), & n);
g_free (commande);
if (error ! = G_IO_ERROR_NONE)
{
g_warning (" Erreur: %d\n " , error);
}
outFile = fopen (outFilename, " w " );
g_assert (outFile ! = NULL );
while (1 )
{
error = g_io_channel_read (ioChannel, buffer, sizeof (buffer), & n);
if (error ! = G_IO_ERROR_NONE)
{
g_warning (" Erreur: %d\n " , error);
break ;
}
if (n = = 0 )
break ;
fwrite (buffer, n, 1 , outFile);
}
fclose (outFile);
gnet_tcp_socket_delete (socket);
}
|
Vous pouvez maintenant rediriger les sorties vers un autre fichier grâce à l'option -o, la procédure de compilation n'a pas changée.