【问题标题】:Linux, sockets, non-blocking connectLinux,套接字,非阻塞连接
【发布时间】:2013-07-20 03:33:44
【问题描述】:

我想创建一个非阻塞连接。 像这样:

socket.connect(); // returns immediately

为此,我使用了另一个线程,一个无限循环和 Linux epoll。像这样(伪代码):

// in another thread
{
  create_non_block_socket();
  connect();

  epoll_create();
  epoll_ctl(); // subscribe socket to all events
  while (true)
  {
    epoll_wait(); // wait a small time(~100 ms)
    check_socket(); // check on EPOLLOUT event
  }
}

如果我先运行服务器,然后运行客户端,则一切正常。如果我先运行客户端,稍等片刻,运行服务器,然后客户端无法连接。

我做错了什么?也许它可以做得不同?

【问题讨论】:

  • 如果你正在提升另一个线程来执行连接,你为什么要异步进行呢?另外,也可以把剩下的通讯录放在那里。
  • 好吧,没有epoll和非阻塞怎么办?如果我只是调用 connect() 那么它将阻塞并等待连接(我是对的吗?)。但是如果我想把这个连接线程加入主线程,我不能这样做,因为连接线程会处于阻塞状态。对不起,如果我错了。
  • 这不是“异步”。这是非阻塞的。

标签: c linux sockets asynchronous epoll


【解决方案1】:

您应该使用以下步骤进行异步连接:

  • socket(..., SOCK_NONBLOCK, ...)创建套接字
  • 开始与connect(fd, ...)的连接
  • 如果返回值既不是0也不是EINPROGRESS,则错误中止
  • 等到fd 发出准备输出的信号
  • getsockopt(fd, SOL_SOCKET, SO_ERROR, ...)检查socket的状态
  • 完成

没有循环——除非你想处理EINTR

如果首先启动客户端,您应该会在最后一步看到错误ECONNREFUSED。如果发生这种情况,请关闭套接字并从头开始。

如果不查看更多详细信息,很难判断您的代码有什么问题。我想,您不会因check_socket 操作中的错误而中止。

【讨论】:

  • 我知道这是一条旧评论,但我只想指出,我必须等待读取才能捕获 ETIMEDOUT。这发生在未返回 SYN 响应时。如果我只等待写入,那么套接字将从 netstat 中消失(从 SYN_SENT 状态),但我不会收到关于套接字可写以调用 getsockopt 并找到 ETIMEDOUT 的通知。我还在连接到 getsockopt 后立即添加了一个调用,以查看在轮询之前是否有任何立即可用的错误。
  • @DreamWarrior:这很奇怪。查看connect(2)connect(3) 并搜索poll。两个手册页都声明,您应该等待指示,socketwritable。您能否提供一个显示意外行为的最小示例?
  • @DreamWarrior:我无法重现您描述的问题。我写了一个最小的test program,它使用POLLOUT正确报告ETIMEDOUT
  • 这不是一个“异步连接”。这是一个非阻塞连接。鉴于程序除了等待成功或失败之外什么都不做,这种方法完全是徒劳的。更重要的是在阻塞模式下进行连接,然后然后恢复为非阻塞,如果有的话。
  • getsockopt(fd, SOL_SOCKET, SO_ERROR, ...)返回0时,so_error中为0,这并不表示socket已经连接。这意味着到目前为止没有发生错误。在这种特定情况下,您需要调用getpeername(),如果getpeername() 返回0,则表示套接字已连接。如果套接字未连接,getpeername() 将返回 -1,并在 errno 中显示 ENOTCONN。 getsockopt(fd, SOL_SOCKET, SO_ERROR, ...) 可以通知您连接被拒绝,但不能通知您连接的套接字。您需要使用getpeername() 或其他方式确保套接字已连接。
【解决方案2】:

有几种方法可以测试非阻塞连接是否成功。

  1. 首先调用getpeername(),如果失败并出现错误ENOTCONN,则连接失败。然后使用 SO_ERROR 调用 getsockopt 以获取套接字上的未决错误
  2. 调用read,长度为0。如果读取失败,则连接失败,read的errno表示连接失败的原因;如果连接成功,read 返回 0
  3. 再次调用connect;如果 errno 是 EISCONN,则连接已经连接并且第一次连接成功。

参考:UNIX 网络编程 V1

【讨论】:

  • 请注意:read() man page 说:“如果 count 为零,read() 可能 i> 检测以下描述的错误。如果没有任何错误,或者如果 read() 不检查错误,则返回计数为 0 的 read()零,没有其他影响。”因此,它可能检测到错误。
【解决方案3】:

D. J. Bernstein 收集了各种方法来检查异步 connect() 调用是否成功。其中许多方法在某些系统上确实存在缺陷,因此为此编写可移植代码是出乎意料的困难。如果有人想了解所有可能的方法及其缺点,check out this document

对于那些只想要 tl;dr 版本的人,最便携的方式如下:

一旦系统将套接字通知为可写,首先调用getpeername() 以查看它是否连接。如果该调用成功,则套接字已连接,您可以开始使用它。如果该调用以ENOTCONN 失败,则连接失败。要找出它失败的原因,请尝试从套接字read(fd, &ch, 1) 读取一个字节,这也会失败,但你得到的错误是你从connect() 得到的错误,如果它不是非阻塞的。

【讨论】:

    猜你喜欢
    • 2013-06-03
    • 2017-04-18
    • 1970-01-01
    • 1970-01-01
    • 2010-10-31
    • 2013-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多