Future Enhancements

Back in Chapter 2, we discussed the sockaddr_in structure. This structure supports IPv4, but not IPv6. As we noted in the previous two sections, the IPv6 version of the structure is known as sockaddr in6 and looks like this:

struct sockaddr_in6 {

sa_family_t sin6_family; in_port_t sin6_port; uint32_t sin6_flowinfo; struct in6_addr sin6_addr; uint32_t sin6_scope_id;

/* Transport layer port # */ /* IPv6 flow information */ /* IPv6 address */ /* IPv6 scope-id */

At first glance, the easiest thing to do to port an application to IPv6 would be to change the code to use sockaddr_in6 instead of sockaddr_in, as we did with our IPv4 client and server earlier in this section. But what happens, then, if you want to use IPv4 as well as IPv6? By switching the structures, you end up splitting your application into an IPv4 version and an IPv6 version. This is workable, but it isn't the optimal solution. Fortunately, there's a better way. Instead of trying to determine which structure to use when, and instead of maintaining two versions of the same application, you can use a special structure specifically designed for portability, the sockaddr_storage structure:

/* Structure large enough to hold any socket address (with the historical exception of AF_UNIX). We reserve 128 bytes. */ #if ULONG_MAX > 0xffffffff

# define _ss_aligntype _uint64_t

#else

# define _ss_aligntype _uint32_t

#endif

#define _SS_SIZE 128

#define _SS_PADSIZE (_SS_SIZE - (2 * sizeof (__ss_aligntype)))

struct sockaddr_storage {

_ss_aligntype _ss_align; /* Force desired alignment. */

char _ss_padding[_SS_PADSIZE];

The sockaddr_storage structure uses 128 bytes and is designed to be large enough to hold any socket address. By using this new structure, you can hide the particulars of whether the address space is IPv4 or IPv6, while still allowing the application to use either. By using sockaddr_storage along with the IPv4-compatible functions getnameinfo() and getaddrinfo(), you can create applications that are ready for either IPv4 or IPv6.

Let's tweak our IPv6 client so that it can handle either an IPv4 or an IPv6 connection. The changes are fairly simple. We start out with our standard declarations.

#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h>

int simpleSocket = 0;

int simplePort = 0;

int returnStatus = 0;

The next thing we need to do is declare our addrinfo structure, because this version of our client must handle either an IPv4 address or IPv6 address. We set up our structure like this:

struct addrinfo hints, *res;

bzero(&hints, sizeof(struct addrinfo));

hints.ai_family = PF_UNSPEC;

hints.ai_socktype = SOCK_STREAM;

We use two addrinfo structures. Of special note here is the use of PF_UNSPEC to set ai_family in the first structure. This is a special constant that means the address family is unspecified. The first structure, called hints, is used to set our protocol preferences. The second addrinfo structure, called res, is used to contain a linked list of all the possible addresses on this host. This is used because a given host can have more than one IP address, whether the IP version is 4 or 6. For our purposes, we're using only localhost.

Next, we do a standard arguments check, and then we populate our addrinfo structure using the getaddrinfo() function we've already discussed. For the arguments to getaddrinfo(), we use the server's IP address and the server's port number from our command line, and we pass it pointers to our addrinfo structure.

fprintf(stderr, "Usage: %s <server> <port>\n", argv[0]); exit(l);

/* setup the address structure */

returnStatus = getaddrinfo(argv[l], argv[2], &hints, &res);

if(returnStatus) {

fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(returnStatus)); exit(l);

As noted previously, we need to remember to use getaddrinfo()'s special error function, gai_strerror(), instead of the typical perror(). Our next step is to create our socket. This is a change from our earlier client code, because instead of using our command-line arguments directly, we use the values from our addrinfo structure that were filled in by getaddrinfo().

simpleSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

fprintf(stderr, "Could not create a socket!\n"); exit(1);

fprintf(stderr, "Socket created!\n");

Next, we take our socket and make our connection to the server. As before, we use the values in our addrinfo structure.

/* connect to the address and port with our socket */ returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);

fprintf(stderr, "Connect successful!\n");

fprintf(stderr, "Could not connect to address!\n");

close(simpleSocket);

Then we read the message from the server, just as we did in our earlier client examples.

/* get the message from the server */

returnStatus = read(simpleSocket, buffer, sizeof(buffer));

printf("%d: %s", returnStatus, buffer); } else {

fprintf(stderr, "Return Status = %d \n", returnStatus);

Finally, we close our socket and clean up, remembering to use getad-drinfo()'s companion function, freeaddrinfo(), to clean up our structure.

close(simpleSocket); freeaddrinfo(res); return 0;

That is all that's needed to handle either IPv4 or IPv6 addresses. You can test your multiaddress client using the IPv4 server from Chapter 2, and the IPv6 server from this appendix. For example, take the server example from Chapter 2 and start it on port 8888. Take the IPv6 server from this appendix and start it on port 9999. Using the new multiaddress client, we can run a test like this:

[[email protected] projects]$ cc -o multiClient multiClient.c [[email protected] projects]$./multiClient 127.0.0.1 8888 Socket created! Connect successful!

46: APRESS - For Professionals, By Professionals! [[email protected] projects]$ ./multiClient ::1 9999 Socket created! Connect successful!

46: APRESS - For Professionals, By Professionals! [[email protected] projects]$

In the tests just shown, we make our first connection using the IPv4-style 127.0.0.1 (localhost), while our second connection is done using the IPv6-style ::1. As you can see, using the same version of our client and our addrinfo structure, we're able to connect to either an IPv4 server or an IPv6 server.

Was this article helpful?

0 0

Post a comment