【问题标题】:How to get out from a TCP blocking connect() call?如何摆脱 TCP 阻塞 connect() 调用?
【发布时间】:2015-04-13 05:40:57
【问题描述】:
    int tcp_sock = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);    

    struct sockaddr_in remoteaddr;
    struct sockaddr_in localAddr;
    short local_port = 22222;
    short remote_port = 33333;

    // local addr
    localAddr.sin_family      = AF_INET;
    localAddr.sin_port        = htons(local_port);
    localAddr.sin_addr.s_addr = 0xC0A80AA5; // we don't give a shit
    int addrLen = sizeof(struct sockaddr_in);

    //Now bind TCP to local addr
    int result = bind(tcp_sock,(struct sockaddr*)&localAddr,addrLen);
    if (result < 0)
    {
        perror("\nbind failed");
        close(tcp_sock);
        return -1;
    }

    result = connect(tcp_sock, (struct sockaddr*)&remoteaddr, sizeof(struct sockaddr_in));
    printf("\nConnect returned %d, error no: %d", result, errno);

这里connect() 调用很长时间后失败。有什么方法可以让connect 函数在我选择的一段时间后返回?我尝试从另一个线程调用close(),但这并没有改变任何东西。

【问题讨论】:

  • 你不能在另一个线程中调用close。你甚至不能尝试。想一想——如何确保close 出现在connect 开始阻塞之后而不是在它之前?
  • @DavidSchwartz:这有关系吗? close 事先应该会在 connect 尝试上导致错误代码 (EBADF?)。如果connect() 已经在飞行中,我会更担心会发生什么......
  • FWIW /-如果套接字在任何线程的connect 之前是closed,我在Linux 上的测试会产生“错误的文件描述符”,但如果connect 已经在运行,则不会“超时”/返回。
  • @TonyD 这非常重要。想象一下,如果在您调用close 之后,但在connect 执行之前,在另一个线程中运行的系统库调用socket,并且您最终连接了一个不属于您的套接字。这可能会导致敏感信息被发送到不受信任的端点。您的测试就像过马路,它恰好是空的,但不看两边,并推断出不看两边的情况下过马路是安全的。 (是的,这确实发生了。)
  • @DavidSchwartz:在后台线程可能正在创建套接字的情况下,这很重要;分开-在您的第一条评论中可能有一个无意的推论,如果close 可以保证在connect 阻塞之后发生,那么它可能会起到超时的作用;正如我的测试所示,这具有误导性。

标签: c++ sockets tcp network-programming blocking


【解决方案1】:

在调用connect()之前将socket设置为非阻塞模式,然后你可以使用select()来指定超时时间。 select() 会告诉你连接是成功还是超时。如果成功,您可以根据需要将套接字重新设置为阻塞模式。如果失败/超时,请关闭套接字。

int tcp_sock = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);    

...

int flags = fcntl(tcp_sock, F_GETFL, 0);
fcntl(tcp_sock, F_SETFL, flags | O_NONBLOCK);

int errCode = 0;

result = connect(tcp_sock, ...);
if (result < 0)
{
    errCode = errno;

    if (errCode == EINPROGRESS)
    {
        fd_set wfd;
        FD_ZERO(&wfd);
        FD_SET(tcp_sock, &wfd);

        struct timeval timeout;
        timeout.tv_sec = ...;
        timeout.tv_usec = ...;

        result = select(tcp_sock+1, NULL, &wfd, NULL, &timeout);
        if (result > 0)
        {
            socklen_t len = sizeof(errCode);
            result = getsockopt(tcp_sock, SOL_SOCKET, SO_ERROR, &errCode, &len);
            if (result < 0)
                errCode = errno;
            else
                result = (errCode == 0) ? 0 : -1;
        }
        else if (result == 0)
        {
            errCode = ETIMEDOUT;
            result = -1;
        }
        else
        {
            errCode = errno;
        }
    }
}

if (result == 0)
{
    // connected
    fcntl(tcp_sock, F_SETFL, flags);
    ...
}
else
{
    // error, use errCode as needed
    ...
}

【讨论】:

  • 我只需要在阻塞模式下使用它。谢谢。
  • 阻塞模式下无法让connect()超时。
  • @Tahlil 您希望connect 阻止直到它成功或失败,或者您不希望。选择。
  • 根据文档,您需要检查套接字状态。 成功的选择并不表示它已连接! 请参阅EINPROGRESS 上的文档:在 select(2) 指示可写后,使用 getsockopt(2) 读取 SOL_SOCKET 级别的 SO_ERROR 选项以确定是否 connect()成功完成(SO_ERROR 为零)或不成功(SO_ERROR 是此处列出的常见错误代码之一,解释了失败的原因)。
【解决方案2】:

有什么方法可以让连接函数在我选择的时间后返回?

向进程发送信号。

#define TIME_OUT_SECONDS (15)

void alarm_handler(int sig)
{
   /* Do nothing. */
}

int main(void)
{
  ...

  signal(alarm_handler);
  alarm(TIME_OUT_SECONDS);

  result = connect(tcp_sock, ...);

  ...

【讨论】:

  • 请注意,这只适用于支持信号的平台,如 Unix/Linux。
【解决方案3】:

如果花费时间,退出连接的唯一方法是使套接字非阻塞。如果它是阻塞套接字,你就出不来了。

有什么方法可以让连接函数在我选择的时间后返回?

您的选择是将套接字标记为非阻塞。没有别的办法。阻塞意味着“阻塞”正在执行的线程,直到套接字上发生事件。您不能根据需要同时阻塞套接字和超时。

使用selectepoll 机制来监控套接字。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-22
    • 2012-01-05
    • 2013-11-04
    • 2017-07-15
    • 2013-11-16
    • 1970-01-01
    • 1970-01-01
    • 2014-04-16
    相关资源
    最近更新 更多