Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/assets/resources/linux-epoll-wait.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/stage-13/implementation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/stage-14/implementation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/stage-8/implementation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 23 additions & 19 deletions docs/guides/index.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
# Guides

<script setup>
const isProd = import.meta.env.PROD
</script>

The guides feature supplementary documentation intended for your reference as you progress through the roadmap. You'll be directed to the relevant pages as necessary. These documents are broadly classified into two categories:

- **Resources:** These articles include information designed to help us understand concepts. They may include links to third-party websites for deeper understanding of specific topics.
- **References:** These articles provides descriptions and code snippets related to functions and libraries that you'll utilize during implementation. They serve a similar purpose to a `README.md` file found in projects.

## Resources

✅ Reviewed
🟡 To be reviewed
⚪ Partially reviewed
<span v-if="!isProd">✅ Reviewed </span>
<span v-if="!isProd">🟡 To be reviewed </span>
<span v-if="!isProd">⚪ Partially reviewed </span>

- [Architecture](/guides/resources/architecture)
- [Coding Conventions](/guides/resources/coding-conventions)
- [GDB](/guides/resources/gdb)
- [TCP/IP Model](/guides/resources/tcp-ip-model)
- [TCP Socket Programming](/guides/resources/tcp-socket-programming)
- [UDP Socket Programming](/guides/resources/udp-socket-programming)
- [Process and Threads](/guides/resources/process-and-threads)
- [System Calls](/guides/resources/system-calls)
- 🟡 [Introduction to Linux epoll](/guides/resources/introduction-to-linux-epoll)
- 🟡 [Linux epoll](/guides/resources/linux-epoll)
- 🟡 [Blocking & Non-Blocking Sockets](/guides/resources/blocking-and-non-blocking-sockets)
- 🟡 [HTTP](/guides/resources/http)
- <span v-if="!isProd">✅</span> [Architecture](/guides/resources/architecture)
- <span v-if="!isProd">✅</span> [Coding Conventions](/guides/resources/coding-conventions)
- <span v-if="!isProd">✅</span> [GDB](/guides/resources/gdb)
- <span v-if="!isProd">✅</span> [TCP/IP Model](/guides/resources/tcp-ip-model)
- <span v-if="!isProd">✅</span> [TCP Socket Programming](/guides/resources/tcp-socket-programming)
- <span v-if="!isProd">✅</span> [UDP Socket Programming](/guides/resources/udp-socket-programming)
- <span v-if="!isProd">✅</span> [Process and Threads](/guides/resources/process-and-threads)
- <span v-if="!isProd">✅</span> [System Calls](/guides/resources/system-calls)
- <span v-if="!isProd">🟡</span> [Introduction to Linux epoll](/guides/resources/introduction-to-linux-epoll)
- <span v-if="!isProd">🟡</span> [Linux epoll](/guides/resources/linux-epoll)
- <span v-if="!isProd">🟡</span> [Blocking & Non-Blocking Sockets](/guides/resources/blocking-and-non-blocking-sockets)
- <span v-if="!isProd">🟡</span> [HTTP](/guides/resources/http)


## References

- [vec](/guides/references/vec)
- [xps_logger](/guides/references/xps_logger)
- 🟡 [xps_buffer](/guides/references/xps_buffer)
- 🟡 [xps_utils](/guides/references/xps_utils)
- <span v-if="!isProd">✅</span> [vec](/guides/references/vec)
- <span v-if="!isProd">✅</span> [xps_logger](/guides/references/xps_logger)
- <span v-if="!isProd">🟡</span> [xps_buffer](/guides/references/xps_buffer)
- <span v-if="!isProd">🟡</span> [xps_utils](/guides/references/xps_utils)
3 changes: 2 additions & 1 deletion docs/guides/resources/linux-epoll.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,5 @@ When the application closes a monitored FD:

