0

I am utilizing the Berkeley sockets select function in the following way.

/*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;
}

I have written a class that will watch a container of sockets (wrapped up in aother class) and add an ID to a separate container that stores info on what sockets are ready to be accessed. The map is an 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);
          }
     }
}

As I'm sure many have noticed, this code run insanely fast and gobbles up CPU like there is no tomorrow (40% CPU usage with only one socket in the map). My first solution was to have a smart waiting function that keeps the iterations per second to a set value. That seemed to be fine with some people. My question is this: How can I be notified when sockets are ready without using this method? Even if it might require a bunch of macro junk to keep it portable that's fine. I can only think there might be some way to have the operating system watch it for me and get some sort of notification or event when the socket is ready. Just to be clear, I have chosen not use dot net.

The loop runs in its own thread, sends notifications to other parts of the software when sockets are ready. The entire thing is multi threaded and every part of it (except this part) uses an event based notification system that eliminates the busy waiting problem. I understand that things become OS-dependent and limited in this area.

Edit: The sockets are run in BLOCKING mode (but select has no timeout, and therefor will not block), but they are operated on in a dedicated thread. Edit: The system performs great with the smart sleeping functions on it, but not as good as it could with some notification system in place (likely from the OS).

Cygnuson
  • 160
  • 1
  • 11
  • 1
    Why not just call `recv`? What's the purpose of the extra call to `select` to try to predict what `recv` will do? Why not just call `recv` and see what happens? – David Schwartz Jan 03 '17 at 15:48
  • Also, you really should just use a library to do this. Doing it 100% right is a *lot* of work and, as you've discovered, faking it with minimal code is painful. There's boost ASIO, there's libevent. – David Schwartz Jan 03 '17 at 15:50
  • David Schwartz: If a socket does not immediately have something to give me, I move to another socket and check if it has something for me. There are thousands of sockets. Block on one socket by just calling recv, will ignore other sockets that may be ready now. Calling recv with no-block option would not solve anything as it would still be busy waiting. – Cygnuson Jan 03 '17 at 15:54
  • If you don't want your sockets to block, you *must* set them non-blocking. The `select` function does *not* provide a future guarantee. It's just a status reporting function (like `stat`). Once you fix that bug and set your sockets non-blocking, the call to `select` buys you nothing except requiring an extra system call for every chunk you receive. Get the simple things right before you move on to the tricky ones! (Or use a library. Seriously, ASIO and libevent are awesome.) – David Schwartz Jan 03 '17 at 15:57
  • David Schwartz:The sockets are put into a queue on first come first serve basis. If I go straight to recv, and get the data if it happens to be ready (from socketA) by the time I check socket B it might be ready, even if socket C was ready first, but I didn't know that because I was busy getting data from socket A while Socket C became ready and then socket B (sockets are ordered A,B,C). With my current method, its much more likely that sockets will be dealt with in the order (or at least closer to it) they became ready. If not for that, I would agree with you 100% on using recv like that. – Cygnuson Jan 03 '17 at 16:07
  • Also, I would also agree with you on using a library if I could, it would solve all these issue :-) – Cygnuson Jan 03 '17 at 16:10
  • Nonsense. Right now, socket C might be ready while you're calling `select` on socket A, and by the time you get around to calling `select` on socket B, it might be ready even though C was ready first. And regardless, you're going to call `recv` on them on their order in the list because you make a list of all the ready sockets before you process any of them. That's not a good rationale. -- Why can't you use a library? The right way to do this differs from platform to platform. Do you want to write platform specific code? Do you want to write code that's very sub-optimal? Those're your choices. – David Schwartz Jan 03 '17 at 16:11
  • your right, but downloading a bunch of data from socket A will significantly increase that possibility. I am making the assumption, though, That checking if a socket is ready, will take a lot less time then downloading multiple KB to potentially hundreds of MB of data. The KB or MB of data will be downloaded in the order that they became ready (or close to it) later. Even if the way the the sockets are being accessed changes in the future, it does not change the fact that the loop runs busy waiting. if sockets are checked with recv and downloaded immediately, I will still be in same boat. – Cygnuson Jan 03 '17 at 16:19
  • At some point, you will have to read the data from socket A. And during all the time it takes to do that, you will lose visibility into the order of data being received at other sockets. As a result, it is basically impossible (and not worth the effort even if it was possible) to track the order in which sockets became ready inside a single loop of your detect/read logic. The best you can do is be efficient so you complete the loop as fast as possible, and using two system calls to do the job of one doesn't qualify. – David Schwartz Jan 03 '17 at 16:44
  • Part of my set of parameters is that data should be downloaded in the order it became available (or close possible). I think I see the breakdown in communication **on my part**. Sockets are added to the que by one thread in as close an order as they become available as I can get it. A different thread is always downloading whatever is on that que, while the que is always updating concurrently. What I might not have mentioned is that the thread that checks for readiness ONLY does that. It does not ever download data. Another thread does that. That way data DL does not effect the checker. – Cygnuson Jan 03 '17 at 18:02
  • Eww. Wow, you're really working hard to make your design as slow and painful as possible. If you care about performance at all, you should abandon as much of that design as possible. So not only do you do an extra system call, but you have to convoy to another thread every time. Worse, you either have to convoy back before you can check again or you have to check for readability of sockets even though you already know they're readable but just haven't gotten around to reading from them yet. Jeez, were you deliberately trying to be as awful as possible? – David Schwartz Jan 03 '17 at 18:06
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/132224/discussion-between-david-schwartz-and-matt). – David Schwartz Jan 03 '17 at 18:07

2 Answers2

1

First, you must set the socket non-blocking if you don't want the sockets to block. The select function does not provide a guarantee that a subsequent operation will not block. It's just a status reporting function that tells you about the past and the present.

Second, the best way to do this varies from platform to platform. If you don't want to write lots of platform specific code, you really should use a library like Boost ASIO or libevent.

Third, you can call select on all the sockets at the same time with a timeout. The function will return immediately if any of the sockets are (or were) readable and, if not, will wait up to the timeout. When select returns, it will report whether it timed out or, if not, which sockets were readable.

This will still perform very poorly because of the large number of wait lists the process has to be put on just to be immediately removed from all of them as soon as a single socket is readable. But it's the best you can do with reasonable portability.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g., read(2) without blocking, or a sufficiently small write(2)). From : http://man7.org/linux/man-pages/man2/select.2.html – Cygnuson Jan 03 '17 at 16:21
  • Calling on multiple sockets at a time only says one or more of the sockets are ready, I will still need to check them individually for WHICH one is ready. some may not be ready. – Cygnuson Jan 03 '17 at 16:24
  • 2
    The `select` function modifies the file descriptor sets you pass to indicate which ones are (or were) ready. When `select` returns a hit, it means the socket *was* ready at some point between when you called `select` and when it returned. It does not guarantee future readiness and people who have made the assumption that it does have gotten burned. – David Schwartz Jan 03 '17 at 16:43
  • OK, that makes sense. I will change the sockets over to non-blocking mode as you suggested, to deal with the fact that they may still block (because there not ready after all). Also, I will start pooling sockets together and checking them in batches. Both things will work better then what I have currently. Thank you for putting in the effort it took to consult. It doesn't seem that there is a simple way to be notified about ready sockets (from the OS), so the changes you suggested and some better sleeping mechanics will do. – Cygnuson Jan 03 '17 at 17:02
  • Actually, if the sockets are going to be non-blocking I will just not use select and use MSG_PEEK (and size of 1) with recv instead. I think that is somewhere around what you suggested before as well. This way I can still avoid download data immediately. – Cygnuson Jan 03 '17 at 17:04
  • But then you have no way to wait for data to be received, which you need to do and which `select` in groups does for you. – David Schwartz Jan 03 '17 at 17:07
0

How can I be notified when sockets are ready without using this method?

That's what select() is for. The idea is that your call to select() should block until at least one of the sockets you passed in to it (via FD_SET()) is ready-for-read. After select() returns, you can find out which socket(s) are now ready-for-read (by calling FD_ISSET()) and call recv() on those sockets to get some data from them and handle it. After that you loop again, go back to sleep inside select() again, and repeat ad infinitum. In this way you handle all of your tasks as quickly as possible, while using the minimum amount of CPU cycles.

The entire thing is multi threaded and every part of it (except this part) uses an event based notification system that eliminates the busy waiting problem.

Note that if your thread is blocked inside of select() and you want it to wake up and do something right away (i.e. without relying on a timeout, which would be slow and inefficient), then you'll need some way to cause select() in that thread to return immediately. In my experience the most reliable way to do that is to create a pipe() or socketpair() and have the thread include one end of the file-descriptor-pair in its ready-for-read fd_set. Then when another thread wants to wake that thread up, it can do so simply by sending a byte on the the other end of the pair. That will cause select() to return, the thread can then read the single byte (and throw it away), and then do whatever it is supposed to do after waking up.

Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • Select does not block in my case because I have the timeout set to zero. It immediately tells me if the socket is ready or not. If its ready, it goes to the que, if its not, nothing happens with it. Your idea about the pipe or socket pair is a nice idea and I actually have some other code that could benefit from interrupting a call to select before its timeout finishes. Thank you. – Cygnuson Jan 03 '17 at 16:35
  • 2
    Leave the timeout argument NULL and it will block indefinitely (or until one of the sockets you passed in to is is ready-for-x). Blocking until at least one socket is ready is a necessary step if you want to avoid spinning the CPU. – Jeremy Friesner Jan 03 '17 at 16:38
  • So that comment is the answer that I will go with. Because I cant block (other sockets might be ready while its blocking), I have no choice but to continue using the adaptive sleeping function to keep the loops per second to a reasonable level (the loop will run maximum of once per 10 micro seconds, keeps the CPU usage below 1%). I guess 10 million checks per second will have to be efficient enough. Thank you for putting in the time it took to consult. – Cygnuson Jan 03 '17 at 16:50
  • 1
    @Matt You *can* block. If sockets become ready, it will *stop* blocking! That's what `select` is for and the reason is takes a timeout in the first place. – David Schwartz Jan 03 '17 at 16:55
  • 1
    Also, that's why it is important to pass in (via FD_SET()) *all* of the sockets you want your thread to handle into select(), rather than just one; that way select() will be able to monitor all of them at once, and return whenever any one of them receives data. – Jeremy Friesner Jan 03 '17 at 17:02
  • @JeremyFriesner: the pipe-to-wakeup option is not supported by `select()` on Windows. `select()` can only handle true sockets, not general file descriptors like other platform can. And there is no `pipe()` or `socketpair()` available on Windows anyway. You could use a hidden UDP socket, though. – Remy Lebeau Jan 04 '17 at 02:57
  • @RemyLebeau it's possible to roll your own socketpair() under Windows (using connect(), accept(), etc); that's what I do. Agreed that you could use a UDP socket also. – Jeremy Friesner Jan 04 '17 at 03:22