Programme gelé à cause de la fonction connect

Le problème exposé dans ce sujet a été résolu.

Edit :

Compilateur et terminal en ligne contenant mon code (cliquez sur Compile et Execute dans la barre d'outils en haut à gauche) :

  1. Avec l'instruction connect commentée : http://goo.gl/Om19VE

  2. Avec l'instruction connect non-commentée : http://goo.gl/Ji6Zlb

==================

Bonjour à tous,

Je veux m'initier à la programmation réseau en C (je précise : pas en C++), et pour ce faire j'ai décidé de récupérer le code HTML du site www.google.fr.

Pour l'instant j'ai écrit un programme qui est censé établir une connexion au serveur de Google qui héberge www.google.fr. Petit problème : l'instruction connect semble geler mon programme. Quand je retire connect(), le programme s'exécute bien puis se termine.

Quand connect est commentée :

  1. Le printf-IP s'exécute correctement ;

  2. Seule l'erreur shutdown s'affiche : impression de "Erreur shutdown 1" dans la console.

  3. Le programme se termine correctement, et tout seul.

Quand connect n'est pas commentée, plus rien ne s'affiche et le programme est gelé (il semble tourner en boucle) :

  1. Le printf-IP n'est plus exécuté ;

  2. Aucun printf d'erreur (par exemple le printf-shutdown) n'est exécuté.

  3. Le programme ne se termine pas : il semble tourner en boucle, il est gelé.

Par ailleurs, il semblerait que l'IP que j'obtiens ne soit pas la bonne :-/ Cf. le commentaire précédant le printf correspondant.

Qu'est-ce qui ne va pas avec connect ? Voici le code, je l'ai commenté. Merci d'avance !

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <stdio.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <stdlib.h>

int main (int argc, char** argv) {

    /*  DOCUMENTATION
             * struct hostent* gethostbyname(const char* name)
             * struct hostent {
             *      char *h_name; // Nom officiel de l'host
             *      char** h_aliases; // Autres noms de l'host
             *      int h_addrtype; // AF_INET (pour www.google.fr) ou bien AF_INET6 ; type d'adresse de l'host
             *      int h_length; // Longueur en bytes de chaque adresse
             *      char** h_addr_list; // Vecteur des adresses de l'host
             *      char* h_addr; // h_addr_list[0] (première adresse de l'host : celle du serveur)
             * };
             *
             *
             * struct in_addr {
             *      unsigned long s_addr; // Y stocker l'IP de l'host, qui est : *p_hostent->h_addr_list[0])
             * };
             *
             *
             * struct sockaddr_in {
             *      short sin_family; // Should be AF_INET (famille spécialisée Internet)
             *      u_short sin_port; // Le port sur lequel l'app est connectée
             *      struct in_addr sin_addr; // Structure contenant l'IP de l'host
             *      char sin_zero; // Not used, must be 0
             * };
             *
             *
             * const char* inet_ntop(int adress_family, const void* source, char* destination, socklen_t size);
             *
             *
             * int socket(int famille, int type, int protocol)
             *
             * // sockaddr_in peut être transtypé en sockaddr (car sockaddr est la généralisation de sockaddr_in)
             * int connect(int socket, const struct sockaddr* address, socklen_t address_len)
             *
             *
    */

    // On récupère les infos (notamment les adresses IP) de l'host www.google.fr
    struct hostent* hostinfo = gethostbyname("www.google.fr");
    if (hostinfo == NULL) {
        printf("Erreur gethostbyname : %d", h_errno);
        exit(EXIT_FAILURE);
    }

    // On stocke la première IP de l'host dans cette structure, structure qui sera utilisée dans sockaddr_in
    struct in_addr i_a = {
            *((unsigned long*) hostinfo->h_addr_list[0])
    };

    // On imprime l'IP, pour s'assurer qu'elle correspond effectivement à celle de www.google.fr (vérifier avec un outil-tier : site Web, etc.)
    // Résultat : le printf donne 74.125.195.94 et www.mon-ip.com/adresse-ip-site-internet.php donne 216.58.208.227
    char string [INET6_ADDRSTRLEN];
    const char* i_t = inet_ntop(AF_INET, &i_a.s_addr, string, INET_ADDRSTRLEN);
    if (i_t == NULL) {
        printf("Erreur inet_ntop %d", h_errno);
        exit(EXIT_FAILURE);
    }
    printf("\n IP : %s", string);

    // Sera utilisé dans l'appel à la fonction connect
    struct sockaddr_in s_i = {
                    AF_INET,
                    3840,
                    i_a,
                    0
    };

    // Création du socket, établissement de la connexion, et fermeture du socket
    int s = socket(AF_INET, SOCK_STREAM, 0);
    if (s == -1) {
        printf("Erreur socket %d", h_errno);
        exit(EXIT_FAILURE);
    }

   /* int c = connect(s, (struct sockaddr*) &s_i, sizeof(struct sockaddr));
    if (c == -1) {
        printf("Erreur connect %d", h_errno);
        exit(EXIT_FAILURE);
    }*/

    int sh = shutdown(s, 2);
    if (sh == -1) {
        printf("Erreur shutdown %d", h_errno);
        exit(EXIT_FAILURE);
    }

    return 0;
}
+0 -0

Ksass, ok, je viens de mettre à jour le code !

Quand connect est commentée :

  1. Le printf-IP s'exécute correctement ;

  2. Seule l'erreur shutdown s'affiche : impression de "Erreur shutdown 1" dans la console.

  3. Le programme se termine correctement, et tout seul.

Quand connect n'est pas commentée, plus rien ne s'affiche et le programme est gelé (il semble tourner en boucle) :

  1. Le printf-IP n'est plus exécuté ;

  2. Aucun printf d'erreur (par exemple le printf-shutdown) n'est exécuté.

  3. Le programme ne se termine pas : il semble tourner en boucle, il est gelé.

Edit : par ailleurs, il semblerait que l'IP que j'obtiens ne soit pas la bonne :-/ Cf. le commentaire précédant le printf correspondant.

+0 -0

Salut,

Plusieurs remarques sur ton code :

1
exit;

Cette instruction ne réalise aucune action, elle revient à écrire (void)&exit;. Si tu veux quitter ton programme, tu dois appeler la fonction exit() en lui spécifiant une valeur de retour comme argument (par exemple EXIT_FAILURE).

1
2
3
struct in_addr i_a = {
        *((unsigned long*) hostinfo->h_addr_list[0])
};

Je te déconseil d'essayer de chipoter avec la représentation interne des structures systèmes comme sockaddr_in et, à plus forte raison, addr_in. Préfère utiliser les champs qui sont fournis et spécifiés par les différentes normes (notamment POSIX).

Sinon, la fonction gethostbyname() est déclarée obsolète, il est préférable d'employer la fonction getaddrinfo() à la place. Essaye plutôt ce code pour récupérer l'adresse d'un des serveurs derrière le domaine www.google.fr.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
char buf[64];
static struct addrinfo hints;
struct addrinfo *info;
sockaddr_in *sin;
int n;

hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;

if ((n = getaddrinfo("www.google.fr", "http", &hints, &info)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(n));
        return EXIT_FAILURE;
}

sin = (struct sockaddr_in *)info->ai_addr;

if (inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof buf) == NULL) {
        perror("inet_ntop");
        freeaddrinfo(info);
        return EXIT_FAILURE;
}

