CSCI.4220 Network Programming
Fall, 2006
Advanced Sockets

The basic socket program that we saw in the second class has some limitations. If the receiver executes a read or recv, and the sender is not sending any data, the receiver will be blocked forever. It is easy to imagine a deadlock situation in which each end of a connection is waiting for data from the other.

Also, there are times when a server has several sockets open and would like to do other things during those period when there is no data on any of the lines, but would like to be signaled when data is available on any of the lines.

I/O Multiplexing: The select function

There are several mechanisms which can be used to address these problems. In general, the preferred solution is to use the select function. This function is somewhat awkward to use, but it solves the two problems mentioned above. It allows the process to sleep, but be awakened when any one of multiple events occurs. It also allows the process to set a timer and to be awakened if none of the events occur during that time period.

Here is the function prototype

#include <sys/select.h>
#include <sys/time.h>

int select(int maxfd, 
           fd_set *readset, 
           fd_set *writeset, 
           fd_set *errset, 
           const struct timeval *timeout)

struct timeval {
   long tv_sec;  /* seconds */
   long tv_usec; /* microseconds */
};

Here is the FreeBSD man page for select.

An fd_set is a set of file descriptors. The structure fd_set is an opaque structure, the user does not need to know what the internal structure looks like because there are four macros which perform all of the useful operations.

Here are the macros:

  void FD_SET(fd, &fdset);  //sets a file descriptor

  void FD_CLR(fd, &fdset); // clears a file descriptor

  int FD_ISSET(fd, &fdset); // is a file descriptor set?

  void FD_ZERO(&fdset); //clears all file descriptors

Here is the basic algorithm for reading from a set of file descriptors:

  1. Create an instance of an fd_set
          struct fd_set readset;   
  2. Initialize it by clearing all its values
          FD_ZERO(&readset); 
  3. Set one or more file descriptors in the set with FD_SET. This macro adds file descriptor zero to the set
          FD_SET(0, &readset); 
  4. If you wish to set a timer, create an instance of a timeval and fill in its seconds and microseconds values. Note that you can apparently set a timer to the nearest microsecond, but in fact most systems cannot time events more accurately than 10 msec or so.
  5. Call select with the following arguments.
Select will sleep until either data is available on one of the set file descriptors or the timer goes off. In the former case, select will return the number of file descriptors that have data. If the timer goes off, select will return 0. Select will return a negative value if some sort of exception occurred.

If more than one fd is set, you need enter a loop which calls the macro FD_ISSET on each file descriptor to determine which one has data.

You can set the values of the timer to zero, which would force a call to select to return immediately even if there is no data. This is a non-blocking read.

As usual, some sample programs will help to explain this. This program listens for the user to type some input into standard input. If the user has not entered any input within five seconds, the program wakes up, displays a suitable message, and terminates. (When you are testing this program, recall that characters typed at the keyboard into standard input are not actually sent to the program until the user hits the enter key).

  1 #include <stdio.h>
  2 #include <sys/time.h>
  3 #include <sys/types.h>
  4 #include <unistd.h>
  5
  6 int main() {
  7    fd_set rfds;
  8    struct timeval tv;
  9    int retval,n;
 10    char buffer[256];
 11
 12   /* Watch stdin (fd 0) to see when it has input. */
 13   FD_ZERO(&rfds);
 14   FD_SET(0, &rfds);
 15   /* Wait up to five seconds. */
 16   tv.tv_sec = 5;
 17   tv.tv_usec = 0;
 18
 19   retval = select(1, &rfds, NULL, NULL, &tv);
 20
 21   if (retval) {
 22      /* FD_ISSET(0, &rfds) will be true. */
 23       n = read(0,buffer,255);
 24       if (n > 0) {
 25	 buffer[n]='\0';
 26         printf("Data is available: %s\n",buffer);
 27       }
 28       else printf("error on read\n");
 29   }
 30   else
 31       printf("No data within five seconds.\n");
 32   exit(0);
 33 }

Download it here.

