J'examine des fonctions telles que connect()
et bind()
dans les sockets C et je remarque qu'elles prennent un pointeur vers une structure sockaddr
. J'ai lu et pour rendre votre application indépendante de l'AF, il est utile d'utiliser le pointeur de structure sockaddr_storage
Et de le convertir en un pointeur sockaddr
en raison de tout l'espace supplémentaire dont il dispose pour une plus grande adresses.
Ce que je me demande, c'est comment des fonctions comme connect()
et bind()
qui demandent un pointeur sockaddr
vont accéder aux données à partir d'un pointeur qui pointe vers une structure plus grande que la celui qu'il attend. Bien sûr, vous lui transmettez la taille de la structure que vous lui fournissez, mais quelle est la syntaxe réelle que les fonctions utilisent pour obtenir l'adresse IP des pointeurs vers des structures plus grandes que vous avez castées en struct *sockaddr
?
C'est probablement parce que je viens de OOP langues, mais cela semble être une sorte de hack et un peu désordonné.
Fonctions qui attendent un pointeur sur struct sockaddr
probablement transtypé le pointeur vers lequel vous les envoyez sockaddr
lorsque vous leur envoyez un pointeur vers struct sockaddr_storage
. De cette façon, ils y accèdent comme s'il s'agissait d'un struct sockaddr
.
struct sockaddr_storage
est conçu pour s'adapter à la fois à struct sockaddr_in
et struct sockaddr_in6
Vous ne créez pas votre propre struct sockaddr
, vous créez généralement un struct sockaddr_in
ou un struct sockaddr_in6
selon la version IP que vous utilisez. Afin d'éviter d'essayer de savoir quelle version IP vous utiliserez, vous pouvez utiliser un struct sockaddr_storage
qui peut contenir l'un ou l'autre. Cela sera à son tour transtypé en struct sockaddr
par les fonctions connect (), bind (), etc. et accessible de cette façon.
Vous pouvez voir toutes ces structures ci-dessous (le remplissage est spécifique à l'implémentation, à des fins d'alignement):
struct sockaddr {
unsigned short sa_family; // address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};
struct sockaddr_in {
short sin_family; // e.g. AF_INET, AF_INET6
unsigned short sin_port; // e.g. htons(3490)
struct in_addr sin_addr; // see struct in_addr, below
char sin_zero[8]; // zero this if you want to
};
struct sockaddr_in6 {
u_int16_t sin6_family; // address family, AF_INET6
u_int16_t sin6_port; // port number, Network Byte Order
u_int32_t sin6_flowinfo; // IPv6 flow information
struct in6_addr sin6_addr; // IPv6 address
u_int32_t sin6_scope_id; // Scope ID
};
struct sockaddr_storage {
sa_family_t ss_family; // address family
// all this is padding, implementation specific, ignore it:
char __ss_pad1[_SS_PAD1SIZE];
int64_t __ss_align;
char __ss_pad2[_SS_PAD2SIZE];
};
Donc, comme vous pouvez le voir, si la fonction attend une adresse IPv4, elle lira simplement les 4 premiers octets (car elle suppose que la structure est de type struct sockaddr
. Sinon, il lira les 16 octets complets pour IPv6).
Dans les classes C++ avec au moins une fonction virtuelle reçoivent un TAG. Cette balise vous permet de dynamic_cast<>()
vers n'importe quelle classe dont votre classe dérive et vice versa. Le TAG est ce qui permet à dynamic_cast<>()
de fonctionner. Plus ou moins, cela peut être un nombre ou une chaîne ...
En C, nous sommes limités aux structures. Cependant, les structures peuvent également se voir attribuer un TAG. En fait, si vous regardez toutes les structures que theprole postées dans sa réponse, vous remarquerez qu'elles commencent toutes par 2 octets (un court non signé) qui représente ce que nous appelons la famille de l'adresse. Cela définit exactement ce qu'est la structure et donc sa taille, ses champs, etc.
Par conséquent, vous pouvez faire quelque chose comme ceci:
int bind(int fd, struct sockaddr *in, socklen_t len)
{
switch(in->sa_family)
{
case AF_INET:
if(len < sizeof(struct sockaddr_in))
{
errno = EINVAL; // wrong size
return -1;
}
{
struct sockaddr_in *p = (struct sockaddr_in *) in;
...
}
break;
case AF_INET6:
if(len < sizeof(struct sockaddr_in6))
{
errno = EINVAL; // wrong size
return -1;
}
{
struct sockaddr_in6 *p = (struct sockaddr_in6 *) in;
...
}
break;
[...other cases...]
default:
errno = EINVAL; // family not supported
return -1;
}
}
Comme vous pouvez le voir, la fonction peut vérifier le paramètre len
pour vous assurer que la longueur est suffisante pour s'adapter à la structure attendue et donc ils peuvent reinterpret_cast<>()
(comme on l'appellerait en C++) votre aiguille. La question de savoir si les données sont correctes dans la structure dépend de l'appelant. Il n'y a pas beaucoup de choix à cette fin. Ces fonctions sont censées vérifier toutes sortes de choses avant d'utiliser les données et renvoyer -1 et errno
chaque fois qu'un problème est détecté.
Donc, en fait, vous avez un struct sockaddr_in
Ou struct sockaddr_in6
Que vous (réinterprétez) cast en un struct sockaddr
Et la fonction bind()
(et d'autres) cast ce pointeur revenir à un struct sockaddr_in
ou struct sockaddr_in6
après avoir vérifié le membre sa_family
et vérifié la taille.