Sockets API

[ 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.

Header Files

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.

Table 2.1. Sockets API Header Files

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.

Socket API Errors

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.

Initializing the API

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.

Table 2.2. WSAStartup() Error Codes

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.

Creating a TCP Listening Socket

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.

Creating the Socket

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.

Table 2.3. socket() Error Values

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

Binding the Socket

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.

Figure 2.8. A simple NAT/LAN setup.

graphic/02fig08.gif


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.

Table 2.4. bind() 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!

Listening

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.

Table 2.5. listen() Error Codes

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!

Accepting 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.

Table 2.6. accept() 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."

Creating a TCP Data Socket

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.

Creating the Socket

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 the 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.

Table 2.7. connect() 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.

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!

Sending 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.

Table 2.8. send() 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.

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

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.

Table 2.9. recv() 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.

Closing a Socket

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.

Table 2.10. shutdown() 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.

Miscellaneous Functions

There are several other miscellaneous functions associated with the Sockets API.

Converting IP Addresses to Strings and Back

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.

Getting Socket Information

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 ]


MUD Game Programming
MUD Game Programming (Premier Press Game Development)
ISBN: 1592000908
EAN: 2147483647
Year: 2003
Pages: 147
Authors: Ron Penton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net