I can see why it is useful to cast sockaddr to sockaddr_in, but I don\'t understand how this is possible. From what I\'ve read, they\'re the same size
It is possible because you normally cast pointers, not the structures themselves. You do what in natural language means "please treat this pointer to a socket structure as a pointer to an internet socket structure instead". Compiler has no problems to re-interpret the pointer.
Here is more detailed description taken up from comments:
A sockaddr is 16 bytes in size - the first two bytes are the sa_family, and the remaining 14 bytes are the sa_data which is arbitrary data. A sockaddr_in is also 16 bytes in size - the first 2 bytes are the sin_family (always AF_INET), the next 2 bytes are the sin_port, the next 4 bytes are the sin_addr (IP address), and the last 8 bytes are the sin_zero which is unused in IPv4 and provided only to ensure 16 bytes. This way, you can look at sockaddr.sa_family first, and if it is AF_INET then interpret the entire sockaddr as a sockaddr_in.
A sockaddr_in is not stored inside of sockaddr.sa_data field. The entire sockaddr is the entire sockaddr_in (when sockaddr.sa_family is AF_INET, that is). If you take a sockaddr* pointer and cast it to a sockaddr_in* pointer, then:
sockaddr.sa_family is sockaddr_in.sin_familysockaddr.sa_data are sockaddr_in.sin_portsockaddr_in.sin_addrsockaddr_in.sin_zero.