【问题标题】:Async connect and disconnect with epoll (Linux)使用 epoll 进行异步连接和断开连接(Linux)
【发布时间】:2012-04-17 08:00:02
【问题描述】:

我需要在 Linux 上使用 epoll 的 tcp 客户端进行异步连接和断开连接。有分机。 Windows 中的函数,例如 ConnectEx、DisconnectEx、AcceptEx 等... 在 tcp 服务器标准接受功能正在工作,但在 tcp 客户端不工作连接和断开...所有套接字都是非阻塞的。

我该怎么做?

谢谢!

【问题讨论】:

  • 作为链接 DJB 页面上建议的替代方案,我建议尝试使用 dupclose 描述符(并使用副本)。未经测试,但据我了解,它应该可以工作。文档指出,不检查close 的返回值是一个严重的编程错误,因为它可能会返回先前的错误。这正是您想要的(如果close 给出错误,connect 失败)。当然,如果您使用epoll,那么您肯定会有一个操作系统,getsockopt(SO_ERROR) 可以正常工作...
  • 如果可行,最简单的选择是等到 connect() 返回之后再设置 NON_BLOCK。
  • @goldilocks:+1 不是异步的,除非您为此使用工作线程,但我同意简单性很诱人。另外,DNS 解析(您可能需要)无论如何都需要一个工作线程,除非您想阻止它(getattrinfo_a 在内部也这样做)。因此,当您无论如何阻止工作人员时,您也可以阻止连接......
  • 我有 1 个工作线程来满足我的所有需求(tcp 服务器/客户端、udp 套接字、timerfd)。在这个线程中,我使用 epoll 进行异步工作。所以我等待 epoll_wait(...) 然后做我需要的。例如:如果套接字正在侦听套接字 - 我调用接受函数,使用此套接字创建新客户端并将其添加到 epoll 队列。但是在 tcpclient 中 - 我无法在连接完成之前将其添加到 epoll ......如果我这样做 - 客户端连接多次(3-4)......

标签: linux sockets asynchronous tcp epoll


【解决方案1】:

做一个非阻塞的connect(),假设socket已经变成非阻塞的:

int res = connect(fd, ...);
if (res < 0 && errno != EINPROGRESS) {
    // error, fail somehow, close socket
    return;
}

if (res == 0) {
    // connection has succeeded immediately
} else {
    // connection attempt is in progress
}

对于第二种情况,connect() 因 EINPROGRESS 失败(仅在这种情况下),您必须等待套接字可写,例如对于 epoll 指定您正在此套接字上等待 EPOLLOUT。一旦你收到通知它是可写的(使用 epoll,希望得到一个 EPOLLERR 或 EPOLLHUP 事件),检查连接尝试的结果:

int result;
socklen_t result_len = sizeof(result);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &result, &result_len) < 0) {
    // error, fail somehow, close socket
    return;
}

if (result != 0) {
    // connection failed; error code is in 'result'
    return;
}

// socket is ready for read()/write()

根据我的经验,在 Linux 上,connect() 永远不会立即成功,您总是必须等待可写性。但是,例如,在 FreeBSD 上,我已经看到非阻塞 connect() 到 localhost 立即成功。

【讨论】:

  • @Matt 我知道是这样,你可能在这里做错了什么。你到底在尝试什么,它在哪里失败了?您是否使用 fcntl 将套接字置于非阻塞模式?
  • 糟糕,没关系。我的一个错误。是的,我有非阻塞套接字。检查连接结果时出现错误!经典的 c 错误。
  • 仅供参考:如果非阻塞连接失败,Windows 将通过异常情况通知而不是套接字可写性。 msdn.microsoft.com/en-us/library/windows/desktop/ms740141.aspx
【解决方案2】:

根据经验,在检测非阻塞连接时,epoll与select和poll有些不同。

使用 epoll:

connect() 调用后,检查返回码。

如果无法立即完成连接,则向epoll注册EPOLLOUT事件。

调用 epoll_wait()。

如果连接失败,您的事件将被 EPOLLERR 或 EPOLLHUP 填充,否则将触发 EPOLLOUT。

【讨论】:

  • 是的,我忘了在我的回答中提到,除了 EPOLLOUT 之外,epoll 还可以返回 EPOLLERR 或 EPOLLHUP。感谢您的提及,已更正。
【解决方案3】:

我在这里有一个“完整”的答案,以防其他人正在寻找这个:

#include <sys/epoll.h>
#include <errno.h>
....
....
int retVal = -1;
socklen_t retValLen = sizeof (retVal);

int status = connect(socketFD, ...);
if (status == 0)
 {
   // OK -- socket is ready for IO
 }
else if (errno == EINPROGRESS)
 {
    struct epoll_event newPeerConnectionEvent;
    int epollFD = -1;
    struct epoll_event processableEvents;
    unsigned int numEvents = -1;

    if ((epollFD = epoll_create (1)) == -1)
    {
       printf ("Could not create the epoll FD list. Aborting!");
       exit (2);
    }     

    newPeerConnectionEvent.data.fd = socketFD;
    newPeerConnectionEvent.events = EPOLLOUT | EPOLLIN | EPOLLERR;

    if (epoll_ctl (epollFD, EPOLL_CTL_ADD, socketFD, &newPeerConnectionEvent) == -1)
    {
       printf ("Could not add the socket FD to the epoll FD list. Aborting!");
       exit (2);
    }

    numEvents = epoll_wait (epollFD, &processableEvents, 1, -1);

    if (numEvents < 0)
    {
       printf ("Serious error in epoll setup: epoll_wait () returned < 0 status!");
       exit (2);
    }

    if (getsockopt (socketFD, SOL_SOCKET, SO_ERROR, &retVal, &retValLen) < 0)
    {
       // ERROR, fail somehow, close socket
    }

    if (retVal != 0) 
    {
       // ERROR: connect did not "go through"
    }   
}
else
{
   // ERROR: connect did not "go through" for other non-recoverable reasons.
   switch (errno)
   {
     ...
   }
}

【讨论】:

  • 我相信您在 epoll_wait() 之后的错误检查不正确 - 即使您没有得到 EPOLLERR,您也应该始终通过 getsockopt(SO_ERROR) 检查连接尝试的结果。请参阅手册页中的 EINPROGRESS linux.die.net/man/2/connect 此外,assert() 是处理严重错误的错误方法 - 这意味着您已经证明它永远不会发生。改用 exit(),即使定义了 NDEBUG 也会终止程序。
  • 刚刚添加了建议的编辑。未经编辑的版本似乎对我有用。
  • 超时时间加-1,上述程序中的epoll_wait不应该无限期阻塞吗?
【解决方案4】:

我已经尝试了 Sonny 的解决方案,并且 epoll_ctl 将返回无效参数。所以我认为也许正确的方法如下:

1.创建socketfd和epollfd

2.使用epoll_ctl将socketfd和epollfd与epoll事件关联起来。

3.做连接(socketfd,...)

4.检查返回值或errno

5.如果 errno == EINPROGRESS,做 epoll_wait

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-17
    • 1970-01-01
    • 2018-03-03
    • 2017-06-05
    • 1970-01-01
    • 1970-01-01
    • 2021-10-17
    相关资源
    最近更新 更多