====== Event Handling (non-blocking I/O) ====== ===== Task assignment ===== 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: - Download epoll application template from git repository: git clone https://gitlab.fel.cvut.cz/matejjoe/epoll_server.git - Compile and run the program: cd epoll_server mkdir build && cd build cmake .. && make ./epoll_server - Implement TCP server which will listen on port 12345 into the application (hints below). Use epoll mechanism to handle events on TCP. Take into account multiple simultaneous connections. If you don't like our implementation, feel free to write the application yourself (but keep existing functionality). - For each TCP connection, your application should calculate the length of incomming lines and send the length back as ASCII string (immediately after receiving '\n') eg: receive: "Test string\n" send: "11\n" // keep connection established - You can test basic functionality by using the netcat program: nc localhost 12345 (use CTRL+D to send data without new line -- simulate fragmentation) - Upload system will test your solution, but evaluation is manual - Commit solution to your tutor (and into upload system in diff format) ==== Using epoll ==== 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. * First, it is necessary to create epoll context using [[http://man7.org/linux/man-pages/man2/epoll_create.2.html|epoll_create1()]]. * Then, file descriptors to be monitored can be added to or removed from a given epoll context. The ''epoll_event'' structure and [[http://man7.org/linux/man-pages/man2/epoll_ctl.2.html|epoll_ctl()]] function are used for that. Each event can be edge or level triggered. When edge-triggered behavior is chosen (events |= EPOLLET), each event is notified only once – e.g. you need to read all data from a given file – you will not be notified again, if data are not read completely. * Finally, you can wait for events by calling [[http://man7.org/linux/man-pages/man2/epoll_wait.2.html|epoll_wait()]] function in the main loop. ==== Creating TCP server in C ==== 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 #include #include #include Then create TCP [[http://man7.org/linux/man-pages/man7/socket.7.html|socket]] (AF_INET -> IPv4, SOCK_STREAM -> TCP): int sfd = socket(AF_INET, SOCK_STREAM, 0); Create socket address and [[http://man7.org/linux/man-pages/man2/bind.2.html|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. ([[http://man7.org/linux/man-pages/man2/fcntl.2.html|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 [[http://man7.org/linux/man-pages/man2/listen.2.html|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 [[http://man7.org/linux/man-pages/man2/accept.2.html|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 [[http://man7.org/linux/man-pages/man2/read.2.html|read()]] and [[http://man7.org/linux/man-pages/man2/write.2.html|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, [[http://man7.org/linux/man-pages/man2/close.2.html|close]] the connection: close(cfd); ==== Hints ==== * You probably want to create two new classes (first for opening socket, second for client connection representation) * You can use delete this: [[https://isocpp.org/wiki/faq/freestore-mgmt#delete-this|https://isocpp.org/wiki/faq/freestore-mgmt#delete-this]] * Consider fragmentation ("Hello World!\n" can arrive as two different messages -- "He" "llo World\n") * Don't close connection after receiving first '\n' * Read status of all system calls (especially read) -- if error occur, read errno * What do epoll edge and level triggers mean? * Read can block if respective file descriptor is not set to O_NONBLOCK mode