As with the select() function covered in last week’s Lesson, you can use other networking functions to scan for pending standard input while a program otherwise spins busy. The epoll() family of functions allow for such monitoring.
The epoll() functions are preferred by network programmers over the older select() function. They rely more on functions than macros to get the job done, though the steps involved are similar: Create a set of file descriptors to monitor, monitor the descriptors, act when one of the descriptors resports as busy.
Here is the updated code from last week’s Lesson:
2024_04_13-Lesson.c
#include <stdio.h> #include <unistd.h> #include <sys/epoll.h> #define MAX_EVENTS 5 int main() { char a = 'A'; int x,fd,nfds; struct epoll_event evin,events[MAX_EVENTS]; /* Create an epoll instance */ fd = epoll_create1(0); if (fd == -1) { perror("epoll_create1"); return 1; } /* add input event for standard input */ evin.events = EPOLLIN; evin.data.fd = STDIN_FILENO; if (epoll_ctl(fd, EPOLL_CTL_ADD, STDIN_FILENO, &evin) == -1) { perror("epoll_ctl"); return 1; } /* endless loop to scan for keyboard activity */ while (1) { /* output the alphabet loop */ printf(" %c",a++); if( a=='Z' ) a = 'A'; /* obtain number of file desciptors */ nfds = epoll_wait(fd, events, MAX_EVENTS, 0); if (nfds == -1) { perror("epoll_wait"); return 1; } /* scan file descriptors */ for ( x=0; x<nfds; x++) { /* keyboard input buffer full */ if ( events[x].data.fd==STDIN_FILENO ) { getchar(); return 0; } } } return 0; }
The first task is to create an epoll instance: fd = epoll_create1(0);
This step is followed by adding events to an epoll_event structure:
evin.events = EPOLLIN;
evin.data.fd = STDIN_FILENO;
The EPOLLIN
defined constant sets an input event and STDIN_FILENO
is the file descriptor for standard input (defined in the unistd.h
header file). The epoll_ctl() function adds a control interface for the evin
file descriptor:
epoll_ctl(fd, EPOLL_CTL_ADD, STDIN_FILENO, &evin)
With everything set up, the endless while loop starts outputting letters of the alphabet, A through Z.
Within the loop, the epoll_wait() function obtains the number of monitored file descriptors ready for I/O. A for loop scans these descriptors. When one of the events monitored matches the standard input file descriptor, the loop is broken.
For output, the program spews forth the alphabet over and over. You can type away at the keyboard, but only when you press the Enter key does the output stop. This behavior is identical to select() example from last week’s Lesson. Again, it’s a reason I wouldn’t choose this approach to monitor the keyboard as the kbhit() function does a far better job.
I would love to dive in deeper for network programming, but it’s truly a complex topic and the code can be long and tedious. Further, to do any real testing you need multiple network devices available to confirm that everything works properly. Even given all that, it’s a fun task, and I may write an eBook on it in the future.
Interesting. I have known about epoll() since the time (way back when) I read Richard Stevenʼs Uɴɪx Network Programming, but Iʼve never really used it. Sure, waiting with epoll() might be more efficient than relying on select()… but while the latter is supported through the Win32 API as well, the former isnʼt⁽¹⁾.
Anyway: Iʼm not sure if C11ʼs standard library threads support has already been the topic of a blog post, but I think a simple, threads based (localhost) networking example, where a client socket thread sends (for example) calculations to a server socket thread—who responds with the result—should be doable in a small enough example, I think.
⁽¹⁾ After a quick search I just found out that somebody actually went to the trouble of wrapping Win32ʼs I/O completion ports into an (w)epoll()-compatible API. Maybe epoll() and I still have a chance of coming together after all 😉
I’ve not done network programming in Windows, only Linux. (Well the Linux subsystem.) I used select() for my course examples and someone asked about epoll(), which I was unfamiliar with. So I looked into it.
I didn’t think of spawning another thread to do the keyboard input. I suppose that might work, but I still think that the ioctl() method is the best.
Thanks for looking up all this stuff!