Unix Network Programming, Volume 1: The Sockets Networking API (3rd Edition)
11.12 tcp_connect Function
We will now write two functions that use getaddrinfo to handle most scenarios for the TCP clients and servers that we write. The first function, tcp_connect , performs the normal client steps: create a TCP socket and connect to a server.
Figure 11.10 shows the source code. Figure 11.10 tcp_connect function: performs normal client steps.
lib/tcp_connect.c 1 #include "unp.h" 2 int 3 tcp_connect (const char *host, const char *serv) 4 { 5 int sockfd, n; 6 struct addrinfo hints, *res, *ressave; 7 bzero(&hints, sizeof (struct addrinfo)); 8 hints.ai_family = AF_UNSPEC; 9 hints.ai_socktype = SOCK_STREAM; 10 if ( (n = getaddrinfo (host, serv, &hints, &res)) != 0) 11 err_quit("tcp_connect error for %s, %s: %s", 12 host, serv, gai_strerror (n)); 13 ressave = res; 14 do { 15 sockfd = socket (res->ai_family, res->ai_socktype, res->ai_protocol); 16 if (sockfd < 0) 17 continue; /*ignore this one */ 18 if (connect (sockfd, res->ai_addr, res->ai_addrlen) == 0) 19 break; /* success */ 20 Close(sockfd); /* ignore this one */ 21 } while ( (res = res->ai_next) != NULL); 22 if (res == NULL) /* errno set from final connect() */ 23 err_sys ("tcp_connect error for %s, %s", host, serv); 24 freeaddrinfo (ressave); 25 return (sockfd); 26 } Call getaddrinfo
7 “13 getaddrinfo is called once and we specify the address family as AF_UNSPEC and the socket type as SOCK_STREAM. Try each addrinfo structure until success or end of list
14 “25 Each returned IP address is then tried. socket and connect are called. It is not a fatal error for socket to fail, as this could happen if an IPv6 address is returned but the host kernel does not support IPv6. If connect succeeds, a break is made out of the loop. Otherwise, when all the addresses have been tried, the loop also terminates. freeaddrinfo returns all the dynamic memory. This function (and our other functions that provide a simpler interface to getaddrinfo in the following sections) terminates if either getaddrinfo fails or no call to connect succeeds. The only return is upon success. It would be hard to return an error code (one of the EAI_ xxx constants) without adding another argument. This means that our wrapper function is trivial.
int Tcp_connect (const char *host, const char *serv) { return (tcp_connect (host, serv)); } Nevertheless, we still call our wrapper function instead of tcp_connect , to maintain consistency with the remainder of the text. The problem with the return value is that descriptors are non-negative, but we do not know whether the EAI_ xxx values are positive or negative. If these values were positive, we could return the negative of these values if getaddrinfo fails, but we also have to return some other negative value to indicate that all the structures were tried without success. Example: Daytime Client
Figure 11.11 shows our daytime client from Figure 1.5 recoded to use tcp_connect . Figure 11.11 Daytime client recorded to use tcp_connect .
names /daytimetcpcli.c 1 #include "unp.h" 2 int 3 main(int argc, char **argv) 4 { 5 int sockfd, n; 6 char recvline [MAXLINE + 1]; 7 socklen_t len; 8 struct sockaddr_storage ss; 9 if (argc != 3) 10 err_quit 11 ("usage: daytimetcpcli <hostname/IPaddress> <service/port#>"); 12 sockfd = Tcp_connect (argv[1], argv[2]); 13 len = sizeof (ss); 14 Getpeername (sockfd, (SA *) &ss, &len); 15 printf ("connected to %s\n", Sock_ntop_host ((SA *) &ss, len)); 16 while ( (n = Read (sockfd, recvline, MAXLINE)) > 0) { 17 recvline [n] = 0; /* null terminate */ 18 Fputs (recvline, stdout ); 19 } 20 exit (0); 21 } Command-line arguments
9 “11 We now require a second command-line argument to specify either the service name or the port number, which allows our program to connect to other ports. Connect to server
12 All the socket code for this client is now performed by tcp_connect . Print server's address
13 “15 We call getpeername to fetch the server's protocol address and print it. We do this to verify the protocol being used in the examples we are about to show. Note that tcp_connect does not return the size of the socket address structure that was used for the connect . We could have added a pointer argument to return this value, but one design goal for this function was to reduce the number of arguments compared to getaddrinfo . What we do instead is use a sockaddr_storage socket address structure, which is large enough to hold and fulfills the alignment constraints of any socket address type the system supports. This version of our client works with both IPv4 and IPv6, while the version in Figure 1.5 worked only with IPv4 and the version in Figure 1.6 worked only with IPv6. You should also compare our new version with Figure E.12, which we coded to use gethostbyname and getservbyname to support both IPv4 and IPv6. We first specify the name of a host that supports only IPv4.
freebsd % daytimetcpcli linux daytime connected to 206.168.112.96 Sun Jul 27 23:06:24 2003 Next , we specify the name of a host that supports both IPv4 and IPv6.
freebsd % daytimetcpcli aix daytime connected to 3ffe:b80:1f8d:2:204:acff:fe17:bf38 Sun Jul 27 23:17:13 2003 The IPv6 address is used because the host has both a AAAA record and an A record, and as noted in Figure 11.8, since tcp_connect sets the address family to AF_UNSPEC , AAAA records are searched for first, and only if this fails is a search made for an A record. In the next example, we force the use of the IPv4 address by specifying the host-name with our -4 suffix, which we noted in Section 11.2 is our convention for the host-name with only A records.
freebsd % daytimetcpcli aix-4 daytime connected to 192.168.42.2 Sun Jul 27 23:17:48 2003 |