No, no es solo una convención.
sockaddr
es un descriptor genérico para cualquier tipo de operación de socket, mientras que sockaddr_in
es una estructura específica para la comunicación basada en IP (IIRC, "in" significa "InterNet"). Hasta donde yo sé, esto es una especie de "polimorfismo":el bind()
función pretende tomar un struct sockaddr *
, pero de hecho, asumirá que se pasa el tipo apropiado de estructura; i. mi. uno que corresponda al tipo de socket que le das como primer argumento.
No sé si es muy relevante para esta pregunta, pero me gustaría proporcionar información adicional que puede hacer que el encasillado sea más comprensible para muchas personas que no han pasado mucho tiempo con C
confundirse al ver semejante encasillamiento.
Yo uso macOS
, por lo que estoy tomando ejemplos basados en archivos de encabezado de mi sistema.
struct sockaddr
se define de la siguiente manera:
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
se define de la siguiente manera:
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];
};
Comenzando desde lo básico, un puntero solo contiene una dirección. Así que struct sockaddr *
y struct sockaddr_in *
son bastante iguales. Ambos solo almacenan una dirección. La única diferencia relevante es cómo el compilador trata sus objetos.
Así que cuando dices (struct sockaddr *) &name
, solo está engañando al compilador y diciéndole que esta dirección apunta a un struct sockaddr
tipo.
Así que digamos que el puntero apunta a una ubicación 1000
. Si el struct sockaddr *
almacena esta dirección, considerará la memoria de 1000
a sizeof(struct sockaddr)
que poseen los miembros según la definición de la estructura. Si struct sockaddr_in *
almacena la misma dirección que considerará la memoria de 1000
a sizeof(struct sockaddr_in)
.
Cuando encasilló ese puntero, considerará la misma secuencia de bytes hasta sizeof(struct sockaddr)
.
struct sockaddr *a = &name; // consider &name = 1000
Ahora si accedo a a->sa_len
, el compilador accedería desde la ubicación 1000
a sizeof(__uint8_t)
que tiene el mismo tamaño de bytes que en el caso de sockaddr_in
. Así que esto debería acceder a la misma secuencia de bytes.
El mismo patrón es para sa_family
.
Después de eso, hay una matriz de caracteres de 14 bytes en struct sockaddr
que almacena datos de in_port_t sin_port
(typedef
'd entero sin signo de 16 bits =2 bytes), struct in_addr sin_addr
(simplemente una dirección ipv4 de 32 bits =4 bytes) y char sin_zero[8]
(8 bytes). Estos 3 se suman para hacer 14 bytes.
Ahora estos tres están almacenados en esta matriz de caracteres de 14 bytes y podemos acceder a cualquiera de estos tres accediendo a los índices apropiados y encasillándolos nuevamente.
La respuesta de user529758 ya explica la razón para hacer esto.
Esto se debe a que bind puede vincular otros tipos de sockets además de los sockets IP, por ejemplo, sockets de dominio Unix, que tienen sockaddr_un como su tipo. La dirección de un socket AF_INET tiene el host y el puerto como su dirección, mientras que un socket AF_UNIX tiene una ruta del sistema de archivos.