[ Team LiB ] |
4.6 accept Functionaccept is called by a TCP server to return the next completed connection from the front of the completed connection queue (Figure 4.7). If the completed connection queue is empty, the process is put to sleep (assuming the default of a blocking socket).
The cliaddr and addrlen arguments are used to return the protocol address of the connected peer process (the client). addrlen is a value-result argument (Section 3.3): Before the call, we set the integer value referenced by *addrlen to the size of the socket address structure pointed to by cliaddr; on return, this integer value contains the actual number of bytes stored by the kernel in the socket address structure. If accept is successful, its return value is a brand-new descriptor automatically created by the kernel. This new descriptor refers to the TCP connection with the client. When discussing accept, we call the first argument to accept the listening socket (the descriptor created by socket and then used as the first argument to both bind and listen), and we call the return value from accept the connected socket. It is important to differentiate between these two sockets. A given server normally creates only one listening socket, which then exists for the lifetime of the server. The kernel creates one connected socket for each client connection that is accepted (i.e., for which the TCP three-way handshake completes). When the server is finished serving a given client, the connected socket is closed. This function returns up to three values: an integer return code that is either a new socket descriptor or an error indication, the protocol address of the client process (through the cliaddr pointer), and the size of this address (through the addrlen pointer). If we are not interested in having the protocol address of the client returned, we set both cliaddr and addrlen to null pointers. Figure 1.9 shows these points. The connected socket is closed each time through the loop, but the listening socket remains open for the life of the server. We also see that the second and third arguments to accept are null pointers, since we were not interested in the identity of the client. Example: Value-Result ArgumentsWe will now show how to handle the value-result argument to accept by modifying the code from Figure 1.9 to print the IP address and port of the client. We show this in Figure 4.11. Figure 4.11 Daytime server that prints client IP address and portintro/daytimetcpsrv1.c 1 #include "unp.h" 2 2 #include <time.h> 3 int 4 main(int argc, char **argv) 5 { 6 int listenfd, connfd; 7 socklen_t len; 8 struct sockaddr_in servaddr, cliaddr; 9 char buff[MAXLINE]; 10 time_t ticks; 11 listenfd = Socket(AF_INET, SOCK_STREAM, 0); 12 bzero(&servaddr, sizeof(servaddr)); 13 servaddr.sin_family = AF_INET; 14 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 15 servaddr.sin_port = htons(13); /* daytime server */ 16 Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); 17 Listen(listenfd, LISTENQ); 18 for ( ; ; ) { 19 len = sizeof(cliaddr); 20 connfd = Accept(listenfd, (SA *) &cliaddr, &len); 21 printf("connection from %s, port %d\n", 22 Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)), 23 ntohs(cliaddr.sin_port)); 24 ticks = time(NULL); 25 snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); 26 Write(connfd, buff, strlen(buff)); 27 Close(connfd); 28 } 29 } New declarations7–8 We define two new variables: len, which will be a value-result variable, and cliaddr, which will contain the client's protocol address. Accept connection and print client's address19–23 We initialize len to the size of the socket address structure and pass a pointer to the cliaddr structure and a pointer to len as the second and third arguments to accept. We call inet_ntop (Section 3.7) to convert the 32-bit IP address in the socket address structure to a dotted-decimal ASCII string and call ntohs (Section 3.4) to convert the 16-bit port number from network byte order to host byte order.
If we run our new server and then run our client on the same host, connecting to our server twice in a row, we have the following output from the client: solaris % daytimetcpcli 127.0.0.1 Thu Sep 11 12:44:00 2003 solaris % daytimetcpcli 192.168.1.20 Thu Sep 11 12:44:09 2003 We first specify the server's IP address as the loopback address (127.0.0.1) and then as its own IP address (192.168.1.20). Here is the corresponding server output:
solaris # daytimetcpsrv1
connection from 127.0.0.1, port 43388
connection from 192.168.1.20, port 43389
Notice what happens with the client's IP address. Since our daytime client (Figure 1.5) does not call bind, we said in Section 4.4 that the kernel chooses the source IP address based on the outgoing interface that is used. In the first case, the kernel sets the source IP address to the loopback address; in the second case, it sets the address to the IP address of the Ethernet interface. We can also see in this example that the ephemeral port chosen by the Solaris kernel is 43388, and then 43389 (recall Figure 2.10). As a final point, our shell prompt for the server script changes to the pound sign (#), the commonly used prompt for the superuser. Our server must run with superuser privileges to bind the reserved port of 13. If we do not have superuser privileges, the call to bind will fail:
solaris % daytimetcpsrv1
bind error: Permission denied
|
[ Team LiB ] |