Sagittarius has
(net socket)
library which defines socket selector. A socket selector takes arbitary number of sockets and waits until one or more of the given sockets are readable. The concept was there quit a long time, however it wasn't really working on Windows unfortunately. Now, I put some effort to make it work as I expected.
The requirement of the socket selector is below:
- Interruptible
- No limits of the number of sockets
- Timeout on socket level and procedure call itself
The below piece of code shows essence of the actual implementation. It doesn't handle timeout for the sake of simplicity. What it does is the following;
- Associstes the given sockets to the given event
- Waits the event
- Once an event is received, then associates the sockets to other event, this time an event per socket
- Check if the socket is readable (passing
0
timeout value) - If it's readable, add to the list
WSA_MAXIMUM_WAIT_EVENTS
.
#include <winsock2.h>
#include <windows.h>
typedef struct socket_list_rec
{
SOCKET socket;
struct socket_list_rec *next; /* NULL = term */
} socket_list_t;
socket_list_t * selector_wait(int n, SOCKET *sockets, WSAEVENT event)
{
socket_list_t *list = NULL;
for (int i = 0; i < n; i++) {
WSAEventSelect(sockets[i], event, FD_READ | FD_OOB);
}
int r = WSAWaitForMultipleEvents(1, event, FALSE, INFINITE, FALSE);
if (r == WSA_WAIT_FAILED) {
return NULL;
}
if (r == WSA_WAIT_EVENT_0) {
WSAResetEvent(event);
for (int i = 0; i < n; i++) {
WSAEventSelect(sockets[i], NULL, 0);
}
int rem = (n % WSA_MAXIMUM_WAIT_EVENTS) > 0 ? 1 : 0;
int batch = (n / WSA_MAXIMUM_WAIT_EVENTS) + rem;
int size = min(n, WSA_MAXIMUM_WAIT_EVENTS);
WSAEVENT events[WSA_MAXIMUM_WAIT_EVENTS];
for (int i = 0; i < size; i++) {
events[i] = WSACreateEvent();
}
for (int i = 0; i < batch; i++) {
int count = 0;
int offset = i * WSA_MAXIMUM_WAIT_EVENTS;
for (int j = 0; j < size; j++) {
int m = offset + j;
if (m < n) {
WSAEventSelect(sockets[m], events[j], FD_READ | FD_OOB);
count++;
} else {
break;
}
}
int r = WSAWaitForMultipleEvents(count, events, FALSE, 0, FALSE);
if (r != WSA_WAIT_FAILED && r != WSA_WAIT_TIMEOUT) {
WSANETWORKEVENTS ne;
for (int j = 0; j < count; j++) {
if (WSAEnumNetworkEvents(sockets[offset + j], events[j], &ne) == 0) {
if ((ne.lNetworkEvents & (FD_READ | FD_OOB)) != 0) {
socket_list_t *n = (socket_list_t *)malloc(sizeof(socket_list_t));
n->socket = sockets[offset + j];
n->next = list;
list = n;
}
}
}
}
}
for (int i = 0; i < n; i++) WSAEventSelect(sockets[i], NULL, 0);
for (int i = 0; i < size; i++) WSACloseEvent(events[i]);
}
return list;
}
The above code creates events per call, this might be too expensive and wasteful. The actual code pre-allocates the event array during the initialisation.