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鈥?0
We call fcntl to set the socket to nonblocking.
11鈥?4
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鈥?7
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鈥?4
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鈥?8
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鈥?4
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 鈥? with
errno set to the pending error. Our code handles both
scenarios.
Turn off nonblocking and return
36鈥?2
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 鈥?.
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.
-
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.
-
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.
-
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).
|