Here is a concurrent echo server that uses select.

     1	
     2	/* a single process concurrent server using select */
     3	#include <sys/types.h>
     4	#include <sys/socket.h>
     5	#include <sys/time.h>
     6	#include <netinet/in.h>
     7	#include <unistd.h>
     8	#include <stdio.h>
     9	#define BUFSIZE 1024
    10	
    11	int echo(int), SetupSocket();
    12	void error(char *);
    13	
    14	int main(int argc, char *argv[])
    15	{
    16	     struct sockaddr_in from;
    17	     int msock;       /* master socket */
    18	     fd_set rfds;     /* read file descriptors */
    19	     fd_set afds;     /* active fds */
    20	     int    alen;
    21	     int fd, nfds, n;
    22	
    23	     msock = SetupSocket(); 
    24	
    25	     FD_ZERO(&afds);
    26	     FD_SET(msock,&afds);
    27	     nfds = 4;  /* number of open file descriptors plus 1 */
    28	     while (1) {        
    29	        memcpy((char *)&rfds, (char *)&afds,  sizeof(rfds));
    30	        n = select(nfds,&rfds,NULL, NULL, NULL);
    31	        if (n < 0) error("select");
    32	        if (FD_ISSET(msock, &rfds)) {
    33	             int ssock;
    34	             alen = sizeof(from);
    35	             ssock = accept(msock, (struct sockaddr *)
    36	                     &from,&alen);
    37	             if (ssock < 0) error("accept");
    38	             printf("Accepted a connection, the new socket is %d\n",ssock);
    39	             FD_SET(ssock,&afds);
    40	             nfds++;
    41	        }
    42	        for (fd = 0;fd<nfds;++fd)
    43		  if (fd != msock && FD_ISSET(fd, &rfds)) {
    44		    printf ("The message is on socket %d\n",fd);
    45	                 if (echo(fd) == 0) {
    46	                    close(fd);
    47	                    FD_CLR(fd, &afds);
    48	                 }
    49		  }
    50	      } /* end of while */
    51	}
    52	
    53	/*** echo() ************/
    54	int echo(int fd)
    55	{
    56	   char buf[BUFSIZE]; 
    57	   int n;
    58	   n = read(fd,buf,BUFSIZE);
    59	   if (n < 0) error("read");
    60	   if (n == 0) return 0;
    61	   if (write(1,buf,n) < 0)  error("writing");
    62	   if (write(1,"\n",1) < 0) error("writing");;
    63	   if (write(fd,buf,n) < 0) error("writing");
    64	   return n;
    65	}
    66	
    67	void error(char *msg)
    68	{
    69	    perror(msg);
    70	    exit(0);
    71	}
    72	
    73	/* SetUpSocket() ***
    74	creates a server socket
    75	*/
    76	int SetupSocket()
    77	{
    78	    int sockfd, len;
    79	    struct sockaddr_in server;  
    80	
    81	    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    82	       error("socket");
    83	    server.sin_family=AF_INET;
    84	    server.sin_addr.s_addr=INADDR_ANY;
    85	    server.sin_port=htons(51718);  
    86	    len=sizeof server;
    87	    if (bind(sockfd,(struct sockaddr *)&server,len) < 0) 
    88	        error("binding socket");
    89	    if (listen(sockfd,5)<0)
    90	        error("listen");
    91	    return sockfd;
    92	}
    93	

This is a fully functioning server. It creates a listening socket called msock in the function SetUpSocket. It initializes the active file descriptor set afds and sets the master file descriptor in lines 25 and 26. The while loop copies the active file descriptor set the read file descriptor set and then calls select (line 30). The last argument is NULL, so no timer is set.

The call to select will return under two conditions (or an exception). If the listening socket is set, then a new connection has been accepted and the program calls accept. Accept returns a new file descriptor and this is added to the set of active file descriptors. Otherwise, one of the already active connections has received some data from the client. In this case, the program calls echo, which reads the data, echos it, and closes the socket. When the socket is closed, it is removed from the set of active file descriptors.

Note that the file descriptor set has to be reset each time that select is called.

You can download the program here

What about those other two arguments, writeset and errset? If some of the file descriptors in writeset are set, then select will return when one or more of these are available for writing. Recall that it is possible for a write to a socket to block. Suppose the writer (sender) has written some data and the receiving application has not gotten around to executing a read (recv) so the TCP buffers on the receiver are full, and the receiver is advertising a zero size window. In this case, if the sender tries to write, the write will block until the receiver advertises a non-zero window size.

If some of the file descriptors in errset are set, then select will return if one or more of these has out-of-band data.

Both of these situations are unusual.

A socket is ready for reading when a read operation would not block. There are four such situations:

The select function can be used to solve the two problems which introduced this class. To avoid the deadlock situation in which a receiver is expecting data from its peer and has executed a read, but the peer is also trying to read data, simply set the timer to some reasonable time and return. It would be trivial to modify the first program to read from a socket instead of from stdin. Of course figuring out what to do if the timer goes off may present a different problem.

