Building an application that processes a huge amount of network processing requires a special way of handling connections in a distributed or cloud environment. Applications running on Linux are able to do this thanks to the scalable I/O event notification mechanism that was introduced in version 2.5.44. In this chapter, you will look at epoll. According to the documentation at https://linux.die.net/man/7/epoll,
The epoll API performs a similar task to poll: monitoring multiple file descriptors to see if I/O is possible on any of them.
You will start by looking at what epoll is and then move on to writing a simple application and finish off looking at the Go epoll library and how it works and also how to use it in an application.
How epoll works in Linux
How to write a Go application to use the epoll API
How the epoll library works
How to write a Go application using the epoll library
Source Code
The source code for this chapter is available from the https://github.com/Apress/Software-Development-Go repository.
Understanding epoll
In this section, you will start by looking at what epoll is all about from a system perspective. When you open a socket in Linux, you are given a file descriptor (or FD for short), which is a non-negative value. When the user application wants to perform an I/O operation to the socket, it passes the FD to the kernel. The epoll mechanism is event-driven, so the user application is notified when an I/O operation happens.
![](https://imgdetail.ebookreading.net/2023/10/9781484287316/9781484287316__9781484287316__files__images__527551_1_En_12_Chapter__527551_1_En_12_Fig1_HTML.jpg)
A block diagram has 2 blocks of user space and kernel space. A user-space includes a user app to register, modify, and delete linked to 2 rectangular blocks of interest and a ready list.
epoll data structure
Interest List: This list/set contains FDs that applications are interested in. The kernel will only send events related to a particular FD that applications are interested in.
Ready List: This list/set contains a subset of reference FDs from the Interest List FDs. The FDs in this list are in the *ready* state that the user application will be notified of.
epoll_create: A system call to create a new epoll instance and return a file descriptor.
epoll_ctl: A system call to register, modify, and delete a FD from the Interest List.
epoll_wait: A system call to wait for I/O events or another way the system call is called to fetch items that are ready from the Ready List.
Edge triggered: A monitored FD configured with edge will be guaranteed to get one notification if the readiness state changed since the last time it called epoll_wait. The application will receive one event and, if it requires more events, it must perform an operation via a system call to inform epoll that it is waiting for more events.
Level triggered: A monitored FD configured with level will be batch together as a single notification and an application can process them all at once.
From the above, it is obvious that edge triggered requires an application to do more work compared to level triggered. By default, epoll operates using a level triggered mechanism.
epoll in Golang
In this section, you will write a simple application that uses epoll. The app is an echo server that receives connections and sends responses to the value that is sent to it.
The sample app will respond by sending the string that was sent by the client. Before diving into the code, let’s take a look at how epoll is used in an application.
Epoll Registration
![](https://imgdetail.ebookreading.net/2023/10/9781484287316/9781484287316__9781484287316__files__images__527551_1_En_12_Chapter__527551_1_En_12_Fig2_HTML.jpg)
A block diagram has 2 blocks. A client app is reversibly linked to the user app in the user space block with an F D value of 2000. The user app is linked to interest list blocks in kernel space.
Listener epoll registration
Once the data structure successfully creates the application, it proceeds by registering the socket listener file descriptor, as seen in the following code snippet. The code uses syscall.EPOLL_CTL_ADD to specify to the system call that it is interested in doing a new registration.
The registration is done based on the information provided in the event struct, which contains the file descriptor and the event that it is interested in monitoring.
Epoll Wait
The event received contains the event type generated by the system and the file descriptor that it is for. This information is used by the code to check for a new client connection. This is done by checking whether the file descriptor it received is the same as the listener; if it is, then it will accept the connection by calling syscall.Accept using the listener FD.
As a final step, when the code detects that the FD received from the event is not the same as the listener FD, it will spin off a goroutine to handle the connection, which will echo back data received from the client.
Epoll Library
You looked at what epoll is all about and created an app that uses it. Writing an app that uses epoll requires writing a lot of repetitive code that takes care of accepting connections, reading requests, registering file descriptors, and more.
Using an open source library can help in writing better applications because the library takes care of the heavy lifting required for epoll. In this section, you will look at netpoll (http://github.com/cloudwego/netpoll). You will create an application using the library and see how the library takes care of epoll internally.
This code snippet shows the creation of a socket listener using CreateListener from the library to listen on port 8000. After successfully opening the listener, the code proceeds to configure the netpoll by specifying the timeout and specifying the echoHandler function to handle the incoming request. The code starts listening to incoming requests by calling the Serve function of netpoll.
![](https://imgdetail.ebookreading.net/2023/10/9781484287316/9781484287316__9781484287316__files__images__527551_1_En_12_Chapter__527551_1_En_12_Fig3_HTML.jpg)
A flow chart of the net poll server and new connection represents the Epoll load balancer including epoll blocks under the Linux kernel and finally to the goroutine pool.
netpoll high-level architecture
The library creates more than one epoll and it uses the number of CPUs as the total number of epolls it will create. Internally, it uses a load balancing strategy to decide which epoll a file descriptor will be registered to.
![](https://imgdetail.ebookreading.net/2023/10/9781484287316/9781484287316__9781484287316__files__images__527551_1_En_12_Chapter__527551_1_En_12_Fig4_HTML.jpg)
A block diagram of Epoll load balancer includes two small blocks named round-robin and random.
netpoll load balancer
The library takes care of a high volume of traffic by using goroutines. This is performed internally by utilizing a pool of goroutine pooling mechanisms. Developers just need to focus to ensure that their application and infrastructure can scale properly.
Summary
In this chapter, you looked at different ways of writing applications using epoll. Using your previous learning from Chapter 2 about system calls, you build an epoll-based application using the standard library. You learned that designing and writing epoll network applications is different from normal networking applications. You dove into an epoll library and learned how to use it to write a network application. Also, you looked at how the library works internally.