When an application calls `epoll_wait()`, it essentially hands control to the kernel, asking it to monitor all file descriptors that were previously registered through `epoll_ctl()`. Inside the kernel, each epoll instance is represented by an eventpoll object, which contains three key components — a red-black tree (holding all registered file descriptors), a ready list (containing file descriptors that currently have pending I/O events), and a wait queue (where user processes sleep when there are no ready events). When `epoll_wait()` is invoked, if the ready list is empty, the calling process is put to sleep on the wait queue. Meanwhile, every file descriptor (socket, pipe, etc.) in the system maintains its own internal `poll table`, a structure that records which epoll instances are interested in its state changes. When data arrives or an I/O state changes on any of those file descriptors, the kernel triggers the registered callback `ep_poll_callback()`. This callback runs in interrupt or softirq context, adds the corresponding `epitem` (representing that FD) to the eventpoll’s ready list, and then wakes up any processes sleeping on the epoll’s wait queue. Once the sleeping process wakes, `epoll_wait()` copies the list of ready events from the kernel’s ready list into user-space memory and returns control to the application with a list of file descriptors that are ready for I/O.

Thus, the sequence forms a complete chain: the application waits → the kernel monitors the interest list → a file event triggers the callback → the ready list is updated → the process is woken up → and finally, ready events are delivered back to the user space.
Workflow of `epoll_wait()` :
![epoll_wait.png](/assets/resources/linux-epoll-wait.png)
52 changes: 28 additions & 24 deletions docs/roadmap/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Roadmap

<script setup>
const isProd = import.meta.env.PROD
</script>

The roadmap provides a structured guide for participants to build eXpServer gradually. It outlines the progression of learning objectives, starting from introductory concepts and building up to advanced features and architecture.

Each stage builds upon the previous one, ensuring a systematic approach to the project. Links are provided within the stage documents for additional reference of concepts as and when necessary. There will be two types of links present throughout the documentation:
Expand All @@ -16,39 +20,39 @@ The eXpServer project comprises 24 stages, organized into 5 phases. Prior to the

## Stages

✅ Reviewed
🟡 To be reviewed
🟣 Working on it
🔴 Corrections
<span v-if="!isProd">✅ Reviewed </span>
<span v-if="!isProd">🟡 To be reviewed </span>
<span v-if="!isProd">🟣 Working on it </span>
<span v-if="!isProd">🔴 Corrections </span>

### Phase 0: Introduction to Linux socket programming

- [Overview](phase-0/)
- [Stage 0: Setup](phase-0/stage-0)
- [Stage 1: TCP Server](phase-0/stage-1)
- [Stage 2: TCP Client](phase-0/stage-2)
- 🟡 [Stage 3: UDP with Multi-threading](phase-0/stage-3)
- [Stage 4: Linux Epoll](phase-0/stage-4)
- [Stage 5 a): TCP Proxy](phase-0/stage-5-a)
- 🟡 [Stage 5 b): File Transfer using TCP](phase-0/stage-5-b)
- <span v-if="!isProd">✅</span> [Overview](phase-0/)
- <span v-if="!isProd">✅</span> [Stage 0: Setup](phase-0/stage-0)
- <span v-if="!isProd">✅</span> [Stage 1: TCP Server](phase-0/stage-1)
- <span v-if="!isProd">✅</span> [Stage 2: TCP Client](phase-0/stage-2)
- <span v-if="!isProd">🟡</span> [Stage 3: UDP with Multi-threading](phase-0/stage-3)
- <span v-if="!isProd">✅</span> [Stage 4: Linux Epoll](phase-0/stage-4)
- <span v-if="!isProd">✅</span> [Stage 5 a): TCP Proxy](phase-0/stage-5-a)
- <span v-if="!isProd">🟡</span> [Stage 5 b): File Transfer using TCP](phase-0/stage-5-b)

### Phase 1: Building the core of eXpServer by creating reusable modules

