[ Team LiB ] |
16.6 Nonblocking acceptWe stated in Chapter 6 that a listening socket is returned as readable by select when a completed connection is ready to be accepted. Therefore, if we are using select to wait for incoming connections, we should not need to set the listening socket to nonblocking because if select tells us that the connection is ready, accept should not block. Unfortunately, there is a timing problem that can trip us up here [Gierth 1996]. To see this problem, we modify our TCP echo client (Figure 5.4) to establish the connection and then send an RST to the server. Figure 16.21 shows this new version. Figure 16.21 TCP echo client that creates connection and sends an RST.nonblock/tcpcli03.c 1 #include "unp.h" 2 int 3 main(int argc, char **argv) 4 { 5 int sockfd; 6 struct linger ling; 7 struct sockaddr_in servaddr; 8 if (argc != 2) 9 err_quit("usage: tcpcli <IPaddress>"); 10 sockfd = Socket(AF_INET, SOCK_STREAM, 0); 11 bzero(&servaddr, sizeof(servaddr)); 12 servaddr.sin_family = AF_INET; 13 servaddr.sin_port = htons(SERV_PORT); 14 Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); 15 Connect(sockfd, (SA *) &servaddr, sizeof(servaddr)); 16 ling.l_onoff = 1; /* cause RST to be sent on close() */ 17 ling.l_linger = 0; 18 Setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); 19 Close(sockfd); 20 exit(0); 21 } Set SO_LINGER socket option16–19 Once the connection is established, we set the SO_LINGER socket option, setting the l_onoff flag to 1 and the l_linger time to 0. As stated in Section 7.5, this causes an RST to be sent on a TCP socket when the connection is closed. We then close the socket. Next, we modify our TCP server from Figures 6.21 and 6.22 to pause after select returns that the listening socket is readable, but before calling accept. In the following code from the beginning of Figure 6.22, the two lines preceded by a plus sign are new: if (FD_ISSET(listenfd, &rset)) { /* new client connection */ + printf("listening socket readable\n"); + sleep(5); clilen = sizeof(cliaddr); connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); What we are simulating here is a busy server that cannot call accept as soon as select returns that the listening socket is readable. Normally, this slowness on the part of the server is not a problem (indeed, this is why a queue of completed connections is maintained), but when combined with the RST from the client, after the connection is established, we can have a problem. In Section 5.11, we noted that when the client aborts the connection before the server calls accept, Berkeley-derived implementations do not return the aborted connection to the server, while other implementations should return ECONNABORTED but often return EPROTO instead. Consider the following example of a Berkeley-derived implementation:
The server will remain blocked in the call to accept until some other client establishes a connection. But in the meantime, assuming a server like Figure 6.22, the server is blocked in the call to accept and will not handle any other ready descriptors.
The fix for this problem is as follows:
|
[ Team LiB ] |