在C / C ++中通过阻塞/非阻塞TCP套接字进行连接的超时问题

问题描述 投票:0回答:1

我正在寻找一种有效的C / C ++方法来检查服务器是否可以在特定端口上访问。基本上这个想法是通过socket在客户端上打开一个套接字,然后通过connect连接到这个端口上的服务器。

在服务器上使用阻塞套接字时根本无法访问,我必须等到达到默认超时。如果服务器正在工作,但端口没有暴露,connect会立即返回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_RCVTIMEOSO_SNDTIMEO的阻塞套接字。这也不起作用,因为我喜欢并行探测不同的服务器/端口,非阻塞方法对我来说更方便。

sockets tcp timeout winsock blocking
1个回答
1
投票

如果服务器可访问,但端口不可访问,则非阻塞套接字将立即失败,而不是等待完全超时。但你没有发现,因为你正在以错误的方式使用select()

您正在等待readfds参数。如果非阻塞套接字无法连接,select()将不会报告套接字是否可读。如果套接字成功连接,则在服务器向客户端发送字节之前,套接字不会被报告为可读。

要正确检测非阻塞connect()的结果,您需要使用writefdsexceptfdsselect()参数。这在select() documentation on MSDN中有明确说明:

总之,当select返回时,将在特定集中标识套接字,如果:

readfds:

  • 如果已调用listen并且连接处于挂起状态,则accept将成功。
  • 数据可供读取(如果启用了SO_OOBINLINE,则包括OOB数据)。
  • 连接已关闭/重置/终止。

writefds:

  • 如果处理连接调用(非阻塞),则连接成功。
  • 可以发送数据。

exceptfds:

  • 如果处理连接呼叫(非阻塞),则连接尝试失败。
  • OOB数据可供读取(仅当禁用SO_OOBINLINE时)。

在非阻塞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;
© www.soinside.com 2019 - 2024. All rights reserved.