diff --git a/src/net.h b/src/net.h
--- a/src/net.h
+++ b/src/net.h
@@ -1146,28 +1146,9 @@
     /**
      * Generate a collection of sockets to check for IO readiness.
      * @param[in] nodes Select from these nodes' sockets.
-     * @param[out] recv_set Sockets to check for read readiness.
-     * @param[out] send_set Sockets to check for write readiness.
-     * @param[out] error_set Sockets to check for errors.
-     * @return true if at least one socket is to be checked
-     *     (the returned set is not empty)
+     * @return sockets to check for readiness
      */
-    bool GenerateSelectSet(const std::vector<CNode *> &nodes,
-                           std::set<SOCKET> &recv_set,
-                           std::set<SOCKET> &send_set,
-                           std::set<SOCKET> &error_set);
-
-    /**
-     * Check which sockets are ready for IO.
-     * @param[in] nodes Select from these nodes' sockets.
-     * @param[out] recv_set Sockets which are ready for read.
-     * @param[out] send_set Sockets which are ready for write.
-     * @param[out] error_set Sockets which have errors.
-     * This calls `GenerateSelectSet()` to gather a list of sockets to check.
-     */
-    void SocketEvents(const std::vector<CNode *> &nodes,
-                      std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set,
-                      std::set<SOCKET> &error_set);
+    Sock::EventsPerSock GenerateWaitSockets(Span<CNode *const> nodes);
 
     /**
      * Check connected and listening sockets for IO readiness and process them
@@ -1178,22 +1159,18 @@
     /**
      * Do the read/write for connected sockets that are ready for IO.
      * @param[in] nodes Nodes to process. The socket of each node is checked
-     * against `recv_set`, `send_set` and `error_set`.
-     * @param[in] recv_set Sockets that are ready for read.
-     * @param[in] send_set Sockets that are ready for send.
-     * @param[in] error_set Sockets that have an exceptional condition (error).
+     *     against `what`.
+     * @param[in] events_per_sock Sockets that are ready for IO.
      */
     void SocketHandlerConnected(const std::vector<CNode *> &nodes,
-                                const std::set<SOCKET> &recv_set,
-                                const std::set<SOCKET> &send_set,
-                                const std::set<SOCKET> &error_set)
+                                const Sock::EventsPerSock &events_per_sock)
         EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc);
 
     /**
      * Accept incoming connections, one from each read-ready listening socket.
-     * @param[in] recv_set Sockets that are ready for read.
+     * @param[in] events_per_sock Sockets that are ready for IO.
      */
-    void SocketHandlerListening(const std::set<SOCKET> &recv_set);
+    void SocketHandlerListening(const Sock::EventsPerSock &events_per_sock);
 
     void ThreadSocketHandler() EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc);
     void ThreadDNSAddressSeed()
diff --git a/src/net.cpp b/src/net.cpp
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -1567,12 +1567,11 @@
     return false;
 }
 