- [Overview](phase-1/)
- [Stage 6: Listener & Connection Modules](phase-1/stage-6)
- 🟡 [Stage 7: Core & Loop Modules](phase-1/stage-7)
- 🟡 [Stage 8: Non-Blocking Sockets](phase-1/stage-8)
- 🟡 [Stage 9: epoll Edge Triggered](phase-1/stage-9)
- 🟡 [Stage 10: Pipe Module](phase-1/stage-10)
- 🟡 [Stage 11: Upstream Module](phase-1/stage-11)
- 🟡 [Stage 12: File Module](phase-1/stage-12)
- 🟡 [Stage 13: Session Module](phase-1/stage-13)
- <span v-if="!isProd">✅</span> [Overview](phase-1/)
- <span v-if="!isProd">✅</span> [Stage 6: Listener & Connection Modules](phase-1/stage-6)
- <span v-if="!isProd">🟡</span> [Stage 7: Core & Loop Modules](phase-1/stage-7)
- <span v-if="!isProd">🟡</span> [Stage 8: Non-Blocking Sockets](phase-1/stage-8)
- <span v-if="!isProd">🟡</span> [Stage 9: epoll Edge Triggered](phase-1/stage-9)
- <span v-if="!isProd">🟡</span> [Stage 10: Pipe Module](phase-1/stage-10)
- <span v-if="!isProd">🟡</span> [Stage 11: Upstream Module](phase-1/stage-11)
- <span v-if="!isProd">🟡</span> [Stage 12: File Module](phase-1/stage-12)
- <span v-if="!isProd">🟡</span> [Stage 13: Session Module](phase-1/stage-13)

### Phase 2: Implementing HTTP support

- 🟡 [Overview](phase-2/)
- 🟡 [Stage 14: HTTP Request Module](phase-2/stage-14)
- 🟡 [Stage 15: HTTP Response Module](phase-2/stage-15)
- <span v-if="!isProd">🟡</span> [Overview](phase-2/)
- <span v-if="!isProd">🟡</span> [Stage 14: HTTP Request Module](phase-2/stage-14)
- <span v-if="!isProd">🟡</span> [Stage 15: HTTP Response Module](phase-2/stage-15)
- [Stage 16: Config Module](phase-2/stage-16)
- [Stage 17: Directory Browsing](phase-2/stage-17)

Expand Down
3 changes: 2 additions & 1 deletion docs/roadmap/phase-0/stage-4.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ The above code allowed us to connect to one client at a time and keep serving th
Now let us modify this section and use epoll to achieve our goal of concurrency.

::: tip PRE-REQUISITE READING
Read the following [introduction to epoll](/guides/resources/introduction-to-linux-epoll) before proceeding further.
- Read about [Introduction to epoll](/guides/resources/introduction-to-linux-epoll).
- Read about [Linux epoll](/guides/resources/linux-epoll)
:::

