当socket描述符为非阻塞的时候,连接通常不能立即完成,我们调用connect时候Linux平台经常返回-1,errno==EINPROGRESS, windows平台返回-1,WSAGetLastError()==WSAWOULDBLOCK,那么如何知道连接成功了呢:
man connect中表示:
EINPROGRESS
The socket is non-blocking and the connection cannot be completed immediately. It is possible to select(2) or poll(2) for completion by selecting the socket for writing.
After select(2) indicates writability, use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to determine whether connect() completed successfully
我们可以将描述符加入到读集合,使用select或者poll来监控,当出现读事件的时候,使用getsockopt来获取SO_ERROR的值,当值为0的时候表示连接成功了。
msdn select中表示:
- writefds:
- If processing a connect call (nonblocking), connection has succeeded.
- Data can be sent.
- exceptfds:
- If processing a connect call (nonblocking), connection attempt failed.
当然,我并没有验证过,只是搬运工啦,good luck~
有经验的朋友说:
To do a non-blocking connect(), assuming the socket has already been made non-blocking:
int res = connect(fd, ...);
if (res < 0 && errno != EINPROGRESS) {
// error, fail somehow, close socket
return;
}
if (res == 0) {
// connection has succeeded immediately
} else {
// connection attempt is in progress
}
For the second case, where connect() failed with EINPROGRESS (and only in this case), you have to wait for the socket to be writable, e.g. for epoll specify that you're waiting for EPOLLOUT on this socket. Once you get notified that it's writable (with epoll, also expect to get an EPOLLERR or EPOLLHUP event), check the result of the connection attempt:
int result;
socklen_t result_len = sizeof(result);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &result, &result_len) < 0) {
// error, fail somehow, close socket
return;
}
if (result != 0) {
// connection failed; error code is in 'result'
return;
}
// socket is ready for read()/write()
In my experience, on Linux, connect() never immediately succeeds and you always have to wait for writability. However, for example, on FreeBSD, I've seen non-blocking connect() to localhost succeeding right away.
example:
/* * Example on how to work with a non-blocking connect. Uses fixed input and * should show all 3 situations we care about - a successful connect, a refused * connect, and a timeout. * * (c) jp@devnull.cz, vlada@devnull.cz */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/time.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <err.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <strings.h> #include <unistd.h> #define TIMEOUT 10 #define MAX_SOCKETS 3 typedef struct host_s { char *hostname; char *port; int sockets[MAX_SOCKETS]; int numsock; } host_t; host_t hosts[] = { /* This should connect OK. */ { "www.devnull.cz", "80", {-1, -1, -1}, 0 }, /* This will timeout. */ { "www.devnull.cz", "33", {-1, -1, -1}, 0 }, /* This should refuse the connection on both IPv4 and IPv6. */ { "mail.kolej.mff.cuni.cz", "999", {-1, -1, -1}, 0 }, /* To see if localhost gets EINPROGRESS as well. */ { "localhost", "22", {-1, -1, -1}, 0 }, /* * To see if localhost gets EINPROGRESS as well. Connection refusal * might be different from an open port. Port 7 is echo service, * nobody should run it these days. */ { "localhost", "7", {-1, -1, -1}, 0 }, { NULL, 0, {-1, -1, -1}, 0 } }; int main(int argc, char **argv) { socklen_t optlen; struct hostent *he; struct timeval tout; fd_set wrfds, wrfds_orig; int flags, i, optval, n, timeouts, error, num_sockets = 0; struct addrinfo *res, *resorig, hints; host_t *hostp; char ip_str[INET6_ADDRSTRLEN]; memset(&hints, 0, sizeof (hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; printf("will work with these machines/services:\n"); for (hostp = hosts; hostp->hostname != NULL; hostp++) printf(" %s:%s\n", hostp->hostname, hostp->port); printf("making all sockets non-blocking, and calling connect...\n"); FD_ZERO(&wrfds); n = 0; /* counts finished (succeeded/failed) connects */ /* In this cycle we initiate connects to all the hosts. */ for (hostp = hosts; hostp->hostname != NULL; hostp++) { if ((error = getaddrinfo(hostp->hostname, hostp->port, &hints, &res)) != 0) errx(1, "%s", gai_strerror(error)); i = 0; /* One hostname can map to multiple addresses. */ for (resorig = res; res != NULL; res = res->ai_next, i++) { if (res->ai_family != AF_INET && res->ai_family != AF_INET6) continue; /* Convert IP address to string. */ if ((error = getnameinfo(res->ai_addr, res->ai_addrlen, ip_str, sizeof (ip_str), NULL, 0, NI_NUMERICHOST) != 0)) { errx(1, "%s", gai_strerror(error)); } if ((hostp->sockets[i] = socket(res->ai_family, res->ai_socktype, 0)) == -1) { warn("socket() failed for %s", ip_str); continue; } hostp->numsock++; printf(" %s[%s]:%s socket %d\n", hostp->hostname, ip_str, hostp->port, hostp->sockets[i]); flags = fcntl(hostp->sockets[i], F_GETFL); if (fcntl(hostp->sockets[i], F_SETFL, flags | O_NONBLOCK) == -1) err(1, "fcntl"); if (connect(hostp->sockets[i], (struct sockaddr *)res->ai_addr, res->ai_addrlen) == -1) { /* This is what we expect. */ if (errno == EINPROGRESS) { printf(" connect EINPROGRESS OK " "(expected)\n"); FD_SET(hostp->sockets[i], &wrfds); } else { /* * This may happen right here, on * localhost for example (immediate * connection refused). * I can see that happen on FreeBSD * but not on Solaris, for example. */ printf(" connect: %s\n", strerror(errno)); ++n; } } else { /* This may happen, on localhost for example */ printf(" %s connected OK on port %s\n", hostp->hostname, hostp->port); ++n; } num_sockets++; } freeaddrinfo(resorig); } /* Save our original fd set. */ wrfds_orig = wrfds; /* Let's print some progress every second. */ tout.tv_sec = 1; tout.tv_usec = 0; printf("continuing with select now, checking the connect status...\n"); timeouts = 0; while (n != num_sockets) { /* * Note that non-blocking connect uses the WR set, not the RD * one. */ error = select(FD_SETSIZE, NULL, &wrfds, NULL, &tout); if (error == 0) { if (++timeouts == TIMEOUT) { printf(" TIMEOUT REACHED\n"); break; } printf(" .\n"); /* * Select may change the timeval structure (see the * spec) so reset it to original values. */ tout.tv_sec = 1; tout.tv_usec = 0; continue; } else { if (error == -1) err(1, "select"); } /* Check all hosts. */ for (hostp = hosts; hostp->hostname != NULL; hostp++) { /* Check all sockets for this host. */ for (i = 0; i < hostp->numsock; i++) { if (!FD_ISSET(hostp->sockets[i], &wrfds)) continue; optval = -1; optlen = sizeof (optval); if (getsockopt(hostp->sockets[i], SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1) err(1, "getsockopt"); /* * getsockopt() puts the errno value * for connect into optval so 0 means * no-error. */ if (optval == 0) { printf(" %s connected OK on " "port %s socket %d\n", hostp->hostname, hostp->port, hostp->sockets[i]); } else { printf(" %s connect failed on " "port %s socket %d (%s)\n", hostp->hostname, hostp->port, hostp->sockets[i], strerror(optval)); } /* no longer care about this socket */ FD_CLR(hostp->sockets[i], &wrfds_orig); close(hostp->sockets[i]); ++n; } } /* the shrinking set of sockets we still care about */ wrfds = wrfds_orig; } /* those remaining in the wrfds set are those that timed out on us */ for (hostp = hosts; hostp->hostname != NULL; hostp++) { for (i = 0; i < hostp->numsock; i++) { if (FD_ISSET(hostp->sockets[i], &wrfds_orig)) printf(" %s timed out on port %s socket %d\n", hostp->hostname, hostp->port, hostp->sockets[i]); } } return (0); }