Polling methods on Unix. In generall, all methods provide a way to monitor file descriptors. So in network programming, you’d either make a list of socket FD’s, or use a single one if entirely transmitting over UDP.
Select
The original.
/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
In general, Select is the most widely supported (even on Windows), but most limited. There’s a hard limit on number of FD’s, and has a curious usage: you must pass MaxFD+1 to its first arument, which is the highest numbered FD used in the function.
// Inputs: fd, timeout
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = timeout % 1000;
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(fd, &readSet);
int count = select(fd + 1, &readSet, 0, 0, &tv);
if (FD_ISSET(fd, &readSet)) {
// Read
}
if (count == 0) {
// Empty
}
else if (count < 0) {
if (errno == EINTR) {
// Interrupted
}
else {
// Error
}
}
ReadSet, WriteSet, and ExceptFds are optional. So is time, but it will block forever if you pass a null pointer.
If monitoring the same file descriptor, it’s wise to only pass a single one of the read/write sets in. Write sets will almost always return immediately, making it somewhat wasteful to wait for both when what you’re really waiting for is something incoming.
Reference:
Poll
The improved.
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
Interestingly, this is actually the easiest to use, since there’s nothing weird about the syntax.
// Inputs: fd, timeout
struct pollfd ps;
ps.fd = fd;
ps.events = POLLIN;
ps.revents = 0;
int count = poll(&ps, 1, timeout);
if (ps.revents & POLLIN) {
// Read
}
if (count == 0) {
// Empty
}
else if (count < 0) {
if (errno == EINTR) {
// Interrupted
}
else {
// Error
}
}
poll
takes an array of pollfd
structures, but in our case 1 is enough. ps.revents
is the return value from the poll call. Clearing it might not be necessary, but it doesn’t hurt.
The one downside might be the precision of the timeout is only ms
, but chances are you probably want that (or zero).
Reference
EPoll
The penguin.
#include <sys/epoll.h>
int epoll_create1(int flags);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
typedef union epoll_data {
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
Both epoll
and kqueue
require a new FD that you to register before you use it.
// Inputs: fd, timeout
// ** Somewhere init **
int epFD = epoll_create1(0);
if (epFD == -1) {
return nullptr;
}
struct epoll_event epEvent;
memset(&epEvent, 0, sizeof(struct epoll_event));
epEvent.data.fd = fd;
epEvent.events = EPOLLIN;
if (epoll_ctl(epFD, EPOLL_CTL_ADD, fd, &epEvent) == -1) {
return nullptr;
}
// ** Somewhere looping **
struct epoll_event epEventOut;
int count = epoll_wait(epFD, &epEventOut, 1, timeout);
if (epEventOut.events & EPOLLIN) {
// Read
}
if (count == 0) {
// Empty
}
else if (count < 0) {
if (errno == EINTR) {
// Interrupted
}
else {
// Error
}
}
// ** Somewhere shutdown **
close(epFD);
Once registered, you provide an FD (to the epoll specific FD) and one or more epoll_event
structures for the results. If you bind only a single fd to the epoll FD, that means only 1 event max will occur.
Timeout works the same as poll.
If you wanted to poll READ+WRITE, you’d OR the bitfields together. A EPOLL_CTL_ADD
operation only lets you use the same fd once per epoll FD. So if you wanted to be clever and allow options (Read, Write, or Both), you’d need 3 seperate epoll FD’s, one for each configuration.
Reference
KQueue
The daemon.
#include <sys/event.h>
int kqueue(void);
int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout);
EV_SET(kev, ident, filter, flags, fflags, data, udata);
struct kevent {
uintptr_t ident; /* identifier for this event */
short filter; /* filter for event */
u_short flags; /* action flags for kqueue */
u_int fflags; /* filter flag value */
int64_t data; /* filter data value */
void* udata; /* opaque user data identifier */
uint64_t ext[4]; /* extensions */
};
Use this similar to epoll
// Inputs: fd, timeout
// ** Somewhere init **
int kqFD = kqueue();
if (kqFD == -1) {
return nullptr;
}
struct kevent kqEvent;
memset(&kqEvent, 0, sizeof(struct kevent));
EV_SET(&kqEvent, fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
// ** Somewhere looping **
struct timespec ts;
ts.tv_sec = timeout / 1000;
ts.tv_nsec = (timeout % 1000) * 1000000;
struct kevent kqEventOut;
int count = kevent(ne->kqFD, &kqEvent, 1, &kqEventOut, 1, &ts);
if (kqOutEvent.filter & EVFILT_READ) {
// Read
}
if (count == 0) {
// Empty
}
else if (count < 0) {
if (errno == EINTR) {
// Interrupted
}
else {
// Error
}
}
// ** Somewhere shutdown **
close(kqFD);
To contrast epoll
, you only need a single kqueue FD. Then index the start and number you want to do.
Like select
, if the timespec
argument is a null pointer, it polls forever. timespec
has a much higher available precision too.