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.
#include "unp.h"
|
int tcp_connect (const char
*hostname, const char
*service);
|
Returns: connected socket descriptor if OK, no
return on error
|
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鈥?3
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鈥?5
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鈥?1
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鈥?5
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
|