0

我正在通过以下方式使用 Berkeley 套接字选择功能。

/*Windows and linux typedefs/aliases/includes are made here with wsa 
junk already taken care of.*/

/**Check if a socket can receive data without waiting.
\param socket The os level socket to check.
\param to The timeout value. A nullptr value will block forever, and zero
for each member of the value will cause it to return immediately.
\return True if recv can be called on the socket without blocking.*/
bool CanReceive(OSSocket& socket,
    const timeval * to)
{
    fd_set set = {};
    FD_SET(socket, &set);
    timeval* toCopy = nullptr;
    if (to)
    {
        toCopy = new timeval;
        *toCopy = *to;
    }

    int error = select((int)socket, &set, 0, 0, toCopy);
    delete toCopy;
    if (error == -1)
        throw Err(); //will auto set from errno.
    else if (error == 0)
        return false;
    else
        return true;
}

我编写了一个类,它将监视一个套接字容器(包装在另一个类中)并将一个 ID 添加到一个单独的容器中,该容器存储有关哪些套接字准备好被访问的信息。地图是 unordered_map。

while(m_running)
{
     for(auto& e : m_idMap)
     {
          auto id = e.first;
          auto socket = e.second;
          timeval timeout = ZeroTime; /*0sec, 0micro*/
          if(CanReceive(socket,&timeout) && 
               std::count(m_readyList.begin(),m_readyList.end(),socket) == 0)
          {
               /*only add sockets that are not on the list already.*/
               m_readyList.push_back(id);
          }
     }
}

我相信很多人已经注意到,这段代码运行得非常快,并且像没有明天一样吞噬 CPU(40% 的 CPU 使用率,地图中只有一个套接字)。我的第一个解决方案是拥有一个智能等待功能,将每秒的迭代次数保持在一个设定值。这对某些人来说似乎很好。 我的问题是:如果不使用此方法,如何在套接字准备好时收到通知? 即使它可能需要一堆宏垃圾来保持便携,也没关系。我只能认为可能有某种方法可以让操作系统为我监视它并在套接字准备好时获得某种通知或事件。为了清楚起见,我选择不使用点网。

循环在自己的线程中运行,当套接字准备好时向软件的其他部分发送通知。整个事情是多线程的,它的每个部分(除了这部分)都使用一个基于事件的通知系统,消除了繁忙的等待问题。我知道这方面的事情变得依赖于操作系统并且受到限制。

编辑:套接字以阻塞模式运行(但选择没有超时,因此不会阻塞),但它们在专用线程中运行。编辑:该系统在其上的智能睡眠功能上表现出色,但不如在某些通知系统到位(可能来自操作系统)的情况下那么好。

4

2 回答 2

1

首先,如果您不希望套接字阻塞,则必须将套接字设置为非阻塞。该select函数不保证后续操作不会阻塞。它只是一个状态报告功能,告诉您过去和现在。

其次,做到这一点的最佳方式因平台而异。如果你不想编写大量特定于平台的代码,你真的应该使用像 Boost ASIO 或 libevent 这样的库。

第三,您可以select在超时的情况下同时调用所有套接字。如果任何套接字是(或曾经)可读的,该函数将立即返回,如果不是,将等待超时。返回时select,它会报告它是否超时,如果不是,则报告哪些套接字是可读的。

这仍然会执行得很差,因为必须放入大量等待列表,以便在单个套接字可读时立即从所有等待列表中删除。但这是您可以通过合理的便携性做的最好的事情。

于 2017-01-03T16:15:27.510 回答
0

如果不使用此方法,如何在套接字准备好时收到通知?

这就是 select() 的用途。这个想法是您对 select() 的调用应该阻塞,直到您传递给它的至少一个套接字(通过 FD_SET())准备好读取。在 select() 返回后,您可以找出哪些套接字现在可以读取(通过调用 FD_ISSET())并在这些套接字上调用 recv() 以从中获取一些数据并进行处理。之后,您再次循环,再次在 select() 中进入睡眠状态,并无限重复。通过这种方式,您可以尽可能快地处理所有任务,同时使用最少的 CPU 周期。

整个事情是多线程的,它的每个部分(除了这部分)都使用一个基于事件的通知系统,消除了繁忙的等待问题。

请注意,如果您的线程在 select() 内部被阻塞,并且您希望它立即唤醒并执行某些操作(即不依赖超时,这将是缓慢且低效的),那么您将需要一些方法来导致 select () 在该线程中立即返回。根据我的经验,最可靠的方法是创建一个 pipe() 或 socketpair() 并让线程在其准备好读取的 fd_set 中包含文件描述符对的一端。然后,当另一个线程想要唤醒该线程时,它可以简单地通过在对的另一端发送一个字节来实现。这将导致 select() 返回,然后线程可以读取单个字节(并将其丢弃),然后在醒来后执行它应该执行的任何操作。

于 2017-01-03T16:27:39.200 回答