Web servers — the magical thing.
Is it really magical? Pretty much, yes. But let’s break down this magic today. For fun, I’ve built a basic one using C and the socket library. You can find the backstory and full code here.
Before diving into code, let’s first understand: what is a web server?
By reading this blog, you're already interacting with a web server — specifically, the one hosting this blog. But how does it actually work?
When you access this page through a browser, communication happens over HTTP (Hypertext Transfer Protocol).
Your browser sends a request to a remote cloud server where the blog is hosted.
The web server receives that request and responds with content — like an index.html
or blog.html
file.
This process is built on two main protocols:
- TCP/IP – for establishing connections and transmitting data.
- HTTP – for structuring the exchange of web content (pages, APIs, etc.).
Sounds pretty simple, right?
But what’s happening under the hood?
Let’s explore this using a real example — a web server built using C. You can find the full code in the repo:
https://github.com/uthsobcb/esspress
Note: This server doesn’t use a real database — it reads from a
.txt
file as a data source.
Setting Up: Required Libraries
Since we’ll interact with HTTP, sockets, and the network layer, we need to include the following C standard libraries:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
1. Preparing the Response
The server serves an HTML file (index.html
) as the homepage. First, we open the file, read its content, and attach it to a basic HTTP 200 response header.
FILE* html_data;
html_data = fopen("index.html", "r");
char response_data[1024];
fgets(response_data, 1024, html_data);
char http_header[2048] = "HTTP/1.1 200 OK\r\n\n";
strcat(http_header, response_data);
This builds a minimal HTTP response like:
HTTP/1.1 200 OK
<html>...</html>
2. Setting Up the Server
In this part, we define the server socket, specify the IP and port, and bind it to the machine:
int server_socket;
server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_port = htons(8001);
server_address.sin_addr.s_addr = INADDR_ANY;
Then bind the socket and start listening for connections:
bind(server_socket, (struct sockaddr *) &server_address, sizeof(server_address));
listen(server_socket, 5);
Look closely at this line:
server_address.sin_port = htons(8001);
We're serving the web on port 8001.
3. Receiving Requests and Sending Responses
Inside an infinite loop, the server accepts incoming client connections and responds based on the requested path.
For example, if the browser sends a request for /
, we return the HTML page:
if (strncmp(request_buffer, "GET / ", 6) == 0) {
send(client_socket, http_header, strlen(http_header), 0);
}
Similarly, when the client requests the /data
path, we read data from a db.txt
file and send it as plain text:
else if (strncmp(request_buffer, "GET /data", 9) == 0) {
FILE* db_file = fopen("assets/db.txt", "r");
...
send(client_socket, data_response, strlen(data_response), 0);
}
And if the path is unknown, we send a standard 404 response.
Final Thoughts
This isn’t a production-ready server — it’s a learning project. But it does give you a crystal-clear understanding of how a browser talks to a server, how requests are handled, and how responses are structured.
If you've made it this far, congrats — you now understand the core mechanism behind every website you’ve ever visited.
Feel free to clone, run, and even improve the project:
https://github.com/uthsobcb/esspress
Top comments (1)
That's a pretty good explanation.