欢迎加QQ学习交流群309798848
在广域网中,connect函数可能需要比较长的时间返回(等待对端发送ack),所以我们通常需要非阻塞connect。下面分别实现windows和linux的非阻塞connect,并作出对比。
先大概总结一下winsock和linux socket用法区别:
- winsock需要WSAStartup和WSACleanupleanup进行初始化和反初始化,linux socket不用
- 关闭套接字,winsock用closesocket,linux socket用close
- winsock fd类型为SOCKET,linux fd类型为int
- winsock设置非阻塞用ioctlsocket,linux socket用ioctl或fcntl
- linux下判断函数错误码需要与EINTR比较,winsock不用
- winsock select第一个参数无实际意义,只用来保证兼容,linux socket需要传最大fd+1
- linux程序需要屏蔽sigpipe信号
- winsock错误码为SOCKET_ERROR或INVALID_SOCKET,linux socket为-1
- winsock IO复用模型有:select, WSAAsyncSelect,WSAEventSelect,IOCP,linux socketIO复用模型有:select,poll,epoll
demo1,windows非阻塞connect用法:
- 套接字设置非阻塞
- connect返回0则连接成功,否则判断错误码是否是WSAEWOULDBLOCK
- select判断套接字可写,则连接成功
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用法:
- 套接字设置非阻塞
- connect,比较错误码,EINPROGRESS表示正在建立连接
- select判断fd是否可写
- 跟windows不同,winsock用select就能判断connect连接是否建立,而linux下可能存在出错的情况,所以还需要getsockopt函数判断是否出错,如果没错则连接成功。
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;
}
注意:
- winsock可以用WSAAsyncSelect或WSAEventSelect替代select判断连接是否成功
- linux可以用poll或epoll判断连接是否成功,poll的优点是不需要getsockopt就能判断连接成功,缺点是不支持跨平台,epoll我没测过,不确定能否直接判断连接成功。
- 以上代码没有很好的考虑到socket的创建位置和关闭位置,没有考虑超时时间的消耗,也没有处理到所有的错误码,实际开发还需要进一步优化才能使用。
根据UNP,linux下判断connect连接成功还有三种方法,用于替代getsockopt:
- 调用getpeername,如果没错则连接成功
- 以0长度参数调用read,如果read返回0则连接成功
- 再次调用connect,如果错误是EISCONN,则表示之前的连接已成功
下面还有两个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;
}