Hone logo
Hone
Problems

Building a High-Performance Go Network Server with Epoll

This challenge focuses on integrating Go's net package with the low-level epoll system call to build a highly efficient, event-driven network server. You'll learn how to manage multiple client connections with a single thread by leveraging epoll for asynchronous I/O multiplexing, a crucial technique for scalable server design.

Problem Description

Your task is to implement a simple TCP echo server in Go that utilizes the epoll mechanism for handling client connections. Instead of relying on Go's default goroutine-per-connection model, you will directly interact with the operating system's epoll interface to monitor file descriptors for read/write events.

Key Requirements:

  1. Epoll Integration: Use the golang.org/x/sys/unix package to interact with epoll_create, epoll_ctl (for adding and removing file descriptors), and epoll_wait.
  2. TCP Listener: Create a TCP listener on a specified port.
  3. Accepting Connections: When the listener's file descriptor becomes readable (indicating a new connection), accept the new client connection.
  4. Client Handling: For each accepted client connection:
    • Add its file descriptor to the epoll instance with EPOLLIN (read events) enabled.
    • When the client's file descriptor becomes readable, read data from it.
    • Echo the received data back to the client.
    • If a client disconnects (read returns 0 bytes or an error), remove its file descriptor from epoll and close the connection.
  5. Error Handling: Gracefully handle potential errors during system calls, network operations, and data processing.

Expected Behavior:

The server should listen on a predefined port. When a client connects, it should accept the connection and begin monitoring it for read events. Any data sent by the client should be immediately echoed back. The server should be able to handle multiple concurrent connections efficiently without creating a new goroutine for each.

Edge Cases:

  • Zero-byte reads: A client might send data, and then immediately close the connection, resulting in a zero-byte read.
  • Partial reads/writes: Data may not be read or written in a single epoll event. Your implementation should handle buffering if necessary (though for a simple echo server, reading until EOF or a specific delimiter might suffice for this challenge).
  • Robust error handling: Network glitches or client-side issues can lead to errors. The server should not crash but instead manage these connections appropriately.

Examples

Example 1:

  • Server Setup: Server starts listening on port 8080.
  • Client 1 Connection: A client connects to localhost:8080.
  • Client 1 Data: Client 1 sends "Hello".
  • Server Response: Server reads "Hello" and echoes it back.
  • Client 2 Connection: Another client connects to localhost:8080.
  • Client 2 Data: Client 2 sends "World!".
  • Server Response: Server reads "World!" and echoes it back.

Output (from client's perspective):

Client 1 receives: "Hello" Client 2 receives: "World!"

Explanation: The server successfully accepts two connections and echoes data back to each independently.

Example 2:

  • Server Setup: Server starts listening on port 8080.
  • Client 1 Connection: Client 1 connects.
  • Client 1 Data: Client 1 sends "Test".
  • Server Response: Server echoes "Test".
  • Client 1 Disconnects: Client 1 gracefully closes its connection.
  • Server Action: Server detects the disconnect (e.g., via a zero-byte read or error), removes Client 1's file descriptor from epoll, and closes the server-side socket for that connection.

Output (from client's perspective):

Client 1 receives: "Test" and then the connection is closed.

Explanation: The server correctly handles a client disconnecting after sending data.

Constraints

  • The server must be implemented in Go.
  • You must use golang.org/x/sys/unix for epoll system calls. Standard Go net package abstractions for direct epoll interaction are not allowed (i.e., no net.Poll, net.Listen and then directly adding its underlying FD to epoll is fine, but don't use net's internal event loop if it exists).
  • The server should listen on port 8080 by default.
  • The server should be able to handle at least 100 concurrent connections with reasonable performance.

Notes

  • Familiarize yourself with the epoll_create, epoll_ctl (with EPOLL_CTL_ADD, EPOLL_CTL_DEL, and EPOLL_CTL_MOD operations), and epoll_wait system calls.
  • Remember that file descriptors are integers. You'll need to manage these integers, associating them with connection states or buffers.
  • The unix.Dup or unix.Accept4 system calls might be useful for handling new connections.
  • Consider how to convert the raw file descriptor returned by accept into a Go net.Conn for easier reading and writing, or handle read/write directly using unix.Read and unix.Write.
  • The epoll_event struct is key to passing event information to and from epoll_wait. The Fd field of this struct is often used to store the file descriptor associated with the event. You can also use the Data field for custom user data.
Loading editor...
go