Unix Network Programming, Volume 1: The Sockets Networking API (3rd Edition)
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.
Call getaddrinfo
8 “15 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 “25 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 “27 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 “32 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 “12 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 “22 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 “16 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 } |