printf("IP: %s\n", buf);
freeaddrinfo(info);

Edit : correction mineure.

+0 -0

Salut,

Plusieurs remarques sur ton code :

1
exit;

Cette instruction ne réalise aucune action, elle revient à écrire (void)&exit;. Si tu veux quitter ton programme, tu dois appeler la fonction exit() en lui spécifiant une valeur de retour comme argument (par exemple EXIT_FAILURE).

1
2
3
struct in_addr i_a = {
        *((unsigned long*) hostinfo->h_addr_list[0])
};

Je te déconseil d'essayer de chipoter avec la représentation interne des structures systèmes comme sockaddr_in et, à plus forte raison, addr_in. Préfère utiliser les champs qui sont fournis et spécifiés par les différentes normes (notamment POSIX).

Sinon, la fonction gethostbyname() est déclarée obsolète, il est préférable d'employer la fonction getaddrinfo() à la place. Essaye plutôt ce code pour récupérer l'adresse d'un des serveurs derrière le domaine www.google.fr.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
char buf[64];
static struct addrinfo hints;
struct addrinfo *info;
sockaddr_in *sin;
int n;

hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;

if ((n = getaddrinfo("www.google.fr", "http", &hints, &info)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(n));
        return EXIT_FAILURE;
}

sin = (struct sockaddr_in *)info->ai_addr;

