在广域网中,connect函数可能需要比较长的时间返回(等待对端发送ack),所以我们通常需要非阻塞connect。下面分别实现windows和linux的非阻塞connect,并作出对比。
先大概总结一下winsock和linux socket用法区别:
demo1,windows非阻塞connect用法:
bool nonblockingConnect(const char* ip, short port, int timeout = 3)
{
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == INVALID_SOCKET)
{
cout << "create socket failed, error: " << WSAGetLastError() << endl;
return false;
}
//设置为非阻塞
u_long nonBlock = 1;
int ret = ioctlsocket(fd, FIONBIO, &nonBlock);
if (ret == SOCKET_ERROR)
{
cout << "ioctlsocket failed" << endl;
closesocket(fd);
false;
}
SOCKADDR_IN srvAddr;
memset(&srvAddr, 0, sizeof(SOCKADDR_IN));
srvAddr.sin_addr.s_addr = inet_addr(ip);
srvAddr.sin_port = htons(port);
srvAddr.sin_family = AF_INET;
ret = connect(fd, (PSOCKADDR)&srvAddr, sizeof(SOCKADDR_IN));
if (ret == 0)
{
cout << "connect success" << endl;
return true;
}
else if (ret == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK)
{
cout << "can not connect to server, error: " << WSAGetLastError() << endl;
return false;
}
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
timeval tv = { timeout, 0 };
ret = select(fd + 1, nullptr, &wfds, nullptr, &tv);
if (ret != 1)
{
cout << "can not connect to server";
return false;
}
cout << "connect success" << endl;
return true;
}
demo2,linux下非阻塞connect用法:
bool nonblockingConnect(const char* ip, short port, int timeout = 3)
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
cout << "create socket failed" << endl;
return false;
}
//设置非阻塞
int flag = fcntl(fd, F_GETFL, 0);
if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) == -1)
{
cout << "fcntl failed" << endl;
close(fd);
return false;
}
struct sockaddr_in srvAddr;
memset(&srvAddr, 0, sizeof(struct sockaddr_in));
srvAddr.sin_addr.s_addr = inet_addr(ip);
srvAddr.sin_port = htons(port);
srvAddr.sin_family = AF_INET;
//为了处理EINTR,将connect放在循环内
while (1)
{
int ret = connect(fd, (sockaddr*)&srvAddr, sizeof(struct sockaddr_in));
if (ret == 0)
{
cout << "connect successfully" << endl;
return true;
}
else if (ret == -1)
{
if (errno == EINTR)
{
cout << "signal interrupt" << endl;
continue;
}
else if (errno != EINPROGRESS)
{
cout << "can not connect to server, errno: " << errno << endl;
close(fd);
return false;
}
else
{
break;
}
}
}
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
timeval tv = { timeout, 0 };
int ret = select(fd + 1, nullptr, &wfds, nullptr, &tv);
if (ret <= 0)
{
cout << "can not connect to server" << endl;
close(fd);
return false;
}
if (FD_ISSET(fd, &wfds))
{
int error;
socklen_t error_len = sizeof(int);
ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &error_len);
if (ret == -1 || error != 0)
{
cout << "getsockopt connect failed, errno: " << errno << endl;
return false;
}
/**
* 在linux下,select返回fd可写,有两种情况:1.连接成功,2.发生错误
* getsockopt返回error为0则排除错误情况,连接已建立
*/
cout << "connect successfully" << endl;
while (1)
{
int send_len = 0;
const char buf[] = "hello\n";
if ((send_len = send(fd, buf, sizeof(buf), 0)) == -1)
{
cout << "send failed";
return false;
}
cout << "send len: " << send_len << endl;
sleep(3);
}
return true;
}
cout << "can not connect to server, errno: " << errno << endl;
close(fd);
return false;
}
注意:
根据UNP,linux下判断connect连接成功还有三种方法,用于替代getsockopt:
下面还有两个demo用于在linux下判断非阻塞connect连接成功
demo3,linux下通过重新connect判断连接成功demo:
本机虚拟机测试,第一次重connect返回值仍然是EINPROGRESS,第二次才返回EISCONN
//重connect判断连接
//本机虚拟机测试,第一次重connect返回值仍然是EINPROGRESS,第二次才返回EISCONN
bool nonblockingConnect_v2(const char* ip, short port, int timeout = 3)
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
cout << "create socket failed" << endl;
return false;
}
//设置非阻塞
int flag = fcntl(fd, F_GETFL, 0);
if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) == -1)
{
cout << "fcntl failed" << endl;
close(fd);
return false;
}
struct sockaddr_in srvAddr;
memset(&srvAddr, 0, sizeof(struct sockaddr_in));
srvAddr.sin_addr.s_addr = inet_addr(ip);
srvAddr.sin_port = htons(port);
srvAddr.sin_family = AF_INET;
while (1)
{
int ret = connect(fd, (sockaddr*)&srvAddr, sizeof(struct sockaddr_in));
if (ret == 0)
{
cout << "connect successfully" << endl;
return true;
}
else if (ret == -1)
{
if (errno == EINTR)
{
cout << "signal interrupt" << endl;
continue;
}
else if (errno != EINPROGRESS)
{
cout << "can not connect to server, errno: " << errno << endl;
close(fd);
return false;
}
else
{
break;
}
}
}
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
timeval tv = { timeout, 0 };
//循环select直到EISCONN,实际使用应设置超时
while (1)
{
int ret = select(fd + 1, nullptr, &wfds, nullptr, &tv);
if (ret <= 0)
{
cout << "can not connect to server" << endl;
close(fd);
return false;
}
if (FD_ISSET(fd, &wfds))
{
//重新connect,如果errno==EISCONN则表明连接已建立
ret = connect(fd, (sockaddr*)&srvAddr, sizeof(sockaddr_in));
if (errno == EISCONN)
{
cout << "connect successfully" << endl;
//测试代码,循环发送
while (1)
{
int send_len = 0;
const char buf[] = "hello\n";
if ((send_len = send(fd, buf, sizeof(buf), 0)) == -1)
{
cout << "send failed";
return false;
}
cout << "send len: " << send_len << endl;
sleep(3);
}
return true;
}
else
{
cout << "repet while, errno:" << errno << endl;
}
}
}
cout << "can not connect to server, errno: " << errno << endl;
close(fd);
return false;
}
demo4,linux下通过poll判断连接
//poll判断连接
bool nonblockingConnect_v3(const char* ip, short port, int timeout = 3)
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
cout << "create socket failed" << endl;
return false;
}
//设置非阻塞
int flag = fcntl(fd, F_GETFL, 0);
if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) == -1)
{
cout << "fcntl failed" << endl;
return false;
}
struct sockaddr_in srvAddr;
memset(&srvAddr, 0, sizeof(struct sockaddr_in));
srvAddr.sin_addr.s_addr = inet_addr(ip);
srvAddr.sin_port = htons(port);
srvAddr.sin_family = AF_INET;
while (1)
{
int ret = connect(fd, (sockaddr*)&srvAddr, sizeof(struct sockaddr_in));
if (ret == 0)
{
cout << "connect successfully" << endl;
return true;
}
else if (ret == -1)
{
if (errno == EINTR)
{
cout << "signal interrupt" << endl;
continue;
}
else if (errno != EINPROGRESS)
{
cout << "can not connect to server, errno: " << errno << endl;
close(fd);
return false;
}
else
{
break;
}
}
}
struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLOUT | POLLERR;
int ret = poll(&pfd, 1, timeout * 1000);
if (ret == 1 && pfd.revents == POLLOUT)
{
cout << "connect successfully" << endl;
//测试代码,循环发送
while (1)
{
int send_len = 0;
const char buf[] = "hello\n";
if ((send_len = send(fd, buf, sizeof(buf), 0)) == -1)
{
cout << "send failed";
return false;
}
cout << "send len: " << send_len << endl;
sleep(3);
}
return true;
}
cout << "can not connect to server, errno: " << errno << endl;
close(fd);
return false;
}
linux非阻塞connect源码