-bool CConnman::GenerateSelectSet(const std::vector<CNode *> &nodes,
-                                 std::set<SOCKET> &recv_set,
-                                 std::set<SOCKET> &send_set,
-                                 std::set<SOCKET> &error_set) {
+Sock::EventsPerSock CConnman::GenerateWaitSockets(Span<CNode *const> nodes) {
+    Sock::EventsPerSock events_per_sock;
+
     for (const ListenSocket &hListenSocket : vhListenSocket) {
-        recv_set.insert(hListenSocket.sock->Get());
+        events_per_sock.emplace(hListenSocket.sock, Sock::Events{Sock::RECV});
     }
 
     for (CNode *pnode : nodes) {
@@ -1600,187 +1599,50 @@
             continue;
         }
 
-        error_set.insert(pnode->m_sock->Get());
+        Sock::Event requested{0};
         if (select_send) {
-            send_set.insert(pnode->m_sock->Get());
-            continue;
-        }
-        if (select_recv) {
-            recv_set.insert(pnode->m_sock->Get());
-        }
-    }
-
-    return !recv_set.empty() || !send_set.empty() || !error_set.empty();
-}
-
-#ifdef USE_POLL
-void CConnman::SocketEvents(const std::vector<CNode *> &nodes,
-                            std::set<SOCKET> &recv_set,
-                            std::set<SOCKET> &send_set,
-                            std::set<SOCKET> &error_set) {
-    std::set<SOCKET> recv_select_set, send_select_set, error_select_set;
-    if (!GenerateSelectSet(nodes, recv_select_set, send_select_set,
-                           error_select_set)) {
-        interruptNet.sleep_for(
-            std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS));
-        return;
-    }
-
-    std::unordered_map<SOCKET, struct pollfd> pollfds;
-    for (SOCKET socket_id : recv_select_set) {
-        pollfds[socket_id].fd = socket_id;
-        pollfds[socket_id].events |= POLLIN;
-    }
-
-    for (SOCKET socket_id : send_select_set) {
-        pollfds[socket_id].fd = socket_id;
-        pollfds[socket_id].events |= POLLOUT;
-    }
-
-    for (SOCKET socket_id : error_select_set) {
-        pollfds[socket_id].fd = socket_id;
-        // These flags are ignored, but we set them for clarity
-        pollfds[socket_id].events |= POLLERR | POLLHUP;
-    }
-
-    std::vector<struct pollfd> vpollfds;
-    vpollfds.reserve(pollfds.size());
-    for (auto it : pollfds) {
-        vpollfds.push_back(std::move(it.second));
-    }
-
-    if (poll(vpollfds.data(), vpollfds.size(), SELECT_TIMEOUT_MILLISECONDS) <
-        0) {
-        return;
-    }
-
-    if (interruptNet) {
-        return;
-    }
-
-    for (struct pollfd pollfd_entry : vpollfds) {
-        if (pollfd_entry.revents & POLLIN) {
-            recv_set.insert(pollfd_entry.fd);
-        }
-        if (pollfd_entry.revents & POLLOUT) {
-            send_set.insert(pollfd_entry.fd);
-        }
-        if (pollfd_entry.revents & (POLLERR | POLLHUP)) {
-            error_set.insert(pollfd_entry.fd);
-        }
-    }
-}
-#else
-void CConnman::SocketEvents(const std::vector<CNode *> &nodes,
-                            std::set<SOCKET> &recv_set,
-                            std::set<SOCKET> &send_set,
-                            std::set<SOCKET> &error_set) {
-    std::set<SOCKET> recv_select_set, send_select_set, error_select_set;
-    if (!GenerateSelectSet(nodes, recv_select_set, send_select_set,
-                           error_select_set)) {
-        interruptNet.sleep_for(
-            std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS));
-        return;
-    }
-
-    //
-    // Find which sockets have data to receive
-    //
-    struct timeval timeout;
-    timeout.tv_sec = 0;
-    // frequency to poll pnode->vSend
-    timeout.tv_usec = SELECT_TIMEOUT_MILLISECONDS * 1000;
-
-    fd_set fdsetRecv;
-    fd_set fdsetSend;
-    fd_set fdsetError;
-    FD_ZERO(&fdsetRecv);
-    FD_ZERO(&fdsetSend);
-    FD_ZERO(&fdsetError);
-    SOCKET hSocketMax = 0;
-
-    for (SOCKET hSocket : recv_select_set) {
-        FD_SET(hSocket, &fdsetRecv);
-        hSocketMax = std::max(hSocketMax, hSocket);
-    }
-
-    for (SOCKET hSocket : send_select_set) {
-        FD_SET(hSocket, &fdsetSend);
-        hSocketMax = std::max(hSocketMax, hSocket);
-    }
-
-    for (SOCKET hSocket : error_select_set) {
-        FD_SET(hSocket, &fdsetError);
-        hSocketMax = std::max(hSocketMax, hSocket);
-    }
-
-    int nSelect =
-        select(hSocketMax + 1, &fdsetRecv, &fdsetSend, &fdsetError, &timeout);
-
-    if (interruptNet) {
-        return;
-    }
-
-    if (nSelect == SOCKET_ERROR) {
-        int nErr = WSAGetLastError();
-        LogPrintf("socket select error %s\n", NetworkErrorString(nErr));
-        for (unsigned int i = 0; i <= hSocketMax; i++) {
-            FD_SET(i, &fdsetRecv);
-        }
-        FD_ZERO(&fdsetSend);
-        FD_ZERO(&fdsetError);
-        if (!interruptNet.sleep_for(
-                std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS))) {
-            return;
-        }
-    }
-
-    for (SOCKET hSocket : recv_select_set) {
-        if (FD_ISSET(hSocket, &fdsetRecv)) {
-            recv_set.insert(hSocket);
+            requested = Sock::SEND;
+        } else if (select_recv) {
+            requested = Sock::RECV;
         }
-    }
 
