socket非阻塞connect

当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.
当使用select的时候,出现读事件时候,表示连接成功,当出现异常事件时候,表示连接失败了。



当然,我并没有验证过,只是搬运工啦,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);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值