First we’ll create an epoll instance using [`epoll_create1()`](https://man7.org/linux/man-pages/man2/epoll_create.2.html) given by the `<sys/epoll.h>` header. This returns a file descriptor (FD), and lets call it `epoll_fd`. Remember FD’s are just integers (unsigned integers, to be specific).
Expand Down
96 changes: 95 additions & 1 deletion docs/roadmap/phase-1/stage-10.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Here, the timeout in the `epoll_wait()` is set according to the existence of rea

In Stage 6, we have discussed the issue of accumulating nulls in the events,connections and listeners list, here we would be filtering those.


## Implementation

A new module `xps_pipe` is added and the existing modules are modified.
Expand All @@ -51,6 +52,93 @@ Modifications are carried out in the following order :
- `xps_loop`
- `xps_core`

Find below the updated `xps.h` file.

::: details **expserver/src/xps.h**

```c
#ifndef XPS_H
#define XPS_H

// Header files
#include <arpa/inet.h>
#include <assert.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>

// 3rd party libraries
#include "lib/vec/vec.h" // https://github.com/rxi/vec

// Constants
#define DEFAULT_BACKLOG 64
#define MAX_EPOLL_EVENTS 32
#define DEFAULT_BUFFER_SIZE 100000 // 100 KB
#define DEFAULT_PIPE_BUFF_THRESH 1000000 // 1 MB // [!code ++]
#define DEFAULT_NULLS_THRESH 32

// Error constants
#define OK 0 // Success
#define E_FAIL -1 // Un-recoverable error
#define E_AGAIN -2 // Try again
#define E_NEXT -3 // Do next
#define E_NOTFOUND -4 // File not found
#define E_PERMISSION -5 // File permission denied
#define E_EOF -6 // End of file reached

// Data types
typedef unsigned char u_char;
typedef unsigned int u_int;
typedef unsigned long u_long;

// Structs
struct xps_core_s;
struct xps_loop_s;
struct xps_listener_s;
struct xps_connection_s;
struct xps_buffer_s;
struct xps_buffer_list_s;
struct xps_pipe_s; // [!code ++]
struct xps_pipe_source_s; // [!code ++]
struct xps_pipe_sink_s; // [!code ++]

// Struct typedefs
typedef struct xps_core_s xps_core_t;
typedef struct xps_loop_s xps_loop_t;
typedef struct xps_listener_s xps_listener_t;
typedef struct xps_connection_s xps_connection_t;
typedef struct xps_buffer_s xps_buffer_t;
typedef struct xps_buffer_list_s xps_buffer_list_t;
typedef struct xps_pipe_s xps_pipe_t; // [!code ++]
typedef struct xps_pipe_source_s xps_pipe_source_t; // [!code ++]
typedef struct xps_pipe_sink_s xps_pipe_sink_t; // [!code ++]

// Function typedefs
typedef void (*xps_handler_t)(void *ptr);


// xps headers
#include "core/xps_core.h"
#include "core/xps_loop.h"
#include "core/xps_pipe.h" // [!code ++]
#include "network/xps_connection.h"
#include "network/xps_listener.h"
#include "utils/xps_logger.h"
#include "utils/xps_utils.h"
#include "utils/xps_buffer.h"

#endif

```
:::

## `xps_pipe` Module

We are introducing pipes in this module. This will be connected to the core itself and will be implemented in a similar way how connections and listeners were implemented.
Expand Down Expand Up @@ -764,7 +852,13 @@ bool handle_pipes(xps_loop_t *loop) {
return false;
}
```
:::
:::

:::tip NOTE
The logic for handling cases where **only** a source or **only** a sink exists might seem unnecessary right now, since we currently connect the source and sink of the same pipe to the same client connection. However, this design is crucial for future extensibility.

In later stages (like Stage 11), when we introduce `upstream servers` or `file servers`, the **source** and **sink** of a pipe will often belong to *different* connections (e.g., reading from a client and writing to an upstream server). This independent checking ensures our pipe system can gracefully handle scenarios where one end of the data flow (like the upstream server) closes or disconnects while the other end (the client) is still active, or vice versa.
:::



Expand Down
16 changes: 9 additions & 7 deletions docs/roadmap/phase-1/stage-11.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ xps_connection_t *xps_upstream_create(xps_core_t *core, const char *host, u_int
}
```
:::warning
Dont forget to free allocated struct addrinfo
Dont forget to free the addrinfo object after use
:::

## Modifications to listener module

In `xps_listener` the `listener_connection_handler()` function is having modifications. If the client requests are on port number 8001, an upstream connection instance is created using `xps_upstream_create()` function. Further using `xps_pipe_create()` pipes are created between client source and upstream sink as well as between upstream source and client sink. So the changes are as follows,

- An upstream connection instance is created if `listenerport` is 8001.
- An upstream connection instance is created if `listener->port` is 8001. The connection should be made to **127.0.0.1** on port **3000**.
- A pipe is created between client source and upstream sink.
- A pipe is created between upstream source and client sink.

Expand All @@ -104,7 +104,7 @@ void listener_connection_handler(void *ptr) {
// Handle connection based on listener port (upstream or direct)
if (listener->port == 8001) {

/* create upstream connection */
/* create upstream connection to 127.0.0.1:3000 */
/*create pipe connection to client source and upstream sink for the listener*/
/*create pipe connection to upstream source and client sink for the listener*/
} else {
Expand Down Expand Up @@ -133,13 +133,15 @@ Now start the python file server to serve the current working directory as shown
If file server is successfully operational it will display a message like the one below
`Serving HTTP on 0.0.0.0 port 3000 (http://0.0.0.0:3000/) ...`

Ensure that the port used when creating the upstream connection matches the port on which the Python file server is running (which is **3000** in this case).

In another terminal compile and run the eXpServer code. It will start like this,

```bash
[INFO] xps_start() : Server listening on [http://0.0.0.0:8001](http://0.0.0.0:8001/)
[INFO] xps_start() : Server listening on [http://0.0.0.0:8002](http://0.0.0.0:8002/)
[INFO] xps_start() : Server listening on [http://0.0.0.0:8003](http://0.0.0.0:8003/)
[INFO] xps_start() : Server listening on [http://0.0.0.0:8004](http://0.0.0.0:8004/)
[INFO] xps_core_start() : Server listening on http://0.0.0.0:8001/
[INFO] xps_core_start() : Server listening on http://0.0.0.0:8002/
[INFO] xps_core_start() : Server listening on http://0.0.0.0:8003/
[INFO] xps_core_start() : Server listening on http://0.0.0.0:8004/
```

Now the python file server and our eXpServer are both running. If the implementation was correct then accessing `localhost:8001` will now show the files present in the current working directory. Whenever any files are selected on `localhost:8001` the corresponding request details can be seen as log in the terminal running the python server.
Expand Down
40 changes: 39 additions & 1 deletion docs/roadmap/phase-1/stage-9.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ We will also add a new function called `handle_connections()` in `xps_loop` modu
In this stage we will be modifying the following modules in the given order:

- `xps_connection`
- `xps_listener`
- `xps_loop`

### Modifications to `xps_connection` module:
Expand Down Expand Up @@ -139,7 +140,43 @@ Modify the `xps_loop_attach` in `xps_listener_create` function similarly

:::

## Modifying `xps_loop` module
### Modifications to `xps_listener` module

In `xps_listener` module we will be modifying the `listener_connection_handler()`


```c

void listener_connection_handler(void *ptr) {
/*check parameter validity*/

while (1) { // [!code ++]

/*Accepting connection*/

if (conn_sock_fd < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) //[!code ++]
break; //[!code ++]

/*Making socket non blocking*/

/*Creating connection instance*/

} // [!code ++]
}

```
:::tip NOTE
Here, the connection handling logic is enclosed inside a `while (true)` loop because the listener socket is registered using **edge-triggered epoll**. In **edge-triggered mode**, epoll generates an event only when there is a change in the state of the kernel buffer. As a result, if multiple connection requests arrive at the same time and we do not drain the accept queue completely, the remaining connections will not trigger another epoll event.

By repeatedly calling `accept()` inside the loop, we ensure that all pending connections in the kernel buffer are processed in a single epoll notification. The loop exits when `accept()` returns `-1` with `errno` set to `EAGAIN` or `EWOULDBLOCK`, indicating that the kernel accept queue has been exhausted.

:::

:::danger IMPORTANT
This behavior is not evident during testing with a single client, since the correctness of this logic can only be verified when multiple clients establish connections simultaneously.
:::

### Modifications to `xps_loop` module



Expand Down Expand Up @@ -201,6 +238,7 @@ void xps_loop_run(xps_loop_t* loop) {
}
```


So now we have successfully made the required changes to enable epoll edge triggering.

## Experiment #1
Expand Down
Loading