Search
Applications usually need to simultaneously handle many event sources, such as keyboard, GUI interface, interprocess or network communication. Although many high-performance libraries for specific purposes exist, it is useful to understand how to create light-weight event loop based application, where multiple various events can be handled.
Your goal is to implement an epoll-based application, which can handle timers, keyboard input, and TCP connections simultaneously. The base of the application (epoll loop, timer, and keyboard input) is already implemented in provided template. Don't use multiple threads!
Steps:
git clone https://gitlab.fel.cvut.cz/matejjoe/epoll_server.git
cd epoll_server mkdir build && cd build cmake .. && make ./epoll_server
receive: "Test string\n" send: "11\n" // keep connection established
nc localhost 12345
In UNIX, “everything” is represented as a file descriptor, and therefore we are interested in monitoring these file descriptors for events such as “key was pressed” or “new client is connecting”. All those events are represented by “file descriptor is readable/writable” events. Epoll is high performance mechanism, which enables waiting for multiple events on multiple file descriptors.
epoll_event
TCP provides reliable, ordered, and error-checked transport of data stream (it means, that in one read you can receive only part of a sent message or multiple messages). First of all, we need to include requested headers:
#include <sys/socket.h> #include <sys/types.h> #include <arpa/inet.h> #include <fcntl.h>
Then create TCP socket (AF_INET → IPv4, SOCK_STREAM → TCP):
int sfd = socket(AF_INET, SOCK_STREAM, 0);
Create socket address and bind to existing socket:
short int port = 12345; struct sockaddr_in saddr; memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; // IPv4 saddr.sin_addr.s_addr = htonl(INADDR_ANY); // Bind to all available interfaces saddr.sin_port = htons(port); // Requested port bind(sfd, (struct sockaddr *) &saddr, sizeof(saddr))
Our socket should be non-blocking. Why? By default, sockets are blocking, meaning that a call to read blocks the calling thread until data is received. While the thread is blocked (which can be for long time), events from other sources cannot be handled. (fnctl):
int flags = fcntl(sfd, F_GETFL, 0); flags |= O_NONBLOCK; fcntl(sfd, F_SETFL, flags);
We would like to create server, and therefore we need to listen for connections:
listen(sfd, SOMAXCONN);
Now, we are able to be notified about incoming connections. In order to accept the incoming connection, call function accept:
cfd = accept(sfd, NULL, NULL);
Set new file descriptor for non-blocking operations (same as mentioned earlier).
Then, we can communicate with the client using read() and write() functions:
#define BUF_SIZE 42 char buffer[BUF_SIZE]; int count; count = read(cfd, buffer, BUF_SIZE-1); count = write(cfd, buffer, strlen(buffer));
After the end of communication, close the connection:
close(cfd);