web-dev-qa-db-fra.com

Pourquoi convertissons-nous sockaddr_in en sockaddr lors de l'appel à bind ()?

La fonction bind () accepte un pointeur vers un sockaddr, mais dans tous les exemples que j'ai vus, un sockaddr_in la structure est utilisée à la place et est convertie en sockaddr:

struct sockaddr_in name;
...
if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
...

Je ne peux pas comprendre pourquoi est-ce qu'un sockaddr_in struct utilisé. Pourquoi ne pas simplement préparer et passer un sockaddr?

Est-ce juste une convention?

38
sashoalm

Non, ce n'est pas seulement une convention.

sockaddr est un descripteur générique pour tout type d'opération de socket, tandis que sockaddr_in est une structure spécifique à la communication IP (IIRC, "in" signifie "Internet"). Pour autant que je sache, il s'agit d'une sorte de "polymorphisme": la fonction bind() prétend prendre un struct sockaddr *, Mais en fait, elle supposera que le type de structure approprié est passé dans; je. e. celui qui correspond au type de socket que vous lui donnez comme premier argument.

51
user529758

Cela est dû au fait que la liaison peut lier d'autres types de sockets que les sockets IP, par exemple les sockets de domaine Unix, qui ont sockaddr_un comme type. L'adresse d'un socket AF_INET a pour hôte et port comme adresse, tandis qu'un socket AF_UNIX a un chemin de système de fichiers.

5
Magnus Reftel

Je ne sais pas si c'est très pertinent pour cette question, mais je voudrais fournir quelques informations supplémentaires qui peuvent rendre le typecaste plus compréhensible car beaucoup de gens qui n'ont pas passé beaucoup de temps avec C sont confus en voyant un tel typecaste.

J'utilise macOS, donc je prends des exemples basés sur des fichiers d'en-tête de mon système.

struct sockaddr Est défini comme suit:

struct sockaddr {
    __uint8_t       sa_len;         /* total length */
    sa_family_t     sa_family;      /* [XSI] address family */
    char            sa_data[14];    /* [XSI] addr value (actually larger) */
};

struct sockaddr_in Est défini comme suit:

struct sockaddr_in {
    __uint8_t       sin_len;
    sa_family_t     sin_family;
    in_port_t       sin_port;
    struct  in_addr sin_addr;
    char            sin_zero[8];
};

En partant des bases, un pointeur ne contient qu'une adresse. Donc struct sockaddr * Et struct sockaddr_in * Sont à peu près les mêmes. Ils enregistrent tous les deux une adresse. La seule différence pertinente est la façon dont le compilateur traite leurs objets.

Ainsi, lorsque vous dites (struct sockaddr *) &name, Vous trompez simplement le compilateur et lui dites que cette adresse pointe vers un type struct sockaddr.


Supposons donc que le pointeur pointe vers un emplacement 1000. Si struct sockaddr * Stocke cette adresse, il considérera la mémoire de 1000 À sizeof(struct sockaddr) possédant les membres selon la définition de la structure. Si struct sockaddr_in * Stocke la même adresse, il considérera la mémoire de 1000 À sizeof(struct sockaddr_in).


Lorsque vous avez transtypé ce pointeur, il considérera la même séquence d'octets jusqu'à sizeof(struct sockaddr).

struct sockaddr *a = &name; // consider &name = 1000

Maintenant, si j'accède à a->sa_len, Le compilateur accédera de l'emplacement 1000 À sizeof(__uint8_t) qui a la même taille d'octets que dans le cas de sockaddr_in. Cela devrait donc accéder à la même séquence d'octets.

Le même modèle est pour sa_family.

Après cela, il y a un tableau de caractères de 14 octets dans struct sockaddr Qui stocke les données de in_port_t sin_port (typedef 'entier non signé 16 bits = 2 octets), struct in_addr sin_addr (simplement une adresse ipv4 32 bits = 4 octets) et char sin_zero[8] (8 octets). Ces 3 totalisent 14 octets.

Maintenant, ces trois sont stockés dans ce tableau de caractères de 14 octets et nous pouvons accéder à chacun de ces trois en accédant aux index appropriés et en les transtypant à nouveau.

la réponse de user529758 explique déjà la raison de cela.

2
Mihir