-    for (SOCKET hSocket : send_select_set) {
-        if (FD_ISSET(hSocket, &fdsetSend)) {
-            send_set.insert(hSocket);
-        }
+        events_per_sock.emplace(pnode->m_sock, Sock::Events{requested});
     }
 
-    for (SOCKET hSocket : error_select_set) {
-        if (FD_ISSET(hSocket, &fdsetError)) {
-            error_set.insert(hSocket);
-        }
-    }
+    return events_per_sock;
 }
-#endif
 
 void CConnman::SocketHandler() {
-    std::set<SOCKET> recv_set;
-    std::set<SOCKET> send_set;
-    std::set<SOCKET> error_set;
+    Sock::EventsPerSock events_per_sock;
 
     {
         const NodesSnapshot snap{*this, /*shuffle=*/false};
 
+        const auto timeout =
+            std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS);
+
         // Check for the readiness of the already connected sockets and the
         // listening sockets in one call ("readiness" as in poll(2) or
         // select(2)). If none are ready, wait for a short while and return
         // empty sets.
-        SocketEvents(snap.Nodes(), recv_set, send_set, error_set);
+        events_per_sock = GenerateWaitSockets(snap.Nodes());
+        if (events_per_sock.empty() ||
+            !events_per_sock.begin()->first->WaitMany(timeout,
+                                                      events_per_sock)) {
+            interruptNet.sleep_for(timeout);
+        }
 
         // Service (send/receive) each of the already connected nodes.
-        SocketHandlerConnected(snap.Nodes(), recv_set, send_set, error_set);
+        SocketHandlerConnected(snap.Nodes(), events_per_sock);
     }
 
     // Accept new connections from listening sockets.
-    SocketHandlerListening(recv_set);
+    SocketHandlerListening(events_per_sock);
 }
 
-void CConnman::SocketHandlerConnected(const std::vector<CNode *> &nodes,
-                                      const std::set<SOCKET> &recv_set,
-                                      const std::set<SOCKET> &send_set,
-                                      const std::set<SOCKET> &error_set) {
+void CConnman::SocketHandlerConnected(
+    const std::vector<CNode *> &nodes,
+    const Sock::EventsPerSock &events_per_sock) {
     for (CNode *pnode : nodes) {
         if (interruptNet) {
             return;
@@ -1797,9 +1659,12 @@
             if (!pnode->m_sock) {
                 continue;
             }
-            recvSet = recv_set.count(pnode->m_sock->Get()) > 0;
-            sendSet = send_set.count(pnode->m_sock->Get()) > 0;
-            errorSet = error_set.count(pnode->m_sock->Get()) > 0;
+            const auto it = events_per_sock.find(pnode->m_sock);
+            if (it != events_per_sock.end()) {
+                recvSet = it->second.occurred & Sock::RECV;
+                sendSet = it->second.occurred & Sock::SEND;
+                errorSet = it->second.occurred & Sock::ERR;
+            }
         }
         if (recvSet || errorSet) {
             // typical socket buffer is 8K-64K
@@ -1877,12 +1742,14 @@
     }
 }
 
-void CConnman::SocketHandlerListening(const std::set<SOCKET> &recv_set) {
+void CConnman::SocketHandlerListening(
+    const Sock::EventsPerSock &events_per_sock) {
     for (const ListenSocket &listen_socket : vhListenSocket) {
         if (interruptNet) {
             return;
         }
-        if (recv_set.count(listen_socket.sock->Get()) > 0) {
+        const auto it = events_per_sock.find(listen_socket.sock);
+        if (it != events_per_sock.end() && it->second.occurred & Sock::RECV) {
             AcceptConnection(listen_socket);
         }
     }