[ Team LiB ] Previous Section Next Section

16.4 Nonblocking connect: Daytime Client

Figure 16.11 shows our function connect_nonb, which performs a nonblocking connect. We replace the call to connect in Figure 1.5 with


if (connect_nonb(sockfd, (SA *) &servaddr, sizeof(servaddr), 0) < 0)
    err_sys("connect error");

The first three arguments are the normal arguments to connect, and the fourth argument is the number of seconds to wait for the connection to complete. A value of 0 implies no timeout on the select; hence, the kernel will use its normal TCP connection establishment timeout.

Set socket nonblocking

9–10 We call fcntl to set the socket to nonblocking.

11–14 We initiate the nonblocking connect. The error we expect is EINPROGRESS, indicating that the connection has started, but is not yet complete (p. 466 of TCPv2). Any other error is returned to the caller.

Overlap processing with connection establishment

15 At this point, we can do whatever we want while we wait for the connection to complete.

Check for immediate completion

16–17 If the nonblocking connect returns 0, the connection is complete. As we have said, this can occur when the server is on the same host as the client.

Call select

18–24 We call select and wait for the socket to be ready for either reading or writing. We zero out rset, turn on the bit corresponding to sockfd in this descriptor set, and then copy rset into wset. This assignment is probably a structure assignment since descriptor sets are normally represented as structures. We also initialize the timeval structure and then call select. If the caller specifies a fourth argument of 0 (uses the default timeout), we must specify a null pointer as the final argument to select and not a timeval structure with a value of 0 (which means do not wait at all).

Handle timeouts

25–28 If select returns 0, the timer expired, and we return ETIMEDOUT to the caller. We also close the socket, to prevent the three-way handshake from proceeding any further.

Figure 16.11 Issue a nonblocking connect.

lib/connect_nonb.c

 1 #include     "unp.h"

 2 int
 3 connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
 4 {
 5     int     flags, n, error;
 6     socklen_t len;
 7     fd_set rset, wset;
 8     struct timeval tval;

 9     flags = Fcntl(sockfd, F_GETFL, 0);
10     Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

11     error = 0;
12     if ( (n = connect(sockfd, saptr, salen)) < 0)
13         if (errno != EINPROGRESS)
14             return (-1);

15     /* Do whatever we want while the connect is taking place. */

16     if (n == 0)
17         goto done;               /* connect completed immediately */

18     FD_ZERO(&rset);
19     FD_SET(sockfd, &rset);
20     wset = rset;
21     tval.tv_sec = nsec;
22     tval.tv_usec = 0;

23     if ( (n = Select(sockfd + 1, &rset, &wset, NULL,
24                     nsec ? &tval : NULL)) == 0) {
25         close(sockfd);          /* timeout */
26         errno = ETIMEDOUT;
27         return (-1);
28     }

29     if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
30         len = sizeof(error);
31         if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
32             return (-1);     /* Solaris pending error */
33     } else
34         err_quit("select error: sockfd not set");

35   done:
36     Fcntl(sockfd, F_SETFL, flags);  /* restore file status flags */

37     if (error) {
38         close(sockfd);           /* just in case */
39         errno = error;
40         return (-1);
41     }
42     return (0);
43 }

Check for readability or writability

29–34 If the descriptor is readable or writable, we call getsockopt to fetch the socket's pending error (SO_ERROR). If the connection completed successfully, this value will be 0. If the connection encountered an error, this value is the errno value corresponding to the connection error (e.g., ECONNREFUSED, ETIMEDOUT, etc.). We also encounter our first portability problem. If an error occurred, Berkeley-derived implementations of getsockopt return 0 with the pending error returned in our variable error. But Solaris causes getsockopt itself to return –1 with errno set to the pending error. Our code handles both scenarios.

Turn off nonblocking and return

36–42 We restore the file status flags and return. If our error variable is nonzero from getsockopt, that value is stored in errno and the function returns –1.

As we said earlier, there are portability problems with various socket implementations and nonblocking connects. First, it is possible for a connection to complete and for data to arrive from a peer before select is called. In this case, the socket will be both readable and writable on success, the same as if the connection had failed. Our code in Figure 16.11 handles this scenario by calling getsockopt and checking the pending error for the socket.

Next is determining whether the connection completed successfully or not, if we cannot assume that writability is the only way success is returned. Various solutions have been posted to Usenet. These would replace our call to getsockopt in Figure 16.11.

  1. Call getpeername instead of getsockopt. If this fails with ENOTCONN, the connection failed and we must then call getsockopt with SO_ERROR to fetch the pending error for the socket.

  2. Call read with a length of 0. If the read fails, the connect failed and the errno from read indicates the reason for the connection failure. When a connection succeeds, read should return 0.

  3. Call connect again. It should fail, and if the error is EISCONN, the socket is already connected and the first connection succeeded.

Unfortunately, nonblocking connects are one of the most nonportable areas of network programming. Be prepared for portability problems, especially with older implementations. A simpler technique is to create a thread (Chapter 26) to handle a connection.

Interrupted connect

What happens if our call to connect on a normal blocking socket is interrupted, say, by a caught signal, before TCP's three-way handshake completes? Assuming the connect is not automatically restarted, it returns EINTR. But, we cannot call connect again to wait for the connection to complete. Doing so will return EADDRINUSE.

What we must do in this scenario is call select, just as we have done in this section for a nonblocking connect. select returns when the connection completes successfully (making the socket writable) or when the connection fails (making the socket readable and writable).

    [ Team LiB ] Previous Section Next Section