步骤1: 设置非阻塞,启动连接
实现非阻塞 connect ,首先把 sockfd 设置成非阻塞的。这样调用
connect 可以立刻返回,根据返回值和 errno 处理三种情况:
(1) 如果返回 0,表示 connect 成功。
(2) 如果返回值小于 0, errno 为 EINPROGRESS, 表示连接
建立已经启动但是尚未完成。这是期望的结果,不是真正的错误。
(3) 如果返回值小于0,errno 不是 EINPROGRESS,则连接出错了。
步骤2:判断可读和可写
然后把 sockfd 加入 select 的读写监听集合,通过 select 判断 sockfd
是否可写,处理三种情况:
(1) 如果连接建立好了,对方没有数据到达,那么 sockfd 是可写的
(2) 如果在 select 之前,连接就建立好了,而且对方的数据已到达,
那么 sockfd 是可读和可写的。
(3) 如果连接发生错误,sockfd 也是可读和可写的。
判断 connect 是否成功,就得区别 (2) 和 (3),这两种情况下 sockfd 都是
可读和可写的,区分的方法是,调用 getsockopt 检查是否出错。
步骤3:使用 getsockopt 函数检查错误
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len)
在 sockfd 都是可读和可写的情况下,我们使用 getsockopt 来检查连接
是否出错。但这里有一个可移植性的问题。
如果发生错误,getsockopt 源自 Berkeley 的实现将在变量 error 中
返回错误,getsockopt 本身返回0;然而 Solaris 却让 getsockopt 返回 -1,
并把错误保存在 errno 变量中。所以在判断是否有错误的时候,要处理
这两种情况。
代码:
- int conn_nonb(int sockfd, const struct sockaddr_in *saptr, socklen_t salen, int nsec)
- {
- int flags, n, error, code;
- socklen_t len;
- fd_set wset;
- struct timeval tval;
- flags = fcntl(sockfd, F_GETFL, 0);
- fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
- error = 0;
- if ((n == connect(sockfd, saptr, salen)) == 0) {
- goto done;
- } else if (n < 0 && errno != EINPROGRESS){
- return (-1);
- }
- /* Do whatever we want while the connect is taking place */
- FD_ZERO(&wset);
- FD_SET(sockfd, &wset);
- tval.tv_sec = nsec;
- tval.tv_usec = 0;
- if ((n = select(sockfd+1, NULL, &wset,
- NULL, nsec ? &tval : NULL)) == 0) {
- close(sockfd); /* timeout */
- errno = ETIMEDOUT;
- return (-1);
- }
- if (FD_ISSET(sockfd, &wset)) {
- len = sizeof(error);
- code = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
- /* 如果发生错误,Solaris实现的getsockopt返回-1,
- * 把pending error设置给errno. Berkeley实现的
- * getsockopt返回0, pending error返回给error.
- * 我们需要处理这两种情况 */
- if (code < 0 || error) {
- close(sockfd);
- if (error)
- errno = error;
- return (-1);
- }
- } else {
- fprintf(stderr, "select error: sockfd not set");
- exit(0);
- }
- done:
- fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
- return (0);
- }
其中select部分可以扩展为:
如果你要用下面的代码,需要使用The Secure Mailer license。
{
#ifndef USE_SYSV_POLL
fd_set write_fds;
fd_set except_fds;
struct timeval tv;
struct timeval *tp;
/*
* Sanity checks.
*/
if (FD_SETSIZE <= fd)
msg_panic("descriptor %d does not fit FD_SETSIZE %d", fd, FD_SETSIZE);
/*
* Guard the write() with select() so we do not depend on alarm() and on
* signal() handlers. Restart the select when interrupted by some signal.
* Some select() implementations may reduce the time to wait when
* interrupted, which is exactly what we want.
*/
FD_ZERO(&write_fds);
FD_SET(fd, &write_fds);
FD_ZERO(&except_fds);
FD_SET(fd, &except_fds);
if (timeout >= 0) {
tv.tv_usec = 0;
tv.tv_sec = timeout;
tp = &tv;
} else {
tp = 0;
}
for (;;) {
switch (select(fd + 1, (fd_set *) 0, &write_fds, &except_fds, tp)) {
case -1:
if (errno != EINTR)
msg_fatal("select: %m");
continue;
case 0:
errno = ETIMEDOUT;
return (-1);
default:
return (0);
}
}
#else
/*
* System-V poll() is optimal for polling a few descriptors.
*/
struct pollfd pollfd;
#define WAIT_FOR_EVENT (-1)
pollfd.fd = fd;
pollfd.events = POLLOUT;
for (;;) {
switch (poll(&pollfd, 1, timeout < 0 ?
WAIT_FOR_EVENT : timeout * 1000)) {
case -1:
if (errno != EINTR)
msg_fatal("poll: %m");
continue;
case 0:
errno = ETIMEDOUT;
return (-1);
default:
if (pollfd.revents & POLLNVAL)
msg_fatal("poll: %m");
return (0);
}
}
#endif
}
http://kenby.iteye.com/blog/1183579