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...) :

Du côté du code on en est là...
#include "ftpget.h"

int main (int argc, char** argv)
{
	/* déclaration des variables */
	gchar* serveur = NULL;
    	gchar* portStr = NULL;
    	gint port = 80;
    	gchar* filename = NULL;
    	gchar* p = NULL;
    
	return EXIT_SUCCESS;
}
warning 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);
}
warning 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 :
Bon, au travail !

L'initialisation de GNet, ça devrait pas poser trop de problème :
gnet_init ();
La récupération des arguments non plus (nous allons utiliser notre fameuse variable p) :
p = argv[1];
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 :
/* extraction du préfixe http:// */
if (strncmp ("http://", p, sizeof ("http://") - 1) == 0)
{
    p = &p[sizeof ("http://") - 1];
}
/* extraction du préfixe ftp:// */
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 :
// on assigne le serveur à p
serveur = p;
// on parcourt p en s'arrêtant dès que l'on rencontre un "/" (début du chemin sur le serveur) 
// ou un ":" (indication du numéro de port)
while (*p != 0 && *p != ':' && *p != '/') 
{
	++p;
}
// s'il n'y a rien à part ça, on appel la fonction usage () 
if ((p - serveur) == 0) 
{
	usage ();
	exit (EXIT_FAILURE);
}

// sinon on assigne le serveur à lui même ôté de p
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.
/* récupération du port */
if (*p == ':')
{
	portStr = ++p;
    	while (*p != 0 && *p != '/') ++p;
    	if ((p - portStr) == 0) { usage(); exit (EXIT_FAILURE); }
        	portStr = g_strndup (portStr, p - portStr);

    	/* on convertit la chaîne correspondant au port en entier */
    	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 "/" :
/* par défaut on récupére la racine du serveur */
if (*p == 0)
	filename = g_strdup ("/");
/* sinon on récupére le nom du fichier */
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
/* Programme d'exemple ftpget :
 * permet la récupération d'un fichier dont l'URL est donnée en argument.
 */

#include "ftpget.h"

int main (int argc, char** argv)
{
	/* déclaration des variables */
    	gchar* serveur = NULL;
    	gchar* portStr = NULL;
    	gint port = 80;
    	gchar* filename = NULL;
    	gchar* p = NULL;

    	/* initialisation de GNet */
    	gnet_init ();

    	/* on vérifie les arguments */
    	if (argc != 2)
    	{
        	usage ();
	        exit (EXIT_FAILURE);
    	}

    	/* on récupére les arguments */
    	p = argv[1];

    	/* extraction du préfixe */
    	if (strncmp ("http://", p, sizeof ("http://") - 1) == 0)
    	{
	        p = &p[sizeof ("http://") - 1];
    	}

    	/* on récupére le nom du serveur */
    	serveur = p;
    	while (*p != 0 && *p != ':' && *p != '/')
	{
		++p;
	}
        if ((p - serveur) == 0) 
	{ 
		usage (); 
		exit (EXIT_FAILURE); 
	}
	serveur = g_strndup (serveur, p - serveur);

	/* récupération du port */
    	if (*p == ':')
    	{
        	portStr = ++p;
        	while (*p != 0 && *p != '/') 
		{
			++p;
		}
        	if ((p - portStr) == 0) 
		{ 
			usage (); 
			exit (EXIT_FAILURE); 
		}
            	portStr = g_strndup (portStr, p - portStr);

        	/* on convertit le port en int */
        	port = atoi (portStr);
    	}

    	/* par défaut on récupére la racine du serveur */
    	if (*p == 0)
        filename = g_strdup ("/");

    	/* sinon on récupére le nom du fichier */
    	else
        	filename = g_strdup (p);

    	/* on récupére le fichier */
    	ftpget (serveur, port, filename);

    	/* libération de la mémoire */
    	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 :
void usage (void);
Par contre réfléchissons un peu pour la fonction ftpget, voici les arguments dont nous aurons besoin :

  1. Le nom "purifié" du serveur.
  2. Le numéro de port (tant qu'a faire autant passer celui de type gint).
  3. Le nom du fichier sur le serveur.
Donc voici le prototype que l'on pourrait faire :
void ftpget (gchar* serveur, gint port, gchar* filename);
warning 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>

/* prototypes des fonctions */
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 :
#include "ftpget.h"

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 :
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 :

Au travail !

Pour la création de la GInetAddr, rien de plus facile :
iAddr = gnet_inetaddr_new (serveur, port);
g_assert (iAddr != NULL);
warning 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);
warning 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 fr cette page). Notre commande sera celle-ci :
GET fichier\n
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);
}
idea 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"