if (inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof buf) == NULL) {
        perror("inet_ntop");
        freeaddrinfo(info);
        return EXIT_FAILURE;
}

printf("IP: %s\n", buf);
freeaddrinfo(info);

Edit : correction mineure.

Taurre

Ok, j'ai modifié les instructions exit.

Alors par contre, je n'ai pas du tout compris cette phrase : "Je te déconseil d'essayer de chipoter avec la représentation interne des structures systèmes comme sockaddr_in et, à plus forte raison, addr_in. Préfère utiliser les champs qui sont fournis et spécifiés par les différentes normes (notamment POSIX).".

La structure in_addr possède bel et bien un champ unsigned long s_addr si c'est ce que tu me reproches. Source : http://www.gta.ufrj.br/ensino/eel878/sockets/sockaddr_inman.html.

Après j'ai peut-être mal compris ce que tu voulais dire.

Concernant le code que tu me donnes, il est très différent du mien, et j'aimerais vraiment savoir ce qui ne va pas dans ma version avant de me pencher sur la tienne. Une fois que j'aurais compris mon erreur, je regarderai comment me passer de gethostbyname puisqu'en effet c'est une fonction obsolète (et bien entendu, je m'inspirerai de ton code :) !).

+0 -0

Pour ce qui est de l'affichage de printf(), ajoute simplement un retour à la ligne à la fin de ta chaîne, cela provoquera la vidange du tampon du flux stdout.

Alors par contre, je n'ai pas du tout compris cette phrase : "Je te déconseil d'essayer de chipoter avec la représentation interne des structures systèmes comme sockaddr_in et, à plus forte raison, addr_in. Préfère utiliser les champs qui sont fournis et spécifiés par les différentes normes (notamment POSIX).".

La structure in_addr possède bel et bien un champ unsigned long s_addr si c'est ce que tu me reproches. Source : http://www.gta.ufrj.br/ensino/eel878/sockets/sockaddr_inman.html.

Lern-X

Oui, la représentation de la structure peut être connue en regardant dans le code source, mais elle est variable d'un système à un autre (par exemple entre Debian et OpenBSD). Aussi, il est préférable de ne faire aucune supposition sur les types.

Sinon, le soucis viens certainement de ton initialisation de la structure i_a (le type unsigned long fait-il bien 32 bits sur ta machine ?). Essaye plutôt de recourir au champ h_addr_list de la structure hostent et à la fonction memcpy().

1
2
3
4
5
6
7
struct sockaddr_in sin;
memcpy(&sin.sin_addr, hostinfo->h_addr_info[0], 4);

/* ... */

char string [INET6_ADDRSTRLEN];
const char* i_t = inet_ntop(AF_INET, hostinfo->h_addr_list[0], string, sizeof string);
+0 -0

Pour ce qui est de l'affichage de printf(), ajoute simplement un retour à la ligne à la fin de ta chaîne, cela provoquera la vidange du tampon du flux stdout.

Taurre

J'ai rajouté le \n et en effet, l'IP s'affiche bien. Est-ce que tu as le temps de m'expliquer pourquoi, s'il te plaît ?

Sinon, le soucis viens certainement de ton initialisation de la structure i_a (le type unsigned long fait-il bien 32 bits sur ta machine ?).

Taurre

J'ai fait un printf("\n%zu\n", sizeof(unsigned long)); et ça affiche 8. Ainsi, sur mon système (Linux Kubuntu 64-bit), une variable de type unsigned long est allouée sur 8 octets, soit 84 = 32 bits si je ne m'abuse. Donc pas de souci de ce côté-là, sauf erreur de ma part !

  • : oups, un octet faisant 8b, on a en fait 8*8=64!

Je vais essayer avec memcpy, je te tiens au courant :) !