The select function is available on Win32 as well. Here is a web site that describes its use. and here is the Microsoft Online help page for select

Using alarms to cause a read to time out

Another way to cause a read or recv or recvfrom to return after a set amount of time if no data has appeared is to use the alarm function. Recall that an interrupt is an asynchronous event which can happen at any time. When an interrupt occurs, the processor stops executing instructions in the current running process and executes an interrupt handler function in the kernel. Unix systems have a software interrupt mechanism called signals.

The read, recv, and recvfrom system calls are supposed to return with error EINTR if a signal (interrupt) is received. However on some systems such as FreeBSD the default is to disable this; while on other systems such as Solaris, the default is not to disable this. The action of restarting a read without returning if an interrupt occurred is called a restart. You can control restart behavior with the siginterrupt function. Here is part of the man page for this.

#include <signal.h>

int siginterrupt(int sig, int flag);

DESCRIPTION
The siginterrupt() function is used to change the system call restart
behavior when a system call is interrupted by the specified signal.  If
the flag is false (0), then system calls will be restarted if they are
interrupted by the specified signal and no data has been transferred yet.
System call restart has been the default behavior since 4.2BSD, and is
the default behaviour for signal(3) on FreeBSD.

If the flag is true (1), then restarting of system calls is disabled.  If
a system call is interrupted by the specified signal and no data has been
transferred, the system call will return -1 with the global variable
errno set to EINTR.  Interrupted system calls that have started transfer-
ring data will return the amount of data actually transferred.  System
call interrupt is the signal behavior found on 4.1BSD and AT&T System V
UNIX systems.

If you would like read or recv to return after a period of time rather than be blocked indefinitely if there is nothing to read, and you were too lazy to track all of the complexities of the select function, you can use this feature by setting an alarm.

The alarm(int t) function causes a SIGALRM signal to be sent after t seconds. The default signal handler for SIGALRM is to terminate the program, so you have to write an alternative signal handler, which includes whatever action you want to happen if the alarm goes off.

To set a signal handler for a signal, use the signal function. This takes two arguments, the signal and the name of the function that will be the signal handler.

Here is a UDP server that waits for datagram. If it has not received a datagram within five seconds of starting, it gives up. The interesting lines have this /****/; the other lines are the standard udp server stuff.

/* This program creates a datagram server process 
   in the internet domain on Unix.  It times out
   if no datagram is received within 5 seconds
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <strings.h>
#include <arpa/inet.h> 
#include <stdlib.h> /* for atoi */
#include <signal.h>
#include <unistd.h> /* for alarm */
#include <errno.h>  /* for EINTR */
#define BUFSIZE 1024

void sig_alm(int signo)  /*****/
{
  printf("The alarm went off\n"); /*****/
}
extern int errno;

void error(char *);

int main(int argc, char *argv[])
{
   int sock, length, fromlen, n;
   struct sockaddr_in server;
   struct sockaddr_in from;
   char buf[BUFSIZE];
   char *returnmsg = "I got your message";
      
   if (argc < 2) {
     fprintf(stderr,"usage %s port\n",argv[0]);
     exit(0);
   }

   signal(SIGALRM, sig_alm); /*****/
   siginterrupt(SIGALRM,1);  /*****/  /* turns off restart */
   sock=socket(AF_INET, SOCK_DGRAM, 0);
   if (sock < 0)
       error("Opening socket");
   length = sizeof(server);
   memset(&server,0,length);
   server.sin_family=AF_INET;
   server.sin_addr.s_addr=INADDR_ANY;
   server.sin_port=htons((unsigned short)atoi(argv[1]));
   if (bind(sock,(struct sockaddr *)&server,length)<0) 
       error("binding"); 
   while (1) {
       fromlen = sizeof(from);
       alarm(5);  /*****/
       n = recvfrom(sock,buf,BUFSIZE,0,(struct sockaddr *)&from,&fromlen);
       if (n < 0) {
          if (errno == EINTR) {   /****/
              printf("recvfrom timed out; good bye\n");
              exit(0);              
          }
          else  error("recvfrom");
       }
       buf[n]='\0';
       printf("The message from %s is %s\n",
            inet_ntoa((struct in_addr)from.sin_addr), buf);
       n = sendto(sock,returnmsg,strlen(returnmsg),0,(struct sockaddr *)&from,fromlen);
       if (n < 0) error("sendto");
   }
   return 0;
 }

void error(char *msg)
{
    perror(msg);
    exit(0);
}

Socket Options

