I'm researching sockets in c and in
connect functions use this
(struct sockaddr *)&client and I don't get it :'(
From what I understand, you can pass a data type or struct or int or char or whatever you want to a function, but I had never seen this before. I don't understand why it puts the pointer in the parentheses and the & someone who knows a little more could explain it to me. Maybe I lack the basis to understand it but it would be good to try it and if I need it, tell me what I have to learn so that I don't miss it XD.
The underlying problem
The designers of the sockets API wanted to achieve these two goals:
- Support many different types of "addresses", so that sockets are not tied to a particular technology (eg TCP or UDP)
- But make a single
bind()function that could accept a socket and an address, to assign that address to that socket.
If you realize, the problem here is that you want to make a "generic" API, but that later you will have to use it with "specific" protocols.
For example, the concept of "IP and port" is specific to the TCP or UDP protocol. The designers of the sockets API wanted to prevent
bind() from specifically receiving an IP and port, as that wouldn't make it generic enough. It is possible to imagine other connection schemes and protocols that are not based on IPs or ports (for example perhaps Bluetooth).
So the real problem is, if we want
bind() to receive only two parameters, the socket and the address, what type to declare the address?
Do you know anything about object-oriented programming? If the designers of the sockets API had used a language that supported OOP instead of C, they might have designed things differently. For example, they could have declared an abstract class called
GenericAddress , and then derived concrete classes from it, such as
BluetoothAddress , and so on.
Thanks to the polymorphism that OOP allows, we could then have written a
bind() declaration whose second parameter was declared as
GenericAdress , but when it was invoked, instead passed a
TCPAdress , for example.
How did they solve it?
The C has no OOP or polymorphism. So the trick the API designers used was to declare a
sockaddr structure that would be equivalent to the
GenericAddress class. The
sockaddr structure simply has a field called
sin_family and that field is intended to contain a number that is different for each "concrete implementation".
Then they implemented the structure
sockaddr_in , which would be an equivalent to
UDPAddress (these two cases are equivalent for the purposes of what they should contain). In C it is not possible to declare in any way that
sockaddr_in is a "particular implementation" of
sockaddr . But using the first
.sin_family field puts a special code there that identifies that type as suitable for IP addresses/ports.
Thus, when you are going to use such a structure, you must first put the special value
AF_FAMILY in the
.sin_family field, and then the IP and port in the remaining fields.
The problem is that C requires that the types of the arguments you pass to a function match the types declared in its header. So we have another problem here. If you write a
bind() prototype that expects a socket as its first parameter and a
struct sockaddr (the generic one) as its second parameter, the compiler will complain if you then pass it a
struc sockaddr_in (the particular one with IP/Port) instead.
The trick used to "bypass" that restriction is to pass a pointer. Since a pointer is basically a memory address, in a way the specific type it points to is not that important. If
bind() is defined to expect a pointer, it can be passed the address of a
struct sockaddr_in (that's what you get by putting
& in front of a variable name, getting its address). Then the
bind() function can be programmed to access the address the pointer points to, and what it finds there is the value of the first
.sin_family field. Based on the value found, you will already know what type of structure has actually been passed to it, and therefore you will be able to cast the correct type to be able to access the rest of the elements of that structure, if necessary.
In reality, even that is not necessary, because
bind() will simply copy the complete received structure, as it is, to a memory zone from which the TCP/IP implementation of the operating will take it. That's why the data inside that structure has to be network- endianized (which forces you to use
htons() and the like).
Since the pointer indicates where the structure to copy begins, but not where it ends, it is necessary to also pass to
bind() , in a third parameter, the number of bytes that structure occupies.
All this finally leads us to the following situation:
Prototype of the
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
The first parameter is the socket, which will always be an
int. The second is the pointer to the "generic structure" (which in each particular call will be a "concrete structure"). The third is the size in bytes of the structure.
bind(s, &dir, sizeof(dir));
swill be a socket of type
dirwill be a "concrete structure", which depends on which address family we are using. For example, a
struct sockaddr_inif we are using the "IPv4 address family plus port".
With this trick "we have almost fooled the compiler". But not quite. The compiler will complain that the types don't match exactly on the call, since the function expected a
struct sockaddr * , but is being passed a
struct sockaddr_in * . Despite that problem, the compiler will just give a warning and compile the program anyway, and it will work, because at the end of the day what is passed to a function is a pointer, it doesn't really matter if the type it points to is correct or not (because the function, as we saw, can internally cast the type that interests it in each case).
Anyway, to remove this warning and make the compiler "happy", we can also cast the call. Casting is nothing more than a way to "force" the compiler to "temporarily" see a variable as being of another type. It consists of putting in front of the variable and between parentheses the type that we want to force.
bind(s, (struct sockaddr *) &dir, sizeof(dir));
The thing in parentheses in front of
&dir is the casting and it simply tells the compiler "at this point consider
&dir to be of type
struct sockaddr * , trust me I know what I'm doing although it's not very orthodox". And therefore it already compiles without warnings .