[ LiB ] |
I'm going to be brutal here for a second; the Sockets API is ugly . It's versatile, it works, and it's stable, but it's ugly. The Sockets API was created when C was king, and C++ didn't even exist, so clean object-oriented interfaces were unknown.
NOTE
The Microsoft Winsock API is an implementation of the Berkeley Sockets API, but they are not com pletely compatible. I will notify you about the differences when I get to them. Don't worrythere aren't many. A few #ifdefs here and there are enough to ensure that your network ing programs are compatible with both UNIX and Windows. Platform independence is a wonderful thing.
Because of this, the entire API is built with all kinds of messy structs and unions, as well as badly named functions. Of course, the Sockets API is also the most popular networking API in the world, which means you usually have no choice but to bite the bullet and use it. The trick is to create a thin layer of sane software above it.
I'm not going to spend a great deal of time on the actual API; after all, this book is about MUDs, not network programming in general. There are literally hundreds of books that cover this material better than I can, since they are purely about networking. This chapter is an introduction to the API that teaches you its use in relation to MUDs; therefore, I won't get into the juicy details of multicasting and other things you may never use.
Two good books that cover this material are Multiplayer Game Programming by Todd Barron, and Network Programming for Microsoft Windows , by Anthony Jones and Jim Ohlund.
The main difference between the Sockets API and Winsock is that they require different header files to access the APIs.
Winsock is easy in this regard; everything you need is in two files:
#include "winsock.h" #include "ws2tcpip.h"
On UNIX systems, the Sockets API is spread among many different header files, thus making things more difficult. What a surprise, huh?
Table 2.1 lists the file names and their contents.
File | Contents |
---|---|
sys/types.h | All the needed basic types |
sys/socket.h | All the socket data structures |
netinet/in.h | All the functions needed for IPv4 and IPv6 |
unistd.h | The gethostname() function needed to find the name of the local machine |
netdb.h | All the needed DNS functions |
arpa/inet.h | All the functions that start with inet_ |
errno.h | All the error handling stuff |
fcntl.h | All the file control stuff |
Don't worry if you don't know what some of that stuff means for now; I will eventually get to it all.
NOTE
The file winsock.h contains the header information for Version 1 of Winsock. The newer version of Winsock, Winsock 2, adds a whole slew of new networking features, but alas, they are for Windows only. The header for Winsock 2 is (surprise!) winsock2.h. It really makes no difference which header you use; however, stick to the base Sockets API, since Winsock 2 is completely backward compatible. In fact, it may even be better to use Winsock 2 (pretty much every Microsoft operating system since Windows 95 has had Winsock 2 built in) since the imple mentation is better optimized than in the old version. Just be sure to include the same version library file with your project. See Appendix A , " Setting Up Your Compilers " (found on the CD), for more information.
In UNIX, all sockets are files. The operating system doesn't differentiate between sockets and files, so you can use the same reading and writing functions with both. Because of this, whenever a socket error occurs, the global variable errno is set with the error value.
Unfortunately , Windows took an entirely different approach to the system, so sockets and files are treated as separate entities. To make matters worse , they've also made Winsock incompatible with the errno error reporting system. So, whenever an error occurs, you must retrieve the error by using the WSAGetLastError() function. Don't worry, thoughthey both return the same error values.
You can solve this conflict by using #defines , and I show you how to do this in Chapter 4, "The Basic Library," where I explain how to abstract the Socket APIs into a wrapper.
Appendix B, "Socket Error Codes" (found on the CD), has a complete listing of all the error codes and their meanings. I have attempted to list every error that is possible for each socket function in this chapter, but you should note that other socket errors might occur in the underlying network subsystem as well.
This is another area in which Winsock strays from the pure Sockets API. The Sockets API doesn't need to be initialized; you can just jump right in and start using it. Winsock, however, needs to be initialized first. It also needs to be shut down when you are finished using it. The initialization and shutdown functions are listed here:
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData ); int WSACleanup( void );
The first function takes two parameters: the number of the Winsock version you want to use, and a pointer to a WSADATA structure that will contain data about Winsock. As of this writing, the most current Winsock version is 2.2, so you initialize Winsock like this:
WSADATA winsockdata; WSAStartup( MAKEWORD( 2, 2 ), &winsockdata );
It is usually best to keep track of the WSADATA structure, even though you're probably not going to use it. The MAKEWORD function is a handy macro that creates a 16-bit value using the two-byte values you pass into it.
When initializing the API, either a zero is returned to indicate no errors, or an error value is returned. Table 2.2 lists all possible error values.
Error | Meaning |
---|---|
WSAENOTREADY | The network is not ready to be initialized. |
WSAVERNOTSUPPORTED | The supplied Winsock version is not supported. |
WSAEINPROGRESS | A blocking Winsock call is already in progress. |
WSAEPROCLIM | There are too many programs running Winsock at the moment. |
WSAEFAULT | The pointer to the WSADATA structure was invalid. |
When you want to shut down Winsock, just call the WSACleanup function.
I will begin by showing you how to create a listening socket for TCP.
I've already described the different types of sockets, and unfortunately, the Sockets API doesn't really distinguish among them. All sockets are identified by a common datatype: int . Yep; every socket is just an integer. The Sockets API keeps track of everything for you internally.
NOTE
In general, it is always safer to use the typedefs that the API provides. In Winsock, even though a SOCKET is an int when you use it, it may not always be. The Winsock API functions specifically use SOCKET s, and if Microsoft decides to change a socket into type foobar someday, then you'll be out of luck if you assumed you were using int s. While it would be incon venient for Microsoft to do so, things like this have happened before. The bottom line is this: It's safer to use typedefs than to assume that you're using a specific datatype.
As an alternative, Winsock gives you the option of using the SOCKET typedef. If you trace all the way down through the header files, you eventually find that the SOCKET typedef is an int !
For now, I am going to assume that a socket is an integer; in Chapter 4, I will show you how to seamlessly abstract the two APIs into a single wrapper.
Here's the code to create a socket:
int sock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
This code calls the socket function, which attempts to create a socket for you. The first parameter is the Address Family , which determines what network your socket will use.
The second parameter is the Socket Type . The example uses a type called SOCK_STREAM , which means it will be a TCP socket. If you want to make this a UDP socket, you would use SOCK_DGRAM instead. (DGRAM means datagram.)
NOTE
The Sockets API has the capability to use many types of networks, other than IP networks. However, in practice, you will almost never need to use any of these other networks, since most of them are either outdated or reserved for private companies. Therefore, you will almost always be using the AF_INET address family.
Finally, the last parameter is Protocol . Different socket types may have several associated protocols. For example, the most popular SOCK_STREAM protocol is IPPROTO_TCP , which is used in the example. The popular SOCK_DGRAM protocols are IPPROTO_UDP and IPPROTO_ICMP . As I said earlier, you're going to be concerned mainly with TCP and UDP.
If the function fails, it returns -1. If the function succeeds, a socket descriptor is returned. Table 2.3 lists the various error values that errno / WSAGetLastError() will contain if it fails.
Error | Meaning |
---|---|
ENETDOWN | The network has failed and is down. |
EAFNOSUPPORT | The specified address family is not supported. |
EINPROGRESS | A call to this function is still in progress, so the new call could not be completed. |
EMFILE | No more socket descriptors are available. |
ENOBUFS | There isn't enough memory available. |
EPROTONOSUPPORT | The specified protocol is not supported. |
EPROTOTYPE | The specified protocol is not supported by the socket type. |
ESOCKTNOSUPPORT | The specified socket type is not supported by the address family. |
WSAENOTINITIALIZED [*] | The Socket Library isn't initialized. |
[*] Winsock only
So now you have a socket. Next you want to bind the socket to a port number. Unfortunately, it's not as easy as it sounds; first you need to fill out a messy data structure.
Before you do that, though, I will show you the function definition:
int bind( int socket, struct sockaddr *name, int namelen );
The first parameter is the socket descriptor that you created with the socket function.
The second parameter is a sockaddr structure, which describes all types of things about the socketmost importantly the port number. But there are other things as well, which I'll get to in a bit.
The final parameter is the size of the sockaddr structure. Why is this needed? The sockaddr structure really isn't that important in the grand scheme of things; it's a flexible structure that has no solid definition. Depending on the socket type and protocol you use, this structure varies in size, so it is important for the function to know this.
Here is the standard definition of the sockaddr structure:
struct sockaddr { unsigned short sa_family; char sa_data[14]; };
The first piece of data is the sa_family variable. This is set to the address family that your socket is using, which is almost always AF_INET , the Internet address family. The rest of the structure is just padding to fill out the 16 bytes.
Obviously, this doesn't contain a heck of a lot of information about a socket connection, so it's not that useful. Instead, you'll be using a more specific version, called sockaddr_in , where the in stands for Internet. You can think of this as an ancient form of inheritance, before inheritance was actually invented. The base structure is sockaddr , and a more specific version that is designed for IP networks is the sockaddr_in structure.
The new structure looks like this:
struct sockaddr_in { unsigned short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8]; };
The first variable, sin_family , is the same as the sa_family from the sockaddr structure. The sin_port variable is simply the port number on which the socket will be open .
The third variable, sin_addr , is the IP address, which has two functions: First, if it's a listening socket, the socket uses this address for listening; second, if it's a client-side data socket, the socket uses this as the IP address for connecting. (I'll explain this in more detail in the section entitled "Creating a TCP Data Socket.")
Both the port and the address are supposed to be in network byte order.
The last variable, sin_zero , is just padding to fill the structure to 16 bytes. Some implementations of the Sockets API require that the padding must be filled in to zero, so you must do this.
So, now to actually bind a socket, you first create the sockaddr_in structure and fill it out:
struct sockaddr_in socketaddress; // create struct socketaddress.sin_family = AF_INET; // set it for Internet socketaddress.sin_port = htons( 1000 ); // use port 1000 socketaddress.sin_addr.s_addr = htonl( INADDR_ANY ); // bind to any address memset( &(socketaddress.sin_zero), 0, 8 ); // clear padding
This binds a socket to port 1000 and to address INADDR_ANY . Basically, this means that the socket will accept any incoming connections. You will almost always use that value, but you can use other values instead. For example, if you use the address 127.0.0.1 (the loopback address that references your own computer), the socket accepts only connections that are trying to connect to the IP address 127.0.0.1. Since packets that are sent to your computer from other computers won't be trying to access that address, the socket won't accept connections from outside your computer.
NOTE
If you are running a Network Address Translation (NAT) on your own personal network, each computer on your local area network (LAN) has its own IP address (usually in the 192.168.0.* range of addresses), but from outside your LAN, the Internet sees all your computers as a single IP address. By specifying your LAN IP address, you can prevent people from accessing your network programs if they aren't on your LAN. That way, the socket will only accept connections from computers that are trying to reach your LAN address, which isn't visible on the Internet. Figure 2.8 shows a simple NAT/LAN setup, in which the Internet sees the attached computers as one IP address. All the computers behind the NAT are assigned internal IP addresses and are accessed by the Internet using the IP address of the NAT itself. The Internet doesn't know or care about the internal addresses.
The Sockets API provides a handy function for converting an IP address in string form to an integer in network byte order. For example, if you want to use the IP address 127.0.0.1 in the sockaddr_in structure, you type this:
socketaddress.sin_addr.s_addr = inet_addr( "127.0.0.1" );
Finally, you want to bind the socket with the address structure, so you type this:
bind( sock, (struct sockaddr*)&socketaddress, sizeof(struct sockaddr));
If the function doesn't succeed, it returns -1. If it succeeds, it returns 0. Table 2.4 shows the possible error codes.
Error | Meaning |
---|---|
ENETDOWN | The network has failed and is down. |
EINPROGRESS | A call to this function is still in progress, so the new call could not be completed. |
ENOBUFS | There isn't enough memory available. |
ENOTSOCK | The socket descriptor passed in is not a real socket. |
EACCES | Access was denied . |
EADDRINUSE | The address is already in use. |
EADDRNOTAVAIL | The address is not valid for this machine. |
EFAULT | One or more of the parameters were invalid. |
WSAENOTINITIALIZED [*] | The Socket Library isn't initialized. |
[*] Winsock only
Now your socket is bound and ready to accept connections!
Now that you've bound your socket to an address and port, you need to make it listen for connections. Luckily for us, this function is incredibly simple:
int listen( int socket, int backlog );
The function accepts a socket descriptor and a backlog parameter. The backlog essentially tells the socket how many connections to keep in its queue before it starts refusing them. A connection stays in the socket's queue until you use the accept() function to remove it.
Here is an example of calling the function:
listen( sock, 16 );
This tells the Sockets API that you want to listen on the socket, and you want it to queue 16 connections. If 17 machines try to connect to this socket before you are able to accept the connections, the seventeenth connection is refused , and the first 16 remain in the queue until you accept them. Whenever you accept a connection, it is removed from the queue, and more connections can then be queued up.
If no error occurs, 0 is returned; if an error occurs, -1 is returned. Table 2.5 lists the error codes for this function.
Error | Meaning |
---|---|
ENETDOWN | The network has failed and is down. |
EADDRINUSE | The address is already in use. |
EINPROGRESS | A call to this function is still in progress, so the new call could not be completed. |
EINVAL | The socket is not valid. |
EISCONN | The socket is already connected. |
EMFILE | No more socket descriptors are available. |
ENOBUFS | There isn't enough memory available. |
ENOTSOCK | The socket descriptor passed in is not a real socket. |
EOPNOTSUPP | The socket doesn't support this function. |
WSAENOTINITIALIZED [*] | The Socket Library isn't initialized. |
[*] Winsock only
Now your socket is listening, and you're ready to accept connections!
At last, you are at the final part of the listening socket cycleaccepting connections. It's taken quite a while to get this far, hasn't it? This is how you would call the function:
int accept( int socket, struct sockaddr *addr, socklen_t *addrlen );
The function has three parameters: the listening socket descriptor, a pointer to a sockaddr , and a pointer to an int .
The sockaddr structure is filled out by the function; you can think of it as a caller-id box; it indicates who is connecting to you. The addrlen pointer is supposed to contain the length of the addr structure. Why is it a pointer? Well, presumably, it is possible for the accept function to modify this value, but I've never seen it happen. It's just one of those quirks of the API. Here is how you would accept the function:
int datasock; struct sockaddr_in socketaddress; socklen_t sa_size = sizeof( struct sockaddr_in ); datasock = accept( sock, &socketaddress, &sa_size );
Now the datasock variable should be a data socket, and you can use it to communicate with the caller. If the function fails, it returns -1. Table 2.6 lists the possible error codes.
Error | Meaning |
---|---|
ENETDOWN | The network has failed and is down. |
EINPROGRESS | A call to this function is still in progress, so the new call cannot be completed. |
EINVAL | The socket is not valid. |
EMFILE | No more socket descriptors are available. |
ENOBUFS | There isn't enough memory available. |
ENOTSOCK | The socket descriptor passed in is not a real socket. |
EOPNOTSUPP | The socket doesn't support this function. |
EFAULT | One or more of the parameters were invalid. |
EWOULDBLOCK | The function exited because it would block. |
WSAENOTINITIALIZED [*] | The Socket Library isn't initialized. |
[*] Winsock only
One important aspect of this call differs significantly from any other calls I've shown you so farit blocks . See the following sidebar for an explanation about blocking.
NOTE
Blocking Functions
If you are unfamiliar with the term blocking , that's okay. If you've ever used the cin or scanf functions before, you've encountered blocking.
A blocking function depends on external input (keyboard or network) and cannot complete until that input is received. Unfortunately, these input sources are not reliable; keyboard input can take a long time if the user isn't there to type anything in, and network communications take time. However, a blocking function stops your entire program and just waits for that external data to arrive .
In the bad old days, this was desirable behavior. There were many programs running on a system, and whenever a program needed input from a potentially slow source, it could stop the program and switch to something else while waiting for input.
This isn't such a great idea for games , though. No one wants the entire game to stop just to wait for network or keyboard datathat's just annoying. Fortunately, there are ways around this. You can make sockets nonblocking , which means that any blocking function will fail and return an EWOULDBLOCK error if there is no data already queued up for it to use. I won't go into this method much; it is generally wasteful of CPU usage, since the CPU is wasting time by constantly polling every socket.
Another method uses the select() function to poll many sockets at once to check if any of them has activity. This is the desired method for single-threaded programs. I cover this method later on in this chapter.
A third popular option is to use multithreading and have each blocking call occur in its own thread, so that none disrupts the other threads of the program. I cover threading in detail in Chapter 3, "Introduction to Multithreading."
You've just learned how to create a listening socket for TCP, which is great for servers. However, a listing socket is pretty useless if you don't have a method for connecting to it. This is where the data socket comes in.
Luckily, creating a data socket is nice and easy and takes only two function calls. The first one should be familiar to you.
Actually, creating the socket uses the same function as a listening socket: the socket() function. I bet you didn't see that one coming, right?
Basically, you'll use the same parameters as last time:
int datasock; datasock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
Ta-da! You now have a socket!
Connecting a data socket to a listening socket takes one function call. Can you guess the function name? If you said connect() , you get a star!
The function definition looks like this:
int connect( int socket, const struct sockaddr *name, int namelen );
The first thing you should notice is that it looks exactly like the bind function.
You need to fill out a sockaddr -type structure, which is going to be the sockaddr_in structure, since we're using TCP. This time, you're going to fill it out with the address you want to connect to. For example, if you want to connect to 192.168.0.2 on port 4000, you fill out the structure like this:
struct sockaddr_in socketaddress; socketaddress.sin_family = AF_INET; socketaddress.sin_port = htons( 4000 ); socketaddress.sin_addr.s_addr = inet_addr( "192.168.0.2" ); memset( &(socketaddress.sin_zero), 0, 8 );
After you fill that out, you can connect:
connect( datasock, &socketaddress, sizeof( struct sockaddr ) );
If there are no errors, 0 is returned; if there are errors, -1 is returned. Table 2.7 lists the error codes possible with this function.
Error | Meaning |
---|---|
ENETDOWN | The network has failed and is down. |
EINPROGRESS | A call to this function is still in progress, so the new call could not be completed. |
EADDRINUSE | The address is already in use. |
EINVAL | The socket is not valid. |
EADDRNOTAVAIL | The remote address is not valid. |
EAFNOSUPPORT | The specified address family is not supported. |
ENOBUFS | There isn't enough memory available. |
ENOTSOCK | The socket descriptor passed in is not a real socket. |
EWOULDBLOCK | The function exited because it would block. |
ECONNREFUSED | The server refused the connection. |
EFAULT | One or more of the parameters were invalid. |
EISCONN | The socket is already connected. |
ENETUNREACH | The destination address is unreachable. |
ENOTSOCK | The socket descriptor passed in is not a real socket. |
ETIMEDOUT | The operation failed to complete in the time-out period. |
WSAENOTINITIALIZED [*] | The Socket Library isn't initialized. |
[*] Winsock only
If the function call succeeds, you are connected to a server and ready to send and receive data!
Now that you've got a data socket, you can send data. This is an amazingly simple operation to accomplish, using the send function:
int send( int socket, const char *buffer, int len, int flags );
The first parameter is obviously the socket you want to use for sending the data, and the second parameter is a pointer to a buffer. The buffer is in chars , which is just a big chunk of bytes in memory. The next parameter is the length of the data in the buffer, and finally there is a Flags parameter. You'll probably never use any of the flags, since they're only useful to low-level network programmers, so I won't bother explaining them here. If you're really interested, networking books cover the details.
So, to send data, you would do something like this:
char* string = "hello, Internet!"; int sent; sent = send( datasock, string, strlen( string ), 0 );
The function returns the number of bytes that send actually sent. Beware that the function may not send all the bytes you requested . In this case, you should attempt to resend what wasn't sent.
If the call fails, the value of -1 is returned. Table 2.8 lists the error codes for this function.
Error | Meaning |
---|---|
ENETDOWN | The network has failed and is down. |
EINPROGRESS | A call to this function is still in progress, so the new call could not be completed. |
EACCES | Access was denied. |
EFAULT | One or more of the parameters were invalid. |
ENETRESET | The network has been reset and the connection broken. |
ENOBUFS | There isn't enough memory available. |
ENOTCONN | The socket is not connected. |
ENOTSOCK | The socket descriptor passed in is not a real socket. |
EOPNOTSUPP | The socket doesn't support this function or option. |
ESHUTDOWN | The socket has been shut down. |
EWOULDBLOCK | The function exited because it would block. |
EHOSTUNREACH | The host is unreachable. |
EINVAL | The socket is not valid. |
ECONNABORTED | The connection was aborted and the socket is no longer usable. |
ECONNRESET | The connection was closed by the other side. |
ETIMEDOUT | The connection was closed unexpectedly. |
WSAENOTINITIALIZED [*] | The Socket Library isn't initialized. |
[*] Winsock only
Pretty easy, don't you think?
Receiving data is just as easy as sending data. Here is the function definition for the recv() function:
int recv( int socket, char *buffer, int len, int flags );
As you can see, the parameters are the same as the send() function. So you call it like this:
char buffer[128]; int received; received = recv( datasock, buffer, 128, 0 );
This creates a buffer large enough for 128 bytes of data and then waits for incoming data. Note that this function probably returns before it gets a full 128 bytes of data, and it receives only a maximum of 128 bytes of memory, so you don't have to worry about the buffer overflowing.
As usual, the function returns -1 on failure. Table 2.9 lists the error codes.
Error | Meaning |
---|---|
ENETDOWN | The network has failed and is down. |
EFAULT | One or more of the parameters were invalid. |
ENOTCONN | The socket is not connected. |
EINPROGRESS | A call to this function is still in progress, so the new call could not be completed. |
ENETRESET | The network has been reset and the connection broken. |
ENOTSOCK | The socket descriptor passed in is not a real socket. |
EOPNOTSUPP | The socket doesn't support this function or option. |
ESHUTDOWN | The socket has been shut down. |
EWOULDBLOCK | The function exited because it would block. |
EINVAL | The socket is not valid. |
ECONNABORTED | The connection was aborted, and the socket is no longer usable. |
ETIMEDOUT | The connection was closed unexpectedly. |
ECONNRESET | The connection was closed by the other side. |
WSAENOTINITIALIZED [*] | The Socket Library isn't initialized. |
[*] Winsock only
Note also that recv() is a blocking function; it stops everything in the current thread and waits for the next TCP packet to arrive.
That's pretty much it.
Once you've finished using a socket, you close it by calling two functions: the shutdown() function and the close() function.
First, you call the shutdown() function. Here is the function definition:
int shutdown( int socket, int how );
The first parameter is the socket you are shutting down, and the second parameter is the method you are using to shut it down. In almost all cases, you use the value 2, which shuts down both sending and receiving. The other two possible options are 0 and 1, which shut down receiving and sending, respectively.
As usual, the function returns 0 on success and -1 on failure. Table 2.10 lists the possible error codes.
Error | Meaning |
---|---|
ENETDOWN | The network has failed and is down. |
EINVAL | The socket is not valid. |
EINPROGRESS | A call to this function is still in progress, so the new call could not be completed. |
ENOTCONN | The socket is not connected. |
ENOTSOCK | The socket descriptor passed in is not a real socket. |
WSAENOTINITIALIZED [*] | The Socket Library isn't initialized. |
[*] Winsock only
After you shut down a socket, it still exists in the system. The socket takes care of any pending data that is sent or received and gracefully shuts down, but the socket isn't yet closed. You need to make one more call to close it.
In UNIX sockets are files. Because of this, you can use the standard UNIX close() function to close a socket.
However, Windows sockets are not files, so calling the close() function on them won't work. For this reason, Microsoft changed the function's name to closesocket () . The parameters and the return values are the same. Here is the definition:
int close( int socket );
Pretty simple function, don't you think?
As usual, the function returns 0 on success and -1 on failure. This function has the same error codes as the shutdown() function, which are listed in Table 2.10.
There are several other miscellaneous functions associated with the Sockets API.
The first of these miscellaneous functions is the inet_addr() function, which takes an IP address in a string and converts it into an unsigned long :
unsigned long inet_addr( const char *string );
The string must be in *.*.*.* format, where each number between the periods is a number from 0 to 255. If you used an invalid string, the function returns the value INADDR_NONE . Also remember that the address is returned in network-byte-order (NBO), so there is no need to convert it when using it with the Sockets functions.
The next function does the opposite ; it takes a numeric address and converts it to a string:
char* inet_ntoa( struct in_addr in );
This is one ugly function; I hate it with a passion. Okay, you know that Internet addresses are stored in unsigned long s, right? So why the heck does the function take a structure called in_addr ? Who knows ? The designers decided to make things difficult for us.
So you must convert your Internet address into an in_addr first, which requires code that looks like this:
unsigned long address = inet_addr( "192.168.0.1" ); struct in_addr addr; addr.S_un.S_addr = address; // ugh, UGLY! char* addrstr = inet_ntoa( addr );
Isn't that ugly? The in_addr structure is a union , which is an ancient C concept that is rarely used nowadays, but it allows you to store many different types of data in the same amount of memory, as long as the different types don't exist at the same time. Make sense? If not, don't worry about it. It's not important.
So anyway, addrstr in the example will now be a pointer to a string. But you shouldn't do anything to it, except copy it immediately and store the result for your own use. The function actually has a static (or global, in some implementations) string buffer that it keeps for itself, and whenever the function is called, it overwrites the buffer contents. This means that you shouldn't try deleting the buffer either, which is another reason why this function is just plain ugly. Anyway, when it's done, the string should contain 192.168.0.1. If an error occurs (though I've never seen this happen), NULL is returned.
The next function is used to get information about a socket:
int getsockname( int socket, struct sockaddr* name, socklen_t* namelen );
Essentially, this gets the port and address of a socket and stuffs it into a sockaddr_in structure. Here is an example:
struct sockaddr_in addr; socklen_t sa_size = sizeof( struct sockaddr_in );// fill out the size getsockname( sock, (struct sockaddr*)&addr, &sa_size ); unsigned short port = ntohs( addr.sin_port ); // get the port number unsigned long address = ntohl( addr.sin_addr ); // get the address number
You can see why I dislike this function; it requires an inordinate amount of work to accomplish a simple task. You need to have an address structure, and you need to fill in an integer with its size as well. Then you can call the function and retrieve the data you want. Pain in the butt!
The other function in this category gets the information of the peer , which is the computer on the other side of the connection:
int getpeername( int socket, struct sockaddr* name, socklen_t* namelen );
This function is virtually identical to the previous one. Note that you may get ENOTCONN errors with the peer function, if it's not connected yet.
[ LiB ] |