Edit :

J'ai utilisé memcpy et ça ne change rien :-/ :

1
2
struct in_addr i_a;
memcpy(&i_a, hostinfo->h_addr_list[0], 4);

Si je ne m'abuse, ça revient bien au même que de faire ce que toi tu as écrit (édit 2 : je confirme, cf plus bas) :

1
2
struct sockaddr_in sin;
memcpy(&sin.sin_addr, hostinfo->h_addr_info[0], 4);

Edit 2 :

J'ai aussi apporté la même modification que toi, exactement la même, et le résultat est le même : ça ne marche pas :-/

+0 -0

J'ai rajouté le \n et en effet, l'IP s'affiche bien. Est-ce que tu as le temps de m'expliquer pourquoi, s'il te plaît ?

Lern-X

Grosso modo, chaque flux (structure de type FILE) comporte un tableau de char (appelé un tampon) qui sert à accumuler une certaine quantité de données avant lecture ou écriture. L'objectif de cette technique est d'éviter la lourdeur des opérations de lectures/écritures en récupérant les données par morceaux et non par caractères (ce qui est assez coûteux en performances).

Dans le cas du flux stdout (celui sur lequel écrit la fonction printf()), le tampon n'est vidé (autrement dit, dans ce cas ci, les données ne sont écrites) que s'il est plein (c'est logique) ou si un caractère de fin de ligne est rencontré. On parle dans ce cas d'un flux temporisé (ou mémorisé) par lignes.

Si tu veux creuser le sujet, je t'invite à lire le chapitre relatif aux fichiers dans la partie 2 de ce cours sur le C qui est en bêta.

J'ai fait un printf("\n%zu\n", sizeof(unsigned long)); et ça affiche 8. Ainsi, sur mon système (Linux Kubuntu 64-bit), une variable de type unsigned long est allouée sur 8 octets, soit 8*4 = 32 bits si je ne m'abuse.

L'opérateur sizeof retourne la taille du type de son opérande en byte. Comme un byte vaut le plus souvent (pour ne pas dire toujours) un octet, le type unsigned long fait chez toi huit octets soit 64 bits. Quoiqu'il en soit, même si cette manipulation est incorrecte, ce n'est pas elle qui pose problème… Après relecture, je me rends compte que je suis complètement passé à côté de deux erreurs.

1
int c = connect(s, (struct sockaddr*) &s_i, sizeof(struct sockaddr));

Le troisième argument doit être la taille de la structure passée en deuxième argument soit la taille d'une structure sockaddr_in.

1
2
3
4
5
6
struct sockaddr_in s_i = {
    AF_INET,
    3840,
    i_a,
    0
};

Si tu veux contacter les serveurs HTTP de Google, tu dois le faire via le port 80 et non le port 3840. D'ailleurs, n'oublie pas de faire appel à la fonction htons() pour modifier au besoin le boutisme.

1
sin.sin_port = htons(80);
+0 -0

J'ai rajouté le \n et en effet, l'IP s'affiche bien. Est-ce que tu as le temps de m'expliquer pourquoi, s'il te plaît ?

Lern-X

Grosso modo, chaque flux (structure de type FILE) comporte un tableau de char (appelé un tampon) qui sert à accumuler une certaine quantité de données avant lecture ou écriture. L'objectif de cette technique est d'éviter la lourdeur des opérations de lectures/écritures en récupérant les données par morceaux et non par caractères (ce qui est assez coûteux en performances).

Dans le cas du flux stdout (celui sur lequel écrit la fonction printf()), le tampon n'est vidé (autrement dit, dans ce cas ci, les données sont écrites) que s'il est plein (c'est logique) ou si un caractère de fin de ligne est rencontré. On parle dans ce cas d'un flux temporisé (ou mémorisé) par lignes dans ce cas.

Si tu veux creuser le sujet, je t'invite à lire le chapitre relatif aux fichiers dans la partie 2 de ce cours sur le C qui est en bêta.

J'ai fait un printf("\n%zu\n", sizeof(unsigned long)); et ça affiche 8. Ainsi, sur mon système (Linux Kubuntu 64-bit), une variable de type unsigned long est allouée sur 8 octets, soit 8*4 = 32 bits si je ne m'abuse.

L'opérateur sizeof retourne la taille du type de son opérande en byte. Comme un byte vaut le plus souvent (pour ne pas dire toujours) un octet, le type unsigned long fait chez toi huit octets soit 64 bits. Quoiqu'il en soit, même si cette manipulation est incorrecte, ce n'est pas elle qui pose problème… Après relecture, je me rends compte que je suis complètement passé à côté de deux erreurs.

1
int c = connect(s, (struct sockaddr*) &s_i, sizeof(struct sockaddr));

Le troisième argument doit être la taille de la structure passée en deuxième argument soit la taille d'une structure sockaddr_in.

1
2
3
4
5
6
struct sockaddr_in s_i = {
    AF_INET,
    3840,
    i_a,
    0
};

Si tu veux contacter les serveurs HTTP de Google, tu dois le faire via le port 80 et non le port 3840. D'ailleurs, n'oublie pas de faire appel à la fonction htons() pour modifier au besoin le boutisme.

1
sin.sin_port = htons(80);

Taurre

Le htons(80) a en effet résolu le bug, merci pour ton aide et tes explications.

Je t'invite quand même fortement à oublier gethostbyname et passer à getaddrinfo. C'est beaucoup plus court et concis, c'est conçu de façon à t'éviter des erreurs à la con genre oubli de htons pour changer le boutisme de natif à NBO, et ça permet d'être compatible IPv4 et IPv6 indifféremment. En tout cas dans la doc windows de gethostbyname, c'est clairement précisé qu'il ne supporte que IPv4.

Quand taurre te disait d'éviter de chipoter, c'est aussi à cause de ça: en utilisant une structure intermédiaire pour stocker la'dresse dans un unsigned long, tu te coupes d'office d'IPv6. Mieux vaut ne rien supposer sur la taille de l'adresse, on ne sait jamais ! Dans 10 ans ça aura peut-être son importance.

Si çA peut t'être utile, voici un code C++ (qui fait quand même très C) utilisant getaddrinfo qui marche sous windows. Il n'y a probablement pas grand chose à changer pour que ça fonctionne aussi sous UNIX, getaddrinfo est normalement standard (pour une fois que Windows et UNIX s'entendent à peu près).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int Socket::open (const char* host, int port) {
//https://msdn.microsoft.com/en-us/library/windows/desktop/ms738520(v=vs.85).aspx
addrinfo *addrs=NULL, hint = { 0, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL };
int result=0;
char strPort[7]={0};
snprintf(strPort, 6, "%d", port);
if ((result=getaddrinfo(host, strPort, &hint, &addrs))) return result;
if (addrs) for (addrinfo *p = addrs; p; p=p->ai_next) {
if (
(sock = socket(p->ai_family, p->ai_socktype, 0))
&& SOCKET_ERROR!=connect(sock, (SOCKADDR*)(p->ai_addr), p->ai_addrlen))
break;
result = WSAGetLastError();
if (socket) closesocket(sock);
}
if (addrs) freeaddrinfo(addrs);
return result;
}
+0 -0

Ouais parce que bon en C++, il y a boost::asio quand même

Tiens, tiens… J'utilise déjà plein de libs de boost, je suis sûr d'avoir déjà vu le nom asio dans l'archive, mais je n'ai jamais regardé ce que c'était.

Le problème avec boost c'est que c'est tellement complet qu'on ignore le 95% de ce qu'il y a dedans. ET ce nom n'est pas très explicite, ce qui n'invite pas à aller regarder juste pour voir. Du coup quand on pourrait en avoir réellement besoin on passe à côté. Au moins avec algorithm, string, regex, datetime, filesystem, circular_buffer, etc. on a immédiatement une idée au moins vague de ce que ça permet de faire.

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