11.13 tcp_listen
Function
Our next function, tcp_listen, performs
the normal TCP server steps: create a TCP socket, bind the
server's well-known port, and allow incoming connection requests to
be accepted. Figure 11.12
shows the source code.
#include "unp.h"
|
int tcp_listen (const char
*hostname, const char
*service, socklen_t
*addrlenp);
|
Returns: connected socket descriptor if OK, no
return on error
|
Call getaddrinfo
8鈥?5
We initialize an addrinfo structure with our hints:
AI_PASSIVE, since this function is for a server,
AF_UNSPEC for the address family, and
SOCK_STREAM. Recall from Figure 11.8 that if a
hostname is not specified (which is common for a server that wants
to bind the wildcard address), the AI_PASSIVE and
AF_UNSPEC hints will cause two socket address structures
to be returned: the first for IPv6 and the next for IPv4 (assuming
a dual-stack host).
Create socket and bind address
16鈥?5
The socket and bind functions are called. If
either call fails, we just ignore this addrinfo structure
and move on to the next one. As stated in Section 7.5, we
always set th猫 SO_REUSEADDR socket option for a TCP
server.
Check for failure
26鈥?7
If all the calls to socket and bind fail, we
print an error and terminate. As with our tcp_connect
function in the previous section, we do not try to return an error
from this function.
28 The
socket is turned into a listening socket by listen.
Return size of socket address
structure
29鈥?2
If the addrlenp argument is
non-null, we return the size of the protocol addresses through this
pointer. This allows the caller to allocate memory for a socket
address structure to obtain the client's protocol address from
accept. (See Exercise 11.7
also.)
Example: Daytime Server
Figure
11.13 shows our daytime server from Figure 4.11, recoded
to use tcp_listen.
Require service name or port number as
command-line argument
11鈥?2
We require a command-line argument to specify either the service
name or port number. This makes it easier to test our server, since
binding port 13 for the daytime server requires superuser
privileges.
Create listening socket
13
tcp_listen creates the listening socket. We pass a NULL
pointer as the third argument because we don't care what size
address structure the address family uses; we will use
sockaddr_storage.
Figure 11.12
tcp_listen function: performs normal server steps.
lib/tcp_listen.c
1 #include "unp.h"
2 int
3 tcp_listen(const char *host, const char *serv, socklen_t *addrlenp)
4 {
5 int listenfd, n;
6 const int on = 1;
7 struct addrinfo hints, *res, *ressave;
8 bzero(&hints, sizeof (struct addrinfo)) ;
9 hints.ai_flags = AI_PASSIVE;
10 hints.ai_family = AF_UNSPEC;
11 hints.ai_socktype = SOCK_STREAM;
12 if ( (n = getaddrinfo (host, serv, &hints, &res)) != 0)
13 err_quit("tcp_listen error for %s, %s: %s",
14 host, serv, gai_strerror(n)) ;
15 ressave = res;
16 do {
17 listenfd =
18 socket(res->ai_family, res->ai_socktype, res->ai_protocol);
19 if (listenfd < 0)
20 continue; /* error, try next one */
21 Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on) ) ;
22 if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
23 break; /* success */
24 Close (listenfd); /* bind error, close and try next one */
25 } while ( (res = res->ai_next) != NULL);
26 if (res == NULL) /* errno from final socket () or bind () */
27 err_sys ("tcp_listen error for %s, %s", host, serv);
28 Listen (listenfd, LISTENQ);
29 if (addrlenp)
30 *addrlenp = res->ai_addrlen; /* return size of protocol address */
31 freeaddrinfo (ressave);
32 return (listenfd);
33 }
Server loop
14鈥?2
accept waits for each client connection. We print the
client address by calling sock_ntop. In the case of either
IPv4 or IPv6, this function prints the IP address and port number.
We could use the function getnameinfo (Section
11.17) to try to obtain the hostname of the client, but that
involves a PTR query in the DNS, which can take some time,
especially if the PTR query fails. Section 14.8 of TCPv3 notes that
on a busy Web server, almost 25% of all clients connecting to that
server did not have PTR records in the DNS. Since we do not want a
server (especially an iterative server) to wait seconds for a PTR
query, we just print the IP address and port.
Figure 11.13
Daytime server recoded to use tcp_listen (see also
Figure 11.14).
names/daytimetcpsrv1.c
1 #include "unp.h"
2 #include <time.h>
3 int
4 main(int argc, char **argv)
5 {
6 int listenfd, connfd;
7 socklen_t len;
8 char buff[MAXLINE];
9 time_t ticks;
10 struct sockaddr_storage cliaddr;
11 if (argc != 2)
12 err_quit("usage: daytimetcpsrv1 <service or port#>");
13 listenfd = Tcp_listen (NULL, argv[1], NULL);
14 for ( ; ; ) {
15 len = sizeof (cliaddr);
16 connfd = Accept (listenfd, (SA *) &cliaddr, &len);
17 printf("connection from %s\n", Sock_ntop ( (SA *) &cliaddr, len) );
18 ticks = time (NULL);
19 snprintf(buff, sizeof (buff), "%.24s\r\n", ctime (&ticks) ) ;
20 Write(connfd, buff, strlen (buff) ) ;
21 Close (connfd);
22 }
23 }
Example: Daytime Server with Protocol
Specification
There is a slight problem with Figure 11.13: The first argument to
tcp_listen is a null pointer, which combined with the
address family of AF_UNSPEC that tcp_listen
specifies might cause getaddrinfo to return a socket
address structure with an address family other than what is
desired. For example, the first socket address structure returned
will be for IPv6 on a dual-stack host Figure 11.8, but we
might want our server to handle only IPv4.
Clients do not have this problem since the
client must always specify either an IP address or a hostname.
Client applications normally allow the user to enter this as a
command-line argument. This gives us the opportunity to specify a
hostname that is associated with a particular type of IP address
(recall our -4 and -6 hostnames in Section
11.2), or to specify either an IPv4 dotted-decimal string
(forcing IPv4) or an IPv6 hex string (forcing IPv6).
But there is a simple technique for servers that
lets us force a given protocol on a server, either IPv4 or IPv6:
Allow the user to enter either an IP address or a hostname as a
command-line argument to the program and pass this to
getaddrinfo. In the case of an IP address, an IPv4
dotted-decimal string differs from an IPv6 hex string. The
following calls to inet_pton either fail or succeed, as
indicated:
inet_pton (AF_INET, "0.0.0.0", &foo); /* succeeds */
inet_pton (AF_INET, "0::0", &foo); /* fails */
inet_pton (AF_INET6, "0.0.0.0", &foo); /* fails */
inet_pton (AF_INET6, "0::0", &foo); /* succeeds */
Therefore, if we change our servers to accept an
optional argument, and if we enter
% server
it defaults to IPv6 on a dual-stack host, but
entering
% server 0.0.0.0
explicitly specifies IPv4 and
% server 0::0
explicitly specifies IPv6.
Figure
11.14 shows this final version of our daytime server.
Handle command-line arguments
11鈥?6
The only change from Figure
11.13 is the handling of the command-line arguments, allowing
the user to specify either a hostname or an IP address for the
server to bind, in addition to a service name or port.
We first start this server with an IPv4 socket
and then connect to the server from clients on two other hosts on
the local subnet.
freebsd % daytimetcpsrv2 0.0.0.0 9999
connection from 192.168.42.2:32961
connection from 192.168.42.3:1389
Now we start the server with an IPv6 socket.
freebsd % daytimetcpsrv2 0: :0 9999
c贸nnection from [3ffe:b80:1f8d:2:204:acff:fe17:bf38]:32964
connection from [3ffe:b80:1f8d:2:230:65ff:fe15:caa7]:49601
connection from [::ffff:192.168.42.2]:32967
connection from [::ffff:192.168.42.3]:49602
The first connection is from the host
aix using IPv6 and the second is from the host
macosx using IPv6. The next two connections are from the
hosts aix and macosx, but using IPv4, not IPv6.
We can tell this because the client's addresses returned by
accept are both IPv4-mapped IPv6 addresses.
What we have just shown is that an IPv6 server
running on a dual-stack host can handle either IPv4 or IPv6
clients. The IPv4 client addresses are passed to the IPv6 server as
IPv4-mapped IPv6 addresses, as we will discuss in Section
12.2.
Figure 11.14
Protocol-independent daytime server that uses
tcp_listen.
names/daytimetcpsrv2.c
1 #include "unp.h"
2 #include <time.h>
3 int
4 main (int argc, char **argv)
5 {
6 int listenfd, connfd;
7 socklen_t len;
8 char buff [MAXLINE];
9 time_t ticks;
10 struct sockaddr_storage cliaddr;
11 if (argc == 2)
12 listenfd = Tcp_listen (NULL, argv [1], &addrlen);
13 else if (argc == 3)
14 listenfd = Tcp_listen (argv [1], argv[2], &addrlen);
15 else
16 err_quit ("usage: daytimetcpsrv2 [ <host> ] <service or port>");
17 for ( ; ; ) {
18 len = sizeof (cliaddr);
19 connfd = Accept (listenfd, (SA *) &cliaddr, &len);
20 printf ("connection from %s\n", Sock_ntop ((SA *) &cliaddr, len) ) ;
21 ticks = time (NULL);
22 snprintf (buff, sizeof (buff), "%.24s\r\n", ctime (&ticks) ) ;
23 Write (connfd, buff, strlen (buff) ) ;
24 Close (connfd);
25 }
26 }
|