/* on rappel l'utilisation de ftpget */
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)
{
    /* déclaration des variables */
    GInetAddr* iAddr;
    GTcpSocket* socket;
    GIOChannel* ioChannel;
    gchar* commande;
    gchar buffer[1024];
    GIOError error;
    guint n;

    /* Création de la inetAddr */
    iAddr = gnet_inetaddr_new (serveur, port);
    g_assert (iAddr != NULL);

    /* Création de la socket TCP */
    socket = gnet_tcp_socket_new (iAddr);
    gnet_inetaddr_delete (iAddr);
    g_assert (socket != NULL);

    /* Récupération du canal */
    ioChannel = gnet_tcp_socket_get_io_channel (socket);
    g_assert (ioChannel != NULL);

    /* Préparation et envoie de la commande */
    commande = g_strconcat ("GET ", filename, "\n", NULL);
    error = gnet_io_channel_writen (ioChannel, commande, strlen (commande), &n);
    g_free (commande);

    /* S'il y a une erreur */
    if (error != G_IO_ERROR_NONE)
    {
        g_warning ("Erreur: %d\n", error);
    }

    /* Lecture de la sortie */
    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);
    }

    /* suppression de la socket TCP */
    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 :
/* on vérifie les arguments */
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.

warning 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
/* Programme d'exemple ftpget :
 * permet la récupération d'un fichier dont l'URL est donnée en argument.
 */

#include "ftpget.h"

int main(int argc, char** argv)
{
    /* déclaration des variables */
    gchar* serveur = NULL;
    gchar* portStr = NULL;
    gint port = 80;
    gchar* filename = NULL, *outFilename = NULL;
    gchar* p = NULL;

    /* initialisation de GNet */
    gnet_init ();

    /* on vérifie les arguments */
    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 récupére les arguments */
    p = argv[1];

    /* extraction du préfixe */
    if (strncmp ("http://", p, sizeof ("http://") - 1) == 0)
    {
        p = &p[sizeof ("http://") - 1];
    }

    /* on récupére le nom du serveur */
    serveur = p;
    while (*p != 0 && *p != ':' && *p != '/') ++p;
        if ((p - serveur) == 0) { usage (); exit (EXIT_FAILURE); }
            serveur = g_strndup (serveur, p - serveur);

    /* récupération du port */
    if (*p == ':')
    {
        portStr = ++p;
        while (*p != 0 && *p != '/') ++p;
        if ((p - portStr) == 0) { usage (); exit (EXIT_FAILURE); }
            portStr = g_strndup (portStr, p - portStr);

        /* on convertit le port en int */
        port = atoi (portStr);
    }

    /* par défaut on récupére la racine du serveur */
    if (*p == 0)
        filename = g_strdup ("/");
    /* sinon on récupére le nom du fichier */
    else
        filename = g_strdup (p);

    /* on récupére le fichier */
    ftpget (serveur, port, filename, outFilename);

    /* libération de la mémoire */
    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

/* prototypes des fonctions */
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 :
/* on rappel l'utilisation de ftpget */
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é :

Ajoutons donc notre variable de type FILE* :
FILE* outFile;
On ouvre ensuite le fichier :
/* Ouverture du 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 :
/* fermeture du fichier */
fclose (outFile);
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"

/* on rappel l'utilisation de ftpget */
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)
{
    /* déclaration des variables */
    GInetAddr* iAddr;
    GTcpSocket* socket;
    GIOChannel* ioChannel;
    gchar* commande;
    gchar buffer[1024];
    GIOError error;
    guint n;
    FILE* outFile;

    /* Création de la inetAddr */
    iAddr = gnet_inetaddr_new (serveur, port);
    g_assert (iAddr != NULL);

    /* Création de la socket TCP */
    socket = gnet_tcp_socket_new (iAddr);
    gnet_inetaddr_delete (iAddr);
    g_assert (socket != NULL);

    /* Récupération du canal */
    ioChannel = gnet_tcp_socket_get_io_channel (socket);
    g_assert (ioChannel != NULL);

    /* Préparation et envoie de la commande */
    commande = g_strconcat ("GET ", filename, "\n", NULL);
    error = gnet_io_channel_writen (ioChannel, commande, strlen (commande), &n);
    g_free (commande);

    /* S'il y a une erreur */
    if (error != G_IO_ERROR_NONE)
    {
        g_warning ("Erreur: %d\n", error);
    }

    /* Ouverture du fichier */
    outFile = fopen (outFilename, "w");
    g_assert (outFile != NULL);

    /* Lecture de la sortie */
    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);
    }

    /* fermeture du fichier */
    fclose (outFile);

    /* suppression de la socket TCP */
    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.



 

Valid XHTML 1.1!Valid CSS!