There are several functions that can be used to get and set socket options These functions were originally developed for Unix,but available on Windows as well.

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

int getsockopt(int s, int level, int optname, void  *optval, 
         socklen_t *optlen)

int setsockopt(int s, int level, int optname, const void *optval,
          socklen_t optlen);
The first argument is the socket. The second argument level indicates the protocol stack level that the option applies. The following symbolic names can be used.

IPPROTO_TCP (for TCP)
SOL_SOCKET (for general socket stuff, protocol independent)
IPPROTO_IP (for IPv4)
IPPROTO_IPV6 (for IPv6)

The third argument names the option, the fourth gets or sets its value, and the last is the size of the optionval.

Here are some of the options

level IPPROTO_TCP

TCP_MAXSEG
gets or sets the max segment size
TCP_NODELAY
Disables Nagle's Algorithm The Nagle Algorithm reduces the number of small packets on a network. No small packets will be sent until an ack is received. (rlogin and telnet disable this)

SOL_SOCKET

SO_BROADCAST
enables or disables the ability to send broadcast messages (UDP only)
SO_DONTROUTE
specifies that packets should bypass the normal routing table - forces a packet to be sent on a particular interface
SO_ERROR
returns an error number if an error has occurred (can be fetched but not set)
SO_KEEPALIVE
(If no data has been sent for two hours, TCP sends a keepalive probe. If it is ack'ed the connection is kept open, otherwise it is closed. This sets this option.
SO_LINGER
By default, the close function returns immediately, however if this option is set, it waits for a while to see if more data shows up.
SO_RCVBUF and SO_SNDBUF
returns the size of the send and receive buffers and allows the program to change the size.
SO_RCVLOWAT and SO_SNDLOWAT
These allow a program to set and get the receive and send low water marks, the amount of data that has to in the receive buffer for select to return readable. The default is 1. The latter is the amount of space that has to be in the buffer to return writable (default is 2048)
SO_RCVTIMEO and SO_SENDTIMEO
allow us to set a time value for reads and writes
SO_TYPE
returns the type, (SOCK_STREAM, SOCK_DGRAM or whatver)

level IPPROTO_IP

IP_OPTIONS
allows program to set IP options
IP_TOS
lets user set Type of Service
IP_TTL
returns default TTL value

Here is a function that I added to my elementary server program that displays some options. The argument s is an open socket.

void showdefaultvalues(int s)
{
  int optval, retval, optlen;
  
  optlen = sizeof(int);
  retval = getsockopt(s,IPPROTO_TCP,TCP_MAXSEG,&optval,&optlen);
  if (retval==0) printf("Max Segment Size is %d\n",optval);
  else perror("Error getting MSS");
  retval = getsockopt(s,IPPROTO_IP,IP_TTL,&optval,&optlen);
  if (retval==0) printf("TTL is %d\n",optval);
   else perror("Error getting TTL");
  retval = getsockopt(s,SOL_SOCKET,SO_RCVBUF,&optval,&optlen);
  if (retval==0) printf("Receiver Buffer is %d\n",optval);
   else perror("Error getting RCVBUF");
  retval = getsockopt(s,SOL_SOCKET,SO_SNDBUF,&optval,&optlen);
  if (retval==0) printf("Send Buffer is %d\n",optval);
   else perror("Error getting SNDBUF");
}
When I called this function with a listening socket, I got this output.
Max Segment Size is 512
TTL is 64
Receiver Buffer is 65536
Send Buffer is 32768
When I called the function on a socket which had just been returned from accept but before any calls to read or write, I got this result.
Max Segment Size is 1448
TTL is 64
Receiver Buffer is 66608
Send Buffer is 33304
Here is the complete program (Aside: I had to include the header file netinet/tcp.h.)

IPv6 sockets

Everything that we have done with sockets so far has dealt only with IPv4. Our FreeBSD machines also support IPv6. Only a few changes are required to use IPv6 sockets.

1. The domain of the socket (the first argument to socket) is AF_INET6

2. The structure of an IPv6 socket is different.

struct in6_addr {
uint8_t s6_addr[16]; IPv6 address
};


/*
 * IPv6 address
 */
struct in6_addr {
	union {
		u_int8_t   __u6_addr8[16];
		u_int16_t  __u6_addr16[8];
		u_int32_t  __u6_addr32[4];
	} __u6_addr;			/* 128-bit IP6 address */
};

struct sockaddr_in6 {
	u_int8_t	sin6_len;	/* length of this struct(sa_family_t)*/
	u_int8_t	sin6_family;	/* AF_INET6 (sa_family_t) */
	u_int16_t	sin6_port;	/* Transport layer port # (in_port_t)*/
	u_int32_t	sin6_flowinfo;	/* IP6 flow information */
	struct in6_addr	sin6_addr;	/* IP6 address */
	u_int32_t	sin6_scope_id;	/* scope zone index */
};

Here is a simple server. Lines that are different from our earlier server are marked with /***/

/* IPv6server.c - creates an IPv6 TCP server on Unix
   To compile on solaris gcc ... -lnsl -lsocket -lpthread
   To compile on freebsd gcc ... -pthread
*/

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h> 
#include <stdlib.h>
#include <arpa/inet.h>
#include <pthread.h>

#define BUFSIZE 1024

extern int errno;
void error(char *msg)
{
    perror(msg);
    exit(0);
}

int main(int argc, char *argv[])
{
     int sock, *newsock, len, fromlen, retval;
     unsigned short port;
     struct sockaddr_in6 server; /***/
     struct sockaddr_in6 from;  /***/
     pthread_t tid;
     void *ConnectionThread(void *); //function prototype

     if (argc < 2) {
         fprintf(stderr,"usage %s portnumber\n",argv[0]);
         exit(0);
     }
     port = (unsigned short) atoi(argv[1]);
     sock=socket(AF_INET6, SOCK_STREAM, 0); /***/
     if (sock < 0) error("Opening socket");
     server.sin6_family=AF_INET6;   /***/
     server.sin6_addr =in6addr_any; /***/
     server.sin6_port=htons(port);  /***/
     len=sizeof(server);
     if (bind(sock, (struct sockaddr *)&server, len) < 0) 
          error("binding socket");
     fromlen=sizeof(from);
     if (listen(sock,5) < 0) 
          error("listening");;
     while (1) {
         newsock = (int *)malloc(sizeof (int));
         *newsock=accept(sock, (struct sockaddr *)&from, &fromlen);
         if (*newsock < 0) error("Accepting");
	 //         printf("A connection has been accepted from %s\n",
	 //       inet_ntoa((struct in_addr)from.sin_addr));
         printf("Connection accepted\n");
         retval = pthread_create(&tid, NULL, ConnectionThread, (void *)newsock);
         if (retval != 0)   {
            error("Error, could not create thread");
         }
     } 
     return 0; // we never get here 
}

void *ConnectionThread(void *arg)
{
    int sock, n, len;
    char buffer[BUFSIZE];
    char *msg = "Got your message";
  
    sock = *(int *)arg;
    len  = strlen(msg);
    n = read(sock,buffer,BUFSIZE-1);
    while (n > 0) {
        buffer[n]='\0';
        printf("Message is %s\n",buffer);
        n = write(sock,msg,len);
        if (n < len) error("Error writing");
        n = read(sock,buffer,BUFSIZE-1);
        if (n < 0) error("Error reading");
    }
    if (close(sock) < 0) error("closing");
    pthread_exit(NULL);
    return NULL;
}

The changes in the server were pretty trivial

Here is the client. In this example, the server has to be on the same machine (localhost). The IPv6 address for localhost is 0:0:0:0:0:0:0:1.

/* Creates an IPv6 TCP client on Unix */
/* port number is passed in as an arg, server
   must be on local host */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> /* for atoi */

char *msg = "Hello from the client";

void error(char *msg)
{
    perror(msg);
    exit(0);
}

int main(int argc, char *argv[])
{
   int sock, n;
   unsigned short port;
   struct sockaddr_in6 server;
   char buffer[1200];
   
   if (argc != 2) { 
         printf("Usage: %s port\n", argv[0]);
         exit(1);
   }
   sock= socket(AF_INET6, SOCK_STREAM, 0);
   if (sock < 0) error("Opening socket");

   memset(&server,0,sizeof(struct sockaddr_in6));
   server.sin6_family = AF_INET6;
   port = (unsigned short)atoi(argv[1]);
   server.sin6_port = htons(port);
   if (inet_pton(AF_INET6,"0:0:0:0:0:0:0:1",&server.sin6_addr) <= 0)
     error("inet_pton");
   if (connect(sock, (struct sockaddr *)&server, sizeof (server)) < 0)
             error("Connecting");
   n = write(sock, msg, strlen(msg));
   if (n < strlen(msg))
             error("Writing to socket");
   n = read(sock, buffer, 1024);
   if (n < 1) error("reading from socket");
   buffer[n]='\0';
   printf("The message from the server is %s\n",buffer);
   if (close(sock) < 0) error("closing");
   printf("Client terminating\n");
   return 0;
}

The function inet_pton (Internet Presentation to Numeric) converts a dotted decimal IP address to a numeric address.

The getaddrinfo() function

The gethostbyname() function that we have used in earlier clients only works for IPv4 and in fact has been deprecated on some systems. It is to be replaced by getaddrinfo(). Here is the function prototype.

  #include <netdb.h>

  int getaddrinfo(const char hostname, 
                  const char service,
                  const struct addrinfo *hints, 
                  struct addrinfo **result);

  struct addrinfo {
     int ai_flags;
     int ai_family; /* AF_INET, AF_INET6, AI_UNSPEC etc */  
     int ai_socktype; /* SOCK_STREAM, SOCK_DGRAM, etc /*
     int ai_protocol;  /* 0 or IPPROTO_xxx */
     socklen_t ai_addrlen;
     char *ai_canonname;  /* canonical name of server */
     struct sockaddr *ai_addr;
     struct addrinfo *ai_next; /* ptr to next struct 
                                  in linked list */
  };

This function takes a hostname as its first argument. This can either be a string ashley.cs.rpi.edu or a dotted decimal IP address, either IPv4 or IPv6.

The second argument can be a service name or a port.

The third argument, hints, suggests what type of address you are looking for (IPv4 vs IPv6 for example). This will be explained below.

The last argument is a call by result argument; the data that you are looking for is put into this. This is an array of structures (struct addressinfo). The last two fields of this struct constitute a linked list of IP addresses. It is possible for one hostname to have many IP addresses, and this function allows you to potentially get all of them. As a simple example, ashley has both an IPv4 address and an IPv6 address.

This function also supports service to port translation. You can enter the name of a service (ssh for example) and it will provide the port.

The argument hints can be NULL, in which case, all known addresses are returned. For example, if a host has two IP addresses, a total of four addresses will be returned.

The following code is from the FreeBSD man page for getaddrinfo. It tries to connect to "www.kame.net" service "http". via stream socket. It loops through all the addresses available, regardless of the address family. If the destination resolves to an IPv4 address, it will use an AF_INET socket. Similarly, if it resolves to IPv6, an AF_INET6 socket is used. Observe that there is no hardcoded reference to particular address family. The code works even if getaddrinfo returns addresses that are not IPv4/v6.

     1	struct addrinfo hints, *res, *res0; 
     2	nt error; 
     3	int s; 
     4	const char *cause = NULL;
     5	
     6	memset(&hints, 0, sizeof(hints));
     7	hints.ai_family = PF_UNSPEC;
     8	hints.ai_socktype = SOCK_STREAM;
     9	error = getaddrinfo("www.kame.net", "http", &hints, &res0);
    10	if (error) {
    11	        errx(1, "%s", gai_strerror(error));
    12	        /*NOTREACHED*/
    13	}
    14	s = -1;
    15	cause = "no addresses";
    16	errno = EADDRNOTAVAIL;
    17	for (res = res0; res; res = res->ai_next) {
    18	        s = socket(res->ai_family, res->ai_socktype,
    19	            res->ai_protocol);
    20	        if (s < 0) {
    21	                cause = "socket";
    22	                continue;
    23	        }
    24	
    25	
    26	        if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
    27	                cause = "connect";
    28	                close(s);
    29	                s = -1;
    30	                continue;
    31	        }
    32	
    33	
    34	        break;  /* okay we got one */
    35	}
    36	if (s < 0) {
    37	        err(1, cause);
    38	        /*NOTREACHED*/
    39	}
    40	freeaddrinfo(res0);
The first thing that it does is fill in the values of hints. (lines 7 and 8). ai_family is set to PF_UNSPEC, saying that we are looking for any kind of address. If we only wanted IPv4 addresses, we would have set this to PF_INET, and if we only wanted IPv6 addresses, we would have used PF_INET6. The socket type is set to SOCK_STREAM, saying that we want a TCP socket, not a Datagram socket.

We make the actual call to getaddrinfo on line 9 and do the usual error checking. The function gaistrerror prints error messages associated with the getaddrinfo function.

The function then iterates through a loop, calling connect on each entry in the linked list. If connect fails, we continue If connect is successful, we break out of the loop because we have successfully established a connection on socket s.

The function freeaddrinfo() at line 40 frees up the memory of the linked list.

Here is a link to the freeBSD man page for getaddrinfo and Here is the Winsock help page for getaddrinfo