最近遇到阻塞式connect在三步握手过程中,客户端自身IP发生变化时,connect系统调用阻塞时间过长的问题。故将connect修改为非阻塞的方式。
int is_nonblock(int sockfd)
{
return ((fcntl(sockfd, F_GETFD, 0) & O_NONBLOCK) == O_NONBLOCK) ? TRUE : FALSE;
}
int set_nonblock(int sockfd)
{
return fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0) | O_NONBLOCK);
}
int set_block(int sockfd)
{
return fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0) & (~O_NONBLOCK));
}
int connect_nonblock(int sock_fd, struct sockaddr *addr, socklen_t addrlen, int sec)
{
int is_fd_nonblock = FALSE, ret = 0, error = 0;
socklen_t len;
fd_set rset, wset;
struct timeval tval;
if (0 > sock_fd || NULL == addr)
{
return -1;
}
is_fd_nonblock = is_nonblock(sock_fd);
if (!is_fd_nonblock)
{
set_nonblock(sock_fd);
}
if ((ret = connect(sock_fd, addr, addrlen)) < 0)
{
/* 非阻塞式connect,内核返回错误码为正在执行中 */
if (EINPROGRESS != errno)
{
goto done;
}
}
else if (0 == ret)
{
/* connect直接成功 */
goto done;
}
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_SET(sock_fd, &rset);
FD_SET(sock_fd, &wset);
tval.tv_sec = sec;
tval.tv_usec = 0;
/* select监听描述符, 设置超时时间为tval,当被信号异常中断时,重新进入select,新的超时时间为剩余时间 */
do {
ret = select(sock_fd + 1, &rset, &wset, NULL, &tval);
} while((-1 == ret) && (EINTR == errno));
if (ret == 0)
{
/* 超时 */
errno = ETIMEDOUT;
ret = -1;
goto done;
}
else if (ret < 0)
{
DBG_ERR("select error: %s\n", strerror(errno));
ret = -1;
goto done;
}
ret = 0;
if (FD_ISSET(sock_fd, &rset) || FD_ISSET(sock_fd, &wset))
{
/* 若connect成功,socket的错误应该为0,否则为connect错误时的errno */
len = sizeof(error);
if ((ret = getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &error, &len)) < 0)
{
ret = -1;
goto done;
}
else
{
if (error)
{
errno = error;
ret = -1;
goto done;
}
}
}
else
{
DBG_ERR("select error: sock_fd not set\n");
}
done:
if (!is_fd_nonblock)
{
set_block(sock_fd);
}
return ret;
}