11.5 getservbyname and
getservbyport Functions
Services, like hosts, are often known by names,
too. If we refer to a service by its name in our code, instead of
by its port number, and if the mapping from the name to port number
is contained in a file (normally /etc/services), then if
the port number changes, all we need to modify is one line in the
/etc/services file instead of having to recompile the
applications. The next function, getservbyname, looks up a
service given its name.
The canonical list of port numbers assigned to
services is maintained by the IANA at http://www.iana.org/assignments/port-numbers
(Section 2.9). A
given /etc/services file is likely to contain a subset of
the IANA assignments.
#include <netdb.h>
|
struct servent *getservbyname (const char
*servname, const char
*protoname);
|
Returns: non-null pointer if OK, NULL
on error
|
This function returns a pointer to the following
structure:
struct servent {
char *s_name; /* official service name */
char **s_aliases; /* alias list */
int s-port; /* port number, network-byte order */
char *s_proto; /* protocol to use */
};
The service name servname must be specified. If a protocol is
also specified (protoname is a
non-null pointer), then the entry must also have a matching
protocol. Some Internet services are provided using either TCP or
UDP (for example, the DNS and all the services in Figure
2.18), while others support only a single protocol (e.g., FTP
requires TCP). If protoname is not
specified and the service supports multiple protocols, it is
implementation-dependent as to which port number is returned.
Normally this does not matter, because services that support
multiple protocols often use the same TCP and UDP port number, but
this is not guaranteed.
The main field of interest in the
servent structure is the port number. Since the port
number is returned in network byte order, we must not call
htons when storing this into a socket address
structure.
Typical calls to this function could be as
follows:
struct servent *sptr;
sptr = getservbyname("domain", "udp"); /* DNS using UDP */
sptr = getservbyname("ftp", "tcp"); /* FTP using TCP */
sptr = getservbyname("ftp", NULL); /* FTP using TCP */
sptr = getservbyname("ftp", "udp"); /* this call will fail */
Since FTP supports only TCP, the second and
third calls are the same, and the fourth call will fail. Typical
lines from the /etc/services file are
freebsd % grep -e ^ftp -e ^domain /etc/services
ftp-data 20/tcp #File Transfer [Default Data]
ftp 21/tcp #File Transfer [Control]
domain 53/tcp #Domain Name Server
domain 53/udp #Domain Name Server
ftp-agent 574/tcp #FTP Software Agent System
ftp-agent 574/udp #FTP Software Agent System
ftps-data 989/tcp # ftp protocol, data, over TLS/SSL
ftps 990/tcp # ftp protocol, control, over TLS/SSL
The next function, getservbyport, looks
up a service given its port number and an optional protocol.
#include <netdb.h>
|
struct servent *getservbyport (int
port, const char
*protoname);
|
Returns: non-null pointer if OK, NULL
on error
|
The port value
must be network byte ordered. Typical calls to this function could
be as follows:
struct servent *sptr;
sptr = getservbyport (htons (53), "udp"); /* DNS using UDP */
sptr = getservbyport (htons (21), "tcp"); /* FTP using TCP */
sptr = getservbyport (htons (21), NULL); /* FTP using TCP */
sptr = getservbyport (htons (21), "udp"); /* this call will fail */
The last call fails because there is no service
that uses port 21 with UDP.
Be aware that a few port numbers are used with
TCP for one service, but the same port number is used with UDP for
a totally different service. For example, the following:
freebsd % grep 514 /etc/services
shell 514/tcp cmd #like exec, but automatic
syslog 514/udp
shows that port 514 is used by the rsh
command with TCP, but with the syslog daemon with UDP.
Ports 512鈥?14 have this property.
Example: Using gethostbyname
and getservbyname
We can now modify our TCP daytime client from
Figure 1.5 to use
gethostbyname and getservbyname and take two
command-line arguments: a hostname and a service name. Figure 11.4 shows our program. This
program also shows the desired behavior of attempting to connect to
all the IP addresses for a multihomed server, until one succeeds or
all the addresses have been tried.
Figure 11.4 Our
daytime client that uses gethostbyname and
getservbyname.
names/daytimetcpcli1.c
1 #include "unp.h"
2 int
3 main (int argc, char **argv)
4 {
5 int sockfd, n;
6 char recvline [MAXLINE + 1];
7 struct sockaddr_in servaddr;
8 struct in_addr **pptr;
9 struct in_addr *inetaddrp [2];
10 struct in_addr inetaddr;
11 struct hostent *hp;
12 struct servent *sp;
13 if (argc ! = 3)
14 err_quit ("usage: daytimetcpclil <hostname> <service>");
15 if ( (hp = gethostbyname (argv [1]) ) == NULL) {
16 if (inet_aton (argv [1], &inetaddr) == 0) {
17 err_quit ("hostname error for %s: %s", argv [1],
18 hstrerror (h_errno) );
19 } else {
20 inetaddrp [0] = &inetaddr;
21 inetaddrp [1] = NULL;
22 pptr = inetaddrp;
23 }
24 } else {
25 pptr = (struct in_addr **) hp->h_addr_list;
26 }
27 if ( (sp = getservbyname (argv [2], "tcp") ) == NULL)
28 err_quit ("getservbyname error for %s", argv [2] );
29 for ( ; *pptr != NULL; pptr++) {
30 sockfd = Socket (AF_INET, SOCK_STREAM, 0) ;
31 bzero (&servaddr, sizeof (servaddr) ) ;
32 servaddr.sin_family = AF_INET;
33 servaddr.sin_port = sp->s_port;
34 memcpy (&servaddr.sin_addr, *pptr, sizeof (struct in_addr) ) ;
35 printf ("trying %s\n", Sock_ntop ( (SA *) &servaddr, sizeof (servaddr) ) ) ;
36 if (connect (sockfd, (SA *) &servaddr, sizeof (servaddr) ) == 0)
37 break; /* success */
38 err_ret ("connect error");
39 close (sockfd) ;
40 }
41 if (*pptr == NULL)
42 err_quit ("unable to connect");
43 while ( (n = Read (sockfd, recvline, MAXLINE) ) > 0) {
44 recvline [n] = 0; /* null terminate */
45 Fputs (recvline, stdout);
46 }
47 exit (0);
48 }
Call gethostbyname and
getservbyname
13鈥?8
The first command-line argument is a hostname, which we pass as an
argument to gethostbyname, and the second is a service
name, which we pass as an argument to getservbyname. Our
code assumes TCP, and that is what we use as the second argument to
getservbyname. If gethostbyname fails to look up
the name, we try using the inet_aton function (Section
3.6) to see if the argument was an ASCII-format address. If it
was, we construct a single-element list consisting of the
corresponding address.
Try each server address
29鈥?5
We now code the calls to socket and connect in a
loop that is executed for every server address until a
connect succeeds or the list of IP addresses is exhausted.
After calling socket, we fill in an Internet socket
address structure with the IP address and port of the server. While
we could move the call to bzero and the subsequent two
assignments out of the loop, for efficiency, the code is easier to
read as shown. Establishing the connection with the server is
rarely a performance bottleneck for a network client.
Call connect
36鈥?9
connect is called, and if it succeeds, break
terminates the loop. If the connection establishment fails, we
print an error and close the socket. Recall that a descriptor that
fails a call to connect must be closed and is no longer
usable.
Check for failure
41鈥?2
If the loop terminates because no call to connect
succeeded, the program terminates.
Read server's reply
43鈥?7
Otherwise, we read the server's response, terminating when the
server closes the connection.
If we run this program specifying one of our
hosts that is running the daytime server, we get the expected
output.
freebsd % daytimetcpcli1 aix daytime
trying 192.168.42.2:13
Sun Jul 27 22:44:19 2003
What is more interesting is to run the program
to a multihomed system that is not running the daytime server.
freebsd % daytimetcpcli1 gateway.tuc.noao.edu daytime
trying 140.252.108.1:13
connect error: Operation timed out
trying 140.252.1.4:13
connect error: Operation timed out
trying 140.252.104.1:13
connect error: Connection refused
unable to connect
|