C/C++ 中通过阻塞/非阻塞 TCP 套接字连接的超时问题
timeout issue for connecting via blocking / nonblocking TCP sockets in C/C++
我正在 C/C++ 中寻找一种有效的方法来检查服务器是否可以通过特定端口访问。基本上这个想法是通过 socket
在客户端上打开一个套接字并通过 connect
.
连接到该端口上的服务器
在根本无法访问的服务器上使用阻塞套接字时,我必须等到达到默认超时。如果服务器正在工作,但端口没有暴露 connect
returns 立即 SOCKET_ERROR
.
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int rc = connect(s, (SOCKADDR*)&target, sizeof(target));
closesocket(s);
if (rc == SOCKET_ERROR)
return false;
else
return true
在根本无法访问的服务器上使用非阻塞套接字时,我的行为与端口未暴露的情况相同。我总是必须等待定义的超时(在以下示例中为 10 秒)。
// open socket
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// set mode to unblocking
unsigned long mode = 1;
int rc = ioctlsocket(s, FIONBIO, &mode);
rc = connect(s, (SOCKADDR*)&target, sizeof(target));
struct timeval tv;
tv.tv_sec = 10;
tv.tv_usec = 0;
fd_set read;
FD_ZERO(&read);
FD_SET(s, &read);
fd_set write = read;
rc = select(NULL, &read, NULL, NULL, &tv);
closesocket(s);
if (rc == 0 || rc == SOCKET_ERROR)
return false;
else
return true;
所以问题是是否有办法结合这两种方法的优点。这意味着如果服务器工作但不公开端口,则立即获得结果,如果服务器不工作,则在指定的超时后获得此信息。
我试图使用带有选项 SO_RCVTIMEO
和 SO_SNDTIMEO
的阻塞套接字。这也不起作用,因为我喜欢并行探测不同的服务器/端口,非阻塞方法对我来说更方便。
如果服务器可访问,但端口不可访问,非阻塞套接字将立即失败,而不是等待完全超时。但是你没有检测到,因为你使用 select()
的方式不对。
您正在等待 readfds
参数。如果非阻塞套接字连接失败,select()
将不会报告套接字可读。如果套接字连接成功,直到服务器向您的客户端发送字节之前,套接字将不会被报告为可读。
要正确检测非阻塞 connect()
的结果,您需要使用 select()
的 writefds
和 exceptfds
参数。这在 select()
documentation on MSDN:
中有明确说明
In summary, a socket will be identified in a particular set when select returns if:
readfds:
- If listen has been called and a connection is pending, accept will succeed.
- Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
- Connection has been closed/reset/terminated.
writefds:
- If processing a connect call (nonblocking), connection has succeeded.
- Data can be sent.
exceptfds:
- If processing a connect call (nonblocking), connection attempt failed.
- OOB data is available for reading (only if SO_OOBINLINE is disabled).
如果非阻塞 connect()
失败,您必须使用 getsockopt()
和 SO_ERROR
操作码来获取实际的错误代码。
试试像这样的东西:
// open socket
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET)
{
// handle error...
return false;
}
// set mode to unblocking
unsigned long mode = 1;
int rc = ioctlsocket(s, FIONBIO, &mode);
if (rc == SOCKET_ERROR)
{
// handle error...
closesocket(s);
return false;
}
rc = connect(s, (SOCKADDR*)&target, sizeof(target));
if ((rc == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK))
{
fd_set write, except;
FD_ZERO(&write);
FD_SET(s, &write);
FD_ZERO(&except);
FD_SET(s, &except);
struct timeval tv;
tv.tv_sec = 10;
tv.tv_usec = 0;
rc = select(NULL, NULL, &write, &except, &tv);
if (rc == 0)
{
WSASetLastError(WSAETIMEDOUT);
rc = SOCKET_ERROR;
}
else if (rc > 0)
{
if (FD_ISSET(s, &except))
{
int err = 0;
getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&err, sizeof(err));
WSASetLastError(err);
rc = SOCKET_ERROR;
}
else
rc = 0;
}
}
if (rc == SOCKET_ERROR)
{
// handle error...
clossesocket(s);
return false;
}
closesocket(s);
return true;
我正在 C/C++ 中寻找一种有效的方法来检查服务器是否可以通过特定端口访问。基本上这个想法是通过 socket
在客户端上打开一个套接字并通过 connect
.
在根本无法访问的服务器上使用阻塞套接字时,我必须等到达到默认超时。如果服务器正在工作,但端口没有暴露 connect
returns 立即 SOCKET_ERROR
.
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int rc = connect(s, (SOCKADDR*)&target, sizeof(target));
closesocket(s);
if (rc == SOCKET_ERROR)
return false;
else
return true
在根本无法访问的服务器上使用非阻塞套接字时,我的行为与端口未暴露的情况相同。我总是必须等待定义的超时(在以下示例中为 10 秒)。
// open socket
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// set mode to unblocking
unsigned long mode = 1;
int rc = ioctlsocket(s, FIONBIO, &mode);
rc = connect(s, (SOCKADDR*)&target, sizeof(target));
struct timeval tv;
tv.tv_sec = 10;
tv.tv_usec = 0;
fd_set read;
FD_ZERO(&read);
FD_SET(s, &read);
fd_set write = read;
rc = select(NULL, &read, NULL, NULL, &tv);
closesocket(s);
if (rc == 0 || rc == SOCKET_ERROR)
return false;
else
return true;
所以问题是是否有办法结合这两种方法的优点。这意味着如果服务器工作但不公开端口,则立即获得结果,如果服务器不工作,则在指定的超时后获得此信息。
我试图使用带有选项 SO_RCVTIMEO
和 SO_SNDTIMEO
的阻塞套接字。这也不起作用,因为我喜欢并行探测不同的服务器/端口,非阻塞方法对我来说更方便。
如果服务器可访问,但端口不可访问,非阻塞套接字将立即失败,而不是等待完全超时。但是你没有检测到,因为你使用 select()
的方式不对。
您正在等待 readfds
参数。如果非阻塞套接字连接失败,select()
将不会报告套接字可读。如果套接字连接成功,直到服务器向您的客户端发送字节之前,套接字将不会被报告为可读。
要正确检测非阻塞 connect()
的结果,您需要使用 select()
的 writefds
和 exceptfds
参数。这在 select()
documentation on MSDN:
In summary, a socket will be identified in a particular set when select returns if:
readfds:
- If listen has been called and a connection is pending, accept will succeed.
- Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
- Connection has been closed/reset/terminated.
writefds:
- If processing a connect call (nonblocking), connection has succeeded.
- Data can be sent.
exceptfds:
- If processing a connect call (nonblocking), connection attempt failed.
- OOB data is available for reading (only if SO_OOBINLINE is disabled).
如果非阻塞 connect()
失败,您必须使用 getsockopt()
和 SO_ERROR
操作码来获取实际的错误代码。
试试像这样的东西:
// open socket
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET)
{
// handle error...
return false;
}
// set mode to unblocking
unsigned long mode = 1;
int rc = ioctlsocket(s, FIONBIO, &mode);
if (rc == SOCKET_ERROR)
{
// handle error...
closesocket(s);
return false;
}
rc = connect(s, (SOCKADDR*)&target, sizeof(target));
if ((rc == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK))
{
fd_set write, except;
FD_ZERO(&write);
FD_SET(s, &write);
FD_ZERO(&except);
FD_SET(s, &except);
struct timeval tv;
tv.tv_sec = 10;
tv.tv_usec = 0;
rc = select(NULL, NULL, &write, &except, &tv);
if (rc == 0)
{
WSASetLastError(WSAETIMEDOUT);
rc = SOCKET_ERROR;
}
else if (rc > 0)
{
if (FD_ISSET(s, &except))
{
int err = 0;
getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&err, sizeof(err));
WSASetLastError(err);
rc = SOCKET_ERROR;
}
else
rc = 0;
}
}
if (rc == SOCKET_ERROR)
{
// handle error...
clossesocket(s);
return false;
}
closesocket(s);
return true;