This tutorial provides a rigorous return to first principles, demystifying the web's infrastructure through the raw power of C. It is an essential exercise for any developer looking to understand the mechanics beneath the abstractions of modern frameworks.
Deep Dive
Prerequisite Knowledge
- No data available.
Where to go next
- No data available.
Deep Dive
Coding an HTTP Server in CAdded:
Hello everybody. Today's a super beautiful day. The sun is out and it's no joke a top 5% day of the year today.
Maybe we have like 15 days like this in Germany. So what can we do on this super beautiful day? Well, we definitely don't leave the house. We code an HTTP server in C. How awesome is that?
And before anyone of you comments that there are tutorial tutorials like this online. Yeah, of course I know but I want to try it myself. So let me So let's just create a new directory as always. And let's call it HTTP C. No, HTTP C server. HTTP server. That's fine.
And for those of you who don't know what an HTTP server is, HTTP is the protocol that serves the entire internet. It supports the entire internet. Maybe let's let's pull up some source on this. So what is HTTP? It's an internet protocol. Every website is based on HTTP.
For example, if we if we visit google.com, what you see here in the URL is it's it's this prefix https which is an evolution of http or rather it's just plain HTTP put on top of SSL.
So it's just an encrypted version of HTTP.
And HTTP is a protocol that allows you to transfer files over the internet.
Actually, I think it's the lowest. No.
Is it the lowest level file transfer protocol? No, that's probably FTP. I don't know. Well, it's designed to serve the web. And how it's working is if we access a website using its URL then a server receives the request that we have sent to it. No, let let me just start from scratch again. So we enter a URL in the browser.
Then a then that URL is translated to an IP address and the IP address is our target machine that we want to access.
In this case, we want to access herdaniel.com which translates to some IP address and that is the IP address hosting files that we want to access. In that case, it's that simple website here, my personal website.
And then we send some requests using this HTTP protocol over the TCP IP stack which does all the routing and the transport of our HTTP information and all we send is just a request that's that tells the HTTP server get the contents of this website. It's that simple. And in this case, what we retrieve from the browser is no, what we retrieve from the what we retrieve from the server is an HTML file that contains other links to other files as well.
Let's just inspect the the page source.
As you can see here, this is the this is a plain view of the website. It's just an HTML file and it contains data metadata but also the actual contents of this website here. So you will find the text I am Daniel Hirs in here. I am hir. And there there it is. So that's the pure HTML code here. And it also contains links to other references or references to other files resources in HTML HTTP terminology. For example, this icon here. And then the browser sees in this HTML document that there are other files to be downloaded. And then the browser sends more HTTP requests to the server to download all these assets. For example, this image right here.
Let me just copy the link and paste it here. That's a 404. Probably we can't access this directly.
Okay. in anyway it's a protocol to fetch data from some server and we are going to code that in C. There are a couple of HTTP methods that are how do I even explain it? I don't know.
I I can't explain it if I don't 100% understand it myself.
So yeah, I I think I can I think I know how. So we can request the server to do different things.
For example, in the simplest case, we just want to download data from a website or from a server. In this case, we download a website.
But we might also want to send a request that posts data to a server or that creates new files on a server if you have the permissions to. So there are different request methods in HTTP such as get which is the simplest one.
It's it's just asking to retrieve a simple resource, a file, whatever from a server. Then there are a couple of others like post, put, delete, which deletes a resource from server, trace, connect, etc. That's about it. and our HTTP server.
All it's going to do is it's going to serve to a client a file an an HTML file. So, I want to be able to type something like localhost and a port. And let's just put a complete URL like this. http which is the protocol we're using then col slash the host name which is the server running on my local host plus the port it's using at or if it's a port if at is a port that requires elevated permissions then maybe at that instead of server not found we are going to see the website that we have created all done in C. That's the mission. Now I already took 7 minutes of explaining but I wanted to get you all aligned and also I want to get myself aligned because only by talking uh I somehow well no how do I how do I say it? How do I phrase it?
If I start recording a video and I assume you all know everything, then I usually have a really hard time conveying ideas. But if I if I build up ideas from a common denominator, then it's much simpler to follow along, I mean, and also to build simple things.
In any case, what we are doing in practice, what the browser is doing in practice again, it's send sending an HTTP request to some server over TCP IP, which is the internet protocol stack. Maybe let's also have a look at that. And just to get you all aligned on this, TCP IP is the it's the protocol that supports the entire internet. Everything is TCP IP.
You've probably seen it dozens or hundreds or thousands of times before, but you weren't conscious of it if you don't know what it is yet. So TCP IP is the protocol that defines well no IP is the protocol that defines IP addresses and all the routing and how computers locate each other on the internet and TCP is a protocol that that introduces the concept of ports.
So you can have several applications running on the same machine but all are accessible via different ports. And then TCP also provides error correction and it ensures that all the packets are are being delivered in the same order they were sent and TCP also ensures that all packets are delivered and none of them are lost.
So that's the internet technology. It's a very very rough overview also because I don't I'm not an expert in it but this is just my understanding and HTTP in its classic approach is sent via TCP IP of course you could send HTTP requests via whatever you could you could send HTTP requests via Morsa code or via I don't know what via shouting out of your window to your neighbor but they won't understand the HTTP protocol. So usually people or usually machines understand the HTTP protocol when it's sent over TCP IP. You could of course also send it via UDP or any other protocol. Wow. Okay. So I think that that's enough now. It's already been 10 minutes of me talking and I think we are now aligned. So what we have to do if we want to create an HTTP server then we have to send data via TCP IP.
So what we're doing is we are creating an ATCP IP application.
Let's create a file. And also I have no idea how to do that but I'm going to look it all up.
I remember in the past I did something very similar.
I created a UDP server or client. I don't recall exactly but you might know the video if you followed me for maybe a year.
And I think very similar libraries are used but okay let's just blindly try to solve the issue. So let's create a file http server or only http or server server c and what are we going to include here?
We are going to we're going to include senado io and then we are going to create a main method that does the following. The main method is since it's a server, it's something that needs to run indefinitely.
So we are going to have a while true loop in here.
And what it's doing is it's somehow waiting for requests being received. So some kind of request must be received here.
We don't know yet what kind of requests and also if we just run and execute if we just compile and execute this then we will have an infinite loop running which which is not so nice. So it might make sense for the time being to introduce or to put a sleep function here which which suspends the threat for some time such that we don't run out of power here.
Uh let's just check man three sleep.
There should be a sleep method in uni standard.h.
So let's include that includes uni standard.
And now we have a loop. And let's print waiting for request.
Don't forget the new line. And I think this can now safely be compiled and executed.
So now every second I think every full second, right, this value here is an unscented integer integer seconds. Um, every full second we print this.
Let's go into the HTTP server directory here and compile this. So the output file is called HTTP. No, it's called server and the input file is called server C.
This compiles and now we have this executable here plus the source file.
And if we execute the server, of course, it's not a real server, but it's printing every second that it's waiting for a request. That's fine.
Now, how do we actually do some TCP stuff here? How does TCP even work?
Let's check TCP IP server socket. Wait, what do I even need to search for? I want to wait for an incoming connection because it's a server. And then I want to receive data indefinitely.
and respond to that data.
So what I want to do is I want to have a TCP IP socket which is the TCP IP term of something that you can connect to. I think in Linux it's just a file as everything and it accepts incoming connections. So let's look up TCP IP in C. I'm just going to I'm just going to look up TCP IP and C. So, socket programming in C using TCP IP. I think there's a nice book. It's called B's Guide.
TCP IP B ah BJ's Guide to Network Programming. Oh, that one might also be super helpful. So, maybe maybe this one is the best resource. I remember some of you recommended it in the comments before and I also had a look at it, but it's been some time since I had a look at it. So, what do we do? What is a socket? I think we don't need to start there. Oh, no. Okay, let's just have a look at the first sentence. You hear talk of sockets all the time. Perhaps you're wondering what they are. It's a way to speak to other programs using standard Unix file descriptors. Okay, so it's essentially some kind of file. It's an it's an abstraction of a file, but the data you write into that file is being sent somewhere to some other content to whatever computer you connect to. Okay, so it's an abstraction of a file.
It's something that behaves as a file, but under the hood, TCP IP is sending that data somewhere.
If I'm wrong, put it in the comments. As always, I tend to be wrong. Then IP addresses, strct s, and data munching. Oh, I don't want to I don't want to go too much through this guide here. I know it's super super helpful probably, but I don't want to take the time. I want to figure everything out myself.
System calls or bust. Well, okay. So, those are the system calls we're going to use.
Socket. Aha. Socket. So, get the file descriptor.
I guess I can put it off no longer. I have to talk about the socket system called history breakdown. So we have this socket library. Is it a library? Uh let's not soccer socket here. Create an endpoint for communication. That's something that we can do. In socket domain type protocol domain the domain argument specifies a communication domain. This selects the protocol family which will be used for communication. Ah, and here we probably have to select TCP IP because we can create sockets for multiple different protocols, but most of them are probably irrelevant in for for most of you. So, we can define that we want to use an IPv6 socket. We can use an IPv where's the IPv4, which is the internet protocol version 4, which is the classic one. There's a big movement or semi big. It's a moderate smallish movement to move toward IPv6 which offers a much larger address space for IP addresses. But it's it's just ridiculous to handle IPv6 along with IPv4 and basically all of the internet is still IPv4 then.
Okay. So where is IPv4?
Here. There it is. AFET. Okay. So that's the socket type that we want to handle.
So what we do is we go here and create a socket using the system call coming from socket. So let's include include the socket.h H coming from SIS socket.h H and then call socket like this and then we want to use AF in net as it is specified here and then the type what wait there's also a type I thought the domain and the type well intuitively I would have thought type is something like IP before but okay that seems to be the domain what is the type.
So here the socket has the indicated type which specifies the communication semantics. Currently defined types are ah okay so there's socket stream provides sequenced reliable two-way communication. A connectionbased bite streams an outofband data transmission mechanism may be supported.
Is that what we need? Then there's socket D supports datagramgrams connectionless unreliable messages of a fixed maximum length. And that's the UDP protocol. It's also sent over TCP IP, but it's insecure and well, it's not insecure, it's unreliable because it's possible in UDP that you send something and it doesn't arrive and it also doesn't arrive in order. And also the internet is not based on UDP mostly some parts I think websockets might be based on UDP are they if not okay possibly they also sent on over TCP IP so what do we need there's also sequence packet provides a sequenced reliable two-way connectionbased data transmission path for data of a fixed maximum length. A consumer is required to read an entire packet with each input system call. It somehow sounds like stream is the one that we need. So let's have a look at BJ's guide and it's probably going to answer what we need directly.
Um, okay. Here the sample code is trying to connect to an internet address and then uses the socket type dynamically.
But what if we want to create a server because this is client code it seems because it's looking up a target address.
Ah, it used to be people who would hardcode values and you can absolutely still do that.
Uh that's hard coding the protocol domain is PFET or PF wait type is socket stream or socket D and protocol can be set to zero to choose the proper pro protocol for the given type I'm I'm just going to search for sock_stream and see if it tells me that this is TCP IP. So stream is a socket type in network programming that provides reliable connection oriented two-way byte streams primar primarily using TCP.
Okay, that's the one we need. So let's put it here. Sock stream and then protocol. Wait, the last one is protocol. So I already thought the first one is the protocol. Then I thought the second one was protocol as well. Now I think that what what's what's the protocol then the protocol here specifies a particular protocol to be used with the socket. I already thought that I specified TCP IP but okay probably it's some detail about TCP IP that I'm not even aware of and therefore yeah maybe it's possible to separately select that I want to use IP v4 okay that's clear then I want to use a secure stream no a I want to use a stream type but I thought the stream type itself is already TCP IP, but apparently it's not.
So, where are the available protocols?
If we go down here, there's nothing in here.
And if we go to BJ's guide, what does it say about the protocol fields?
protocol.
Uh protocol can be set to zero to choose the proper protocol for a given type.
Aha. So they are not completely independent of each other.
Or you can call get proto by name to look up the protocol you want.
Yeah, but what are the available protocols? Ah, here the macros prefixed with PF seem to be the protocol family and probably we have to set it to PF_IN inet.
Okay, wait. TCP IP in seam. Let's see if we can find some sample code. There should be something, right? And I know that's the website I've already been on.
Aha, there's some sample code. Perfect.
Okay, so this guy just sets uh sets the protocol to zero apparently.
Let's also do that in this case. So I'm creating a socket. The socket returns a an integer which is a file descriptor that returns to that end point. Let's call it the socket. No, let's call it file descriptor.
And what else do we do?
We probably have to accept a connection.
So accept is what a server does and bind is I think what the client does. Where does the client connect?
Oh god.
How about we look at the sample code?
So the sample code does wait. We are creating a socket.
That's so far just a file a file descriptor and we're we don't have we haven't even set up any connection logic so far. So we are not yet telling the library that we expect a connection from a specific IP or from any IP and we don't even tell it the port to listen on. So where at which point do we do we specify this information?
Once you have a socket, you might have to associate that socket with a port on your local machine. This is commonly done if you're going to listen for incoming connections on a specific port.
Okay, that's exactly what we need. So we need to bind.
Then let's go and bind bind. I'm going to bind the socket file descriptor sock fd in the guide or just fd in my case. I find sock fd actually a quite nice name.
So let's rename it. Then I'm going to specify my address here.
Oh, but this is a structure structure socket address pointer my address and I know I remember from reading this guide in the past or from somewhere I don't know exactly from where I remember that we should not create the socket address structure manually.
My address is a pointer to a str socket address that contains information about your address namely port and IP address.
Yeah, exactly. That is what I just meant. Address and address length is the length and bytes of that address. That's a bit absorbed in one chunk. Let's have an example that binds the socket to the host the program is running on. Okay.
Wait. So this guy here creates a socket.
Okay. And then binds to this result AI address.
What what's the result?
Uh it's created here. Get address info.
So here first this address info hints is created which hints any restrictions to the TCP IP library and the get address info call. What does it do? Get address info translates network addresses given note and service which identify an internet host and a service. Get address info returns one or more address info structures each of which contains an internet address that can be specified in a call to bind or connect.
I don't get it.
So get address info specifies what note service hints result. Given note and service which identify an internet host and a service.
Get address info returns one or more address info structures.
Given note and service is the note my computer and the service. What is the service supposed to be? Is it the port?
Because here in the guide the note is null and uh service is this number. What is that number?
Oh god. TCP I pre-programming and see is so unintuitive or I am too stupid. That's also a possibility.
So, how about Wait, before I do anything here, I bind.
No, let let's just delete that and see if it even compiles before I go on.
Okay.
And let me also just print the socket file descriptor just to confirm then that it's valid or we don't even need to print it. We can just check it. So if and I know that the file descriptor must be a number that is positive. So I can check if the socket file descriptor is bigger than no is smaller than zero then there's some kind of error that has happened P error let's print an error and say could not whatever socket error I have no idea socket error for like this and then exit and for that we need to include standard lip. Now we can compile. No, we can't.
Of course, I need to specify an exit code, an error code here. Let's let's recompile and execute the server. And okay, so the second the socket can be created. That's good.
This way we know that this was a success, but the rest is super unintuitive to me.
So, we get the file descriptor. That's fine.
Bind. What port am I on? Once you have a socket, you might have to associate that socket with a port on your local machine.
This is coming commonly done if you're going to listen for incoming connections on a specific port. Multiplayer network games do this when they tell you to connect to an address port 3490.
The port number is used by the kernel to match an incoming packet to a certain process socket descriptor. Aha.
If you're going to only be doing a connect because you're the client, not the server, this is probably unnecessary. read it anyway just for kicks.
So, sock fd is the socket file descriptor returned by socket. My address is a pointer to a strruct sock address that contains information about your address namely port and IP address.
Address length as the length and bytes of that address.
But I'm waiting. What I don't get right now is that I'm waiting for incoming connections. So why do I need to specify anything about the IP address?
Aha. by using the AI passive flag. By the way, this AI has nothing to do with AI as an artificial intelligence. It stands for arpanet inet protocols. Some historical thing here. So, this has something to do with internet protocols, not AI as you know it. By using the AI passive flag, I'm telling the program to bind to the IP of the host that's running on. If you want to bind to a specific local IP address, drop the AI passive and put an IP address in for the first argument to get address info.
Okay, so paste.
So what are we doing? We have created a hint structure and this hints structure is this up here.
Wait.
So we create oh we allocate space for an address infrastructure hints.
This is zeroed out using mems set and then we specify that we don't have any restraints on the IP v4 or IPv6 protocol types. Well, actually we only use IPv4 but okay the socket type is socket stream. The AI flags are a passive then we get the address info null. Why null?
probably because we don't have any restraints here on the IP connecting to us.
But then why is a socket created using the return values from the result here?
So confusing.
So, can't I just create a socket like this? Isn't this much simpler?
Get address info. Null bind sock fd address address length.
I don't think this is the simplest answer. Wait. Um c bind.
Let's just check this here.
So here this guy creates a socket manually.
Then uses the socket address.
Oh god.
then uses the socket address structure to somehow initialize something.
That's ridiculous, guys.
Man bind bind a name to a socket. We bind sock address length size of address.
So, I don't get why we need this hints and results thing. Why is this all necessary? This looks so ridiculously stupid to me. It's for sure for sure.
That's intelligent. And but I don't I just don't get the complexity here. I just want to specify that I want to I want to listen on a port. That's it. So I bind bind to the socket file descriptor.
What do I bind there? A some address. Uh whatever that is. We're going to create that now. Struct address.
Sock address.
And since that must be a pointer, we need to pass this address of address here.
And then the address address length. So we can just do size of well do we do size of address or size of strct sock address? probably size of the strct.
What does the sample code here say? Size of my address.
Why?
Isn't it the same thing?
And this bind, what does it return? An integer here. Int result bind result is equal to this. And let's print f the bind result is. And then value here.
Bind result like this. Compile. execute bind result is zero. Is that good?
Return value on success zero is returned. Okay, so that's a success. But I haven't even specified anything about the address.
So how's this is even worse than a failure because a failure at least tells me that something I have done wrong. But having a success on something that is nonsensical is not helpful at all. It just reduces my trust in the overall system here.
Okay. So what do we do on the socket address? We can set the SA family.
S a family_t I I need to get some information about all of this by doing the following. It's a nice trick. Let's not compile this to an executable, but let's preprocess it and pipe the result into preprocess.ext or C.
And now here's the full C code that is pre-processed.
And now I can find all the information about all the types that there are. For example, I can find all the information I need about h about the sock address here.
So this is the structure sock address and it has a members sa family t which is where is it defined? Is it defined anywhere?
There it's just an unsigned short int.
Well, great. But what kind of value does it expect?
H probably it expects some some constant like these here.
But I don't know. I can't see it in the pre-processed code because of course the pre-processed code does not contain the macro values anymore sock address.
The only purpose of the structure is to cast the structure pointer based passed in Azures in order to avoid compiler warnings.
It is normally necessary to assign a local address using bind before a sock stream socket may receive connections.
When a socket is created with socket, it exists in a name space but has no address assigned to it. Bind assigns the address specified by address to the socket referred to by the file descriptor sock FD.
Bind let again I think this sentence is important and it must initialize my brain for higher level processing. Now bind assigns the address specified by address to the sockets referred to by the file descriptor sock fd. So bind connects the socket the special file with the address specified by address and then they are linked together.
Address length specifies the size in bytes of the address structure pointed to by address.
Uhhuh. So binding is just assigning a name to a socket.
So what do I pass now in in SA family?
Bite socket file descriptor my address. What is my address? AF Unix. What's that?
I don't want to use this AFUX thing.
I want to use AFET because if AFUNIX can be passed then probably AFET can be passed. So what I'm doing is I'm setting I I wanted to set my AFET on the my address structure. Why is it even necessary to me set the my address to zero if after that all the members are set somehow or uh the only answer can be they aren't set somehow.
So here, what does the sun family? What?
Sun family.
My address. Sun family.
What was I looking at? Ss. I know that's wrong. S a family. S a family is not the same as sun family. What? I I really don't get this API.
Sorry, not sorry. But this is uh really not beginner friendly.
SA family sock address SA family and the BJ guide. No, somewhere they said I know in the documentation they said it's only a structure used for casting.
This is the old way.
My address as in family. So now we see we we have seen as a family, SN family and as in family. What an end AI family. So and it's all on the same address info structure.
Ah that's socket socket address structure I don't get anything right now AI address. So AI address in probably we have we just have to trust this right here.
So essentially there's this get address info call that we have to trust. We just hint what we want and then it's creating a real result but somehow it's not really pleasing.
Man get address info.
There it is network address and service translation. We pass node which is null in the sample code. Then service which is the port number hints and then we get a real result here.
And here the address infrastructure has that many members. Why is that the case?
Because here I know that's the socket address. It's so confusing. Socket address.
Add address info. What's the difference?
And address info. Does it contain the socket address? Yes, it does contain the socket address. Okay. Okay. As you can see, I'm not really my network pro programming is at level absolute zero if not even below that. But I'm airing forward.
So let's do get address info. Get address.
No, get address info. Was it called get address info? Yes, it was called get address info, but it's coming from types.h or socket.h. Why? Why do we why do we need to include three libraries here? Net DB Well, which one is providing get address info now?
Do we need to include all of them?
Probably we do. That's all because this these internet uh protocol libraries they have evolved historically over long periods of times over decades and therefore it's all cluttered.
And now I can probably do get address info. Yes, I can do that. I can hint.
This is my hint structure. And then I want to create a result structure like this. Then I want to get the address info for node null. Service is at80 which is a port that does not require admin privileges.
Then hints I think um you can specify up to port 1,000 or something with uh pseudo privileges only. And then after that you can do without.
And now we use the hints structure to hint something. We probably have to pass the pointer. Yes.
hint and we receive the result from the result here like this.
Now let's fill the hint structure. So hint dot I'm just going to say AI family hint AI family is equal to AFET because I don't want to support IPv6. I only want to support IPv4.
I want to support the socket type sock stream and passive fill in my IP for me. AI passive. Yeah. Okay. Hint AI flags is equal to AI passive.
Now we specify that we want to we want to use port 8080 and then we bind to it using the results results.
Ah wait we bind to the socket file descriptor using AI address and AI address length. There's even a field for that.
AI address and result AI address length.
Okay. Now if we compile and execute.
I know we compile.
That doesn't work. Why doesn't this compile? Struct address has no member named AI address. Why?
strct socket address has no member named AI address, but there it is.
uh and of course this is an address info structure and I'm using sock address structure which which is not the same address info.
Okay, now this compiles. What happens if we execute the server? Bind result is zero. So it's workingish.
We just have to trust it works because previously we saw that binding even works if we don't specify anything.
So this is not too uh trustworthy.
And then we can send something to that server somehow. No, we can't yet send something to that server. Oh, can we?
We bind.
We connect. Do we need to connect from the server? No, this is something the client will do. We bind and we listen. Aha, we listen.
Okay, time for a change of pace. What if you don't want to connect to a remote host? Say just for kickstart, you want to wait for incoming connections and handle them in some way. That's exactly that the thing we want to do. The process is two-step. First, you listen, then you accept.
Okay, the listen call is fairly simple but requires a bit of explanation.
Socket FD FD is the usual file descriptor from the socket system call backlog is the number of connections allowed on the incoming que. Aha.
We can have several incoming connections and this means that we can limit them here. Most systems silently limit this number to about 20.
You can probably get away with setting it to five or 10.
Okay.
Again, as per usual, listen returns minus one and sets error number on error. Okay.
As you can probably imagine, we need to call bind before we call listen so that the server is running on a specific port. You have to be able to tell your No, wait. I need to read I I need to read this again before I just read something and then forget about it. As you can probably imagine, we need to call bind before we call listen so that the server is running on a specific port. Yeah, because bind is assoc is associating the port with the file descriptor.
You have to be able to tell your buddies which port you connect to. So if you are going to be listening for incoming connections, the sequence of system calls you make is this.
Okay.
Okay. And so probably we can just say listen listen for incoming connections.
listen on the socket file descriptor and we accept 20 incoming connections. We only need one in the end I think but that's it. And then let's return the listen result here and check if it's smaller than zero then we print a listen error with the reason this can be compiled. This executes there's no listening error. Okay.
Okay. Okay. Okay.
And now after listening we can accept.
It's also complicated. Why is it so complicated guys?
In which case would I want to listen but not accept.
So why is it two separate calls? This is something I don't know.
So get ready. The accept call is kind of weird. What's going to happen is this.
Someone far far away far far away will try to connect to your machine on a port that you are listening on. Their connection will be cute waiting to be accepted. You call accept and you tell it to get the pending connection. It'll return to you a brand new socket file descriptor to use for this single connection. Oh, right. That's right.
Suddenly, you have two socket file descriptors for the price of one. The original one is still listening for more connections and the newly created one is finally finally ready to send and receive. We're there. Okay.
So, we accept something. Oh god, why why is why do I need to specify all of that stuff again?
We listen now. Accept an incoming correct connection address size. Their address.
What is their address?
Is it the incoming addresses connections that we allow their address?
sock address storage.
Ah maybe accept is putting information about the client into this structure here.
So we can accept.
Let's do that. Here we accept from the socket file descriptor an incoming connection.
We pass their address into it so it can be filled although we don't even need it.
Sock address storage.
All this casting is making me crazy.
Their address and then address size and address size is size of their address.
Isn't it the size of the structure?
Because this wait address size is a pointer here. Ah wait we in we accept an incoming connection and it's passing things. Ah okay. So all of those are return values actually.
So the size of the well the address length is an integer here. Where is it even created?
Address size here. Sock length_t address size.
Then we make it a pointer. No, we let's let's leave it like this.
And then we do address size, close the parentheses, and put an address in front of that.
Okay, wait.
Now the binding has failed.
Why?
Bind result is minus one. We didn't even change the bind line.
Wait, if I remove all of this, does the bind work again?
Oh, how can the line up here fail if I change lines down here? This does not make any sense.
What the Let's just eradicate the case that I'm just lacking permissions by executing this using pseudo.
Ah, this works. Why is at80 something that requires privilege?
Wait, let's do the following. We also add a we also add a an if statement here after the bind that prints the bind error.
If the bind result is smaller than zero just so we can confirm what the error is by this kernel level error message invalid argument invalid argument bind invalid argument.
The problem is that you're trying to bind more than once. What's with the while one loop? No, I'm not doing that.
But for me, the bind is outside of the loop.
I'm just trying to accept something.
And we do get address info. Socket bind.
Listen. Accept.
Get address info.
Okay. Socket. Wrong order. Bind.
Listen.
But if I execute this with pseudo then it works.
Oh god.
What happens if I go into the browser and I just access localhost at80? Does it print anything? Yeah, of course. Now it prints waiting for request because the request is being received.
I just don't get the logic why this code triggers the code up here to be working differently in any way.
So here we accept something on the socket file descriptor their address address size H.
But it's already a good sign to see that the browser trying to access this server is really triggering some change in the process here. That's great.
Then we accept.
Are we accepting? Yes, we are accepting.
accept ready to communicate on socket descriptor.
So can I now can I now just simply send something to the client? Yeah, probably I can by writing to the new socket descriptor here.
And um how do I call it? communication socket or a new file descriptor. Okay.
Int new file descriptor is this. And then if we write if we write something to that file descriptor, what happens? Let's print.
No, let's put C something to a file descriptor or put a full sentence, a full string to a file descriptor and see what happens.
then probably it's completely wrong to do it that way, but I'm just going to try it. So, this puts a string to a file descriptor. What happens if I just do hello and I write it to the new file descriptor?
This of course does not compile because I need to specify I need I need to use fputs which is the function that uses a file descriptor and puts put s defaults to standard out probably this does not work either passing argument two of fput s makes pointer from integer Okay. But the first argument is a string and the second one is a file descriptor.
So it should be okay makes pointer from integer without a cast.
Ah, this is uh expecting a file descriptor which is this file pointer thing here and I'm passing it an integer.
So to write to a file descriptor, I would need to do this maybe, which returns a file pointer new. Oh. Oh my god, this is so horrible, guys. I I just want to see quickly if I can write something to the to the client. This is definitely not correct.
Too few arguments to have open. I want to open and write something to it. So I need to pass write access.
Passing one argument. Passing argument one of f open makes pointer from integer. Again the same thing man f open.
Ah and this opens of course this opens a file. What if I use open man 3 open?
Is it even a system call? Then open I don't know if if opening a file descriptor separately is somehow required.
Ah whatever let's do it properly. Forget about it. So we have a new file descriptor here and now we want to write something.
Here's the send call. Ah, we can write something using send.
Oh my god.
So, what if I just send something to the socket file descriptor? The message is hello Daniel.
The length is I don't know. Let's not count and just pass like a maniac size of hello Daniel here. And flags. What are flags?
Flex zero. Of course, this does not even compile because I'm missing a closing parenthesis somewhere. Ah, no. So, this was wrong. I had the prefixed integer still expected closing parenthesis before semicolon token.
Of course, of course here.
Now it compiles. I now execute this. And if I try to access this, yeah, this doesn't do anything. But if I go into the networks tab, does this print anything? No, of course, because it's not an HTTP HTTP message.
And now we can send some message to this file descriptor.
We sent something and this returns the bytes sent.
What is the message that we want to send in a proper HTTP server? We should send a website.
Let's create a website.
HTML dummy here. This is now our dummy website.
Mhm. That's quite a big dummy dummy website, but okay. I'm just going to create a new file index html. pass all the data into it.
Copy. No, go to the raw website. Copy everything here.
So, this is now a dummy website. title is Daniel's website.
Daniel's website. This is the document header. Okay, perfect. So now we have an index html site and we want to serve it to the client. So we send we don't send hello Daniel.
Well actually what we do is first we receive a request from the server using this right here. H we received let's just delete all of that. Let's receive a request from the server, no from the client, sorry, to the socket file descriptor. We write the response into the buffer probably.
So that's where we need to allocate space for the request since I only expect a request an HTTP request that an HTTP requests are incredibly small as long as they are get requests.
Let's just allocate a buffer of a,000 bytes and then that's it. Let's keep it simple. Let's not bother with melo and all of that stuff.
Then let's say we expect the request here.
It will be filled in here.
The length, what's the length.
Receive returns the number of bytes actually read into the buffer or minus one on error. Okay, fine.
Length is the maximum length of the buffer. All right, so max length is here. Max length is 1,000.
Max length we pass it here and the flags what are they? Flags can again be set to zero.
Okay, happy about that. So we receive something then num bytes is the return value of receive and then we can actually print f whatever we have received.
Wait.
Yeah, it should be written into the HTTP request.
Let's call it the received received request just to be sure.
Then we print print what we have received.
Yeah.
And if we compile this, this doesn't yet work because I'm still using in the HTTP request here. Let's change it and compile. Now, still doesn't work because I am passing argument two of receive max pointer from integer without a cost.
That means I need to do this.
No.
Wait, this expects a buffer, a void pointer.
An array is super similar to a pointer. So, we can probably just cast it.
No, we can't in function main. Wait, warning format not Oh, well, that worked. The this error is something different. Format not a string literal and no format arguments. Ah wait but void pointer is actually under the hood the same thing as a character array I think. So the warning is already something else here. Format not a string literal and no format arguments. All right I'm missing format not a string literal.
This is a string. It's a character array.
warning format not a string literal and no format arguments.
I can also just do put s maybe that's happy. Oh okay. So put s is expecting just some kind of string can be unformatted and maybe we should zero out this character array just to be sure. So let's do mess set received request zero max length.
This sets all values to zero. This of course doesn't work because mems is included in string.h that we need to include first. Include string.h.
Let's go down here. Compile this. This works. Server is now running. Let's see what happens if I use the browser and connect to localhost and this port.
And something has happened but it has not printed anything to the console.
Why is that?
I would have expected that the browser sends a get request to this server.
So we should be able to see the request here because it's printed here.
But apparently that's not happening.
We are accepting a connection then receiving data.
We receive into the received request for max length up to number of bytes and put s just prints the result up to the first zero bite.
Well, we could also just do for just to be sure. Um, we could loop up to the number of bytes and print individual characters. That's actually not a good thing to do, but just to see just to confirm that it works.
Put C. Why doesn't put character works work?
Ah, we need to use put character to put to standard out.
And now this compiles.
Let's connect.
And it doesn't print anything.
So receive the number of bytes received are probably zero.
Let's check what happens if we print the number of bytes like this.
Number of bytes is minus one.
Okay, so that's an error code.
What could be the issue here? Receive returns the number of bytes actually written to the buffer or minus one on error with error number set accordingly.
This means we can probably also just print the error here. P error reception error. Let's do that without an if case just to print out our current error.
For that I need to connect again.
Transport endp point is not connected.
That's the real error we are facing right now.
What does this mean? Receive. We receive from the socket file descriptor. Maybe do we need to receive from the new file descriptor here? Because that would be the connected file descriptor.
I'm just going to experimentally replace it with the new file descriptor because where why else would we have created this refresh? Oh. Oh, and this actually worked guys. As you can see now, we have Yes. So, this is already a very very good sign. We have received this request from the browser 438 bytes and it's a get request with so that's how HTTP requests work. They are plain text separated by well they are lines of text separated by new lines carriage return line feed new lines. So it's a special type of a new line. It's trying to access this local.
So all of this is metadata and there is no payload. It could of course transport more data than that. Um but there is none because it's just a get request.
For example, if we if we had sent a post request, then there would be a body of post data data to be posted to the server. That's not the case. So we are successfully receiving the request from the server. Now all that our HTTP server no we are successfully receiving the request from the client. Now all that our HTTP server is supposed to do is check is it is it a get request.
Is it a get request? Actually we don't even handle any requests any specific request. So we can also just rep respond to any request. We don't really care.
But to be properly doing it, we can just we can just check the get request and then we return a similarly structured answer like this one.
So how do we do it? H we check is it a get request. to check if it's a get request, we just we just need to check the first three bytes from from the from the request.
So, we do a we do the following.
If I don't even know if this is the right way to do this. This is ridiculous. Oh, well, what's the what's the simplest way to get the first three characters in C?
I would have just done naively received request zero equals G.
And I mean there's no substring function, is there?
Oh, we we could use string equals equals or something like that, but oh yeah, string uh string comparison. That's what we could use.
String n comp. Ah, that's a nice function. So, this one probably compares the first n bytes of two strings. So this tells us. So here we can do something like if receive if string comparison of received request and get which is a hard-coded string and we check only the first three bytes is zero. That's the return value if both strings are equal.
Why is it highlighted red here? No idea.
Then we can print f received received http get request new line.
So uh there's some issue with my code. What is it? What is it?
string comparison too few arguments to function. Ah, of course I need to use string and compare.
This doesn't work either because too few arguments to function string and compare. Why?
String compare expected expression before equals token string compare.
Ah, of course I I found it. Okay, I I just closed this parenthesis. It shouldn't be closed there. That's a wrong parenthesis. Now it's compiling.
And if we execute it, let's see. We access, we try to access our HTTP server from the browser. Received HTTP get request.
Nice. Okay. Now that we are successfully receiving a get request, we can do we can actually put all of this in a loop. While true, we are reading and we are trying to we are receiving stuff from this connection actually should it be just one nested loop or two because we are we have now this file descriptor which is accept forget about it. Let's forget about these details. I just want to have it the simplest possible.
Server address already in use. Wait, is my process still running? Grab server.
Well, it definitely shouldn't still run.
It's not running.
Okay, now it seems to be fine again. So, let's try to access it. It receives a get request. If we refresh, ah, now the browser is trying to load it and it's waiting for a response and not doing anything. That's why it's hanging.
And if we terminate this, let's see what does the browser do. Okay, it also cancels the the connection attempt. If we try to do to restart the server, now it says address is already in use for some time probably until it is reset somehow.
This is not so nice.
Address already in use.
Yeah. Anyways, so putting it all in a Y loop is not really beneficial for now. I just want to respond if we get a get request. If we don't get a get request, we can just we can just print something. print f received nonget request ignoring.
And what does that do? That probably will wait for something.
H or we can just exit guys.
But but we will always only send get requests. So we don't even need to handle the special case.
But okay, it's being handled. And now that we are sending that we are receiving the get request we somehow need to respond with the full content of the index html in proper in a proper HTML in a proper HTTP response. So get response.
So how do we form the response? Let's let's check how we can send something here. That's how we send something. So we receive a get request respond to we respond to the HTTP to the HTTP get request here using the send system call int num probably also returns the number of bytes that were sent.
send byes.
We send to the new file descriptor which has initiated the connection to us and we sent a message. And now comes the fun part.
This is actually the reason I started this entire video. It's is because I was reading a bit about HTTP and I learned something that I didn't know before and that is HTTP messages are just plain text as long as they don't have a uh binary body. But the the starting line and the headers are all plain text. So it's super easy to actually construct an HTTP request and response. Both of them are super easy.
So what we can do here is we can create another character array.
Let's call it response.
And that's a response of size max length. No, this must be a way bigger size.
Let's call it response length.
How much uh data can we transmit here?
Let's say 10 kilobytes should be good enough, right? Yeah, I think this document should should fit.
Could be getting close. Let's just confirm this. The index html is 18 kilobytes.
Yeah. Okay. In that case, we need to set the response length to let's say 100,000 bytes, 100 kilobytes.
Then the response is a character array of response length and we send the response here. response and the length is actually the response length should be dynamically computed because we want to be able to serve any HTML file.
Hm.
So, wait, before we do all of this, let's read let's we we definitely have to read um we have to read in the file.
So something we can definitely send already is the header send we sent to the new file descriptor.
Let's just check HTTP messages here. There should be some schematic image here. That's how a request looks like. But how does a response look like here?
Oh, okay. We So, we specify the HTTP version HTTP 1.1 / 1.1.1 and then a status which is 200 and okay. And that's already it.
But we have to be careful. There needs to be a special line break after that. That is the carriage return line feed.
Let's see how do I send it.
Unix line ending is this.
Legacy Mac line ending is this. And this is the Windows line ending. Which one of those is the carriage return line feed?
Ah, it's this one.
back slash r back slashn.
Okay, so we have to do back slash r back slashn here and then we send the length.
Let's create a character array message. Now character pointer status line is equal to and then all of this.
And we send the status line here.
And the size of no the string length string length of the status line and this should already send the status line.
So if we open a server now I think it will still stay idle and not succeed in loading.
Right, we just received the HTTP get request, but the server is still waiting for the rest of the request. But we should all it should already see that we have sent something.
Let's close this and try to return also the rest of the of the HTTP response. So what's what else do we need to send from an HTTP response here? We need to send the content length and we only need we only know about the content length and and we also need to send the content type.
This is something that we can already do. So here content type is text /html.
Okay. Back slash R back slashn.
And then we do content length.
And let's just send some some dummy uh something dummyish.
We can also continue the string down here. Content length. By the way, this is something I learned from you guys. Um so if you have two strings and they are separated only by a new line here outside of the string or spaces then they are concatenated by C. This is called string uh concatenation.
I don't know I think that's it. So we can write content length here and then let's just say we send 30 bytes or 10 Then again back slash r back slashn and then we send the body the response body and the response body contains HTML. So let's just hardcode some HTML here.
And I think we still need to close with back slash r back slashn.
And then we can send the status line.
We can send the header headers. Of course, it's possible to concatenate all of those and send them as one, but I'm just going to do this now because I just like it.
Let's see if this already yields some kind of result.
No.
So it seems that this is not sufficient or something is going wrong.
Let's print the scent bytes here.
in send bytes is equal to this. Then we print send bytes.
Then we do the same thing down here and down here.
And of course we also update the send bytes after each send call.
Send bytes.
Send bytes equals this address already in use. Why is the address already in use? Probably we have to close the connection somehow once the application is done.
Ah, close and shutdown. Get out of my face. Close the socket file descriptor.
Let's close it.
We close the socket file descriptor here. And we also close the new file descriptor.
This is the worst HTTP server server ever invented by anybody. But if it works, it works.
Okay.
Now, received HTTP get request.
If I interrupt this, then nothing will be closed, of course, because it's interrupted.
So this doesn't really help at all but we can still close well we do still close at the end of this. So actually if we invoke the application and and reach the end of this then everything should be closed.
This will prevent any more reads and writes to the socket. Anyone attempting to read or write to the socket on the remote end will receive an error. Just in case you want a little more control over how the socket closes, you can use the shutdown function. It allows you to cut off communication in a certain direction or both ways. Just like close does synopsis this.
Mhm. Okay.
So, ah, of course I forgot to compile.
Oh my god, I forgot to compile. Error. Too few arguments to function send.
Why? What are we even sending?
No wonder that the browser doesn't receive anything if I don't even compile this. So, let's have a look at the BJ guide. It's probably missing a flag at the end, just like receive. Yeah, flag, of course.
Zero.
And then let's do the same thing here and here.
Compile.
Execute.
Then refresh. We have sent byes 17 each time.
Um, okay. At least I can see that the connection has closed and everything is terminated cleanly. I can now not restart it still. Why is the address still in use? I have just cleanly terminated everything here.
H.
And why is sent byes always the exact same value every time?
We print the sent byes here. We print sent byes here and here. But every time we print them, they are updated shortly before. Here.
Yeah. And here and here.
Ah because we're sending the string length of the status line always. So we restrict our our communication. So we need to set change this to the string length of the header here and here to the string length of the response body sudo server. No, don't pseudo server compile. And now it's not compiling anymore.
String length header. String length headers.
Compile.
execute and something has happened here. The browser has accepted some request. Now we don't now we see a blank screen instead of a connection error. I just don't see the message Daniel which I would have expected. So something is working but something else is not working. I don't know what exactly is working and what is not working. And I also can't quickly retest it because the address is already in use. I don't get why see socket address already in use. But in in general it's a great it's a great um intermediate progress here that we have done.
Reuse address only allows limited reuse of ports. Specifically, it does not allow reuse of a port that some other socket is currently actively listening.
H there seems to be an epidemic here of people calling bind and then set sock opt wondering why the set sock opt doesn't fix an error that had already happened on bind.
What about this address here reuse thing? Let's go to the BJ guide and here set sock opt reuse address and after that we bind what's this yes thing.
Yes is one. Great.
So O reuse address. Is it explained in the text anywhere? Yeah.
What do I do when bind reports? Ah address already in use. You have to use set sock opt with the reuse address option on the listening socket. Check out the section on bind and the section on select for an example.
Okay.
There's my example here.
There it is.
Here. Sometimes you might not notice you try to rerun a server and bind fails claiming address already in use. What does that mean? Well, a little bit of a socket that was a little bit of a socket that was connected is still hanging hanging around in the kernel and it's hogging the port. You can either wait for it to clear a minute or so or add code to your program allowing it to reuse the port like this.
Okay, I want to do this.
Give me my socket, guys.
Okay.
Loose. Get rid of.
Okay.
Now, if I compile, this doesn't work. Of course, conflicting types for yes have character. Oh, I just uh blindly did something here. Of course, it's an integer only and I don't need a separate character for the yes flag. Now, listener is what the socket FD and this compiles. If we execute the server now and go to our browser, let's open the inspector for the networks network thing and refresh.
So, okay, that's a good sign. The browser sends a get request here. The browser sends a get request and the get request succeeds. So, our response code 200. Okay, that we are manually sending here that's this thing right here is working.
The only issue is that the response is not being parsed. So here it says no response data available for this request. And also if we inspect and look at the network tab then there's an error to because the the browser tries to get an icon for this website. Of course there's none for that but of okay in general it seems to work.
So now we it's the browser is even receiving the content length header that we are setting and the content type only the response data is somehow empty.
We are setting the content length to 10 and that might be too little. Let's just count the content length here. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bytes.
So maybe it works if we're changing the 10 to 17.
And then refresh.
And no, it still does not work. The headers returned now contain content length 17 which is good but the request is not working.
Why is that?
We probably need to look again at the formatting of a of an HTTP response body. Ah, it's not put into HTML tags and also not into a body.
Is that maybe the issue? HTML HTM HTML like this and body and body.
Let's see if we can access this.
No.
But of course the content length is now wrong again.
How about we do the following?
Oh no. Somehow something's something is really wrong here. The content length is I don't want to count this myself. No, the content length is the Oh, we would have to dynamically enter this somehow.
using sprint f probably is the content length even required for HTML. Is content length header required?
When no content length header is received, the client keeps reading until the server closes the connection. So maybe we can remove the content length header and try again.
But no response data is being fetched.
Ah, because I haven't even compiled.
Let's try again.
No.
How? Why?
HTML. No, HTTP response format delimiter something. What is that?
HTTP responses. There's always this gap after the HTTP headers. Is this maybe missing in our case HTTP response format line break.
Maybe we need two of those line feet carriage return separators.
So, let me just put the response body down here again. Then content type is this. And let's put another one here just to indicate that this is definitely the only header. Then we compile, execute, and now a yes.
Okay, we're back in the game, guys. Now we have Holy smokes. We have a working HTTP server, guys. Now we only need to send we only need to serve the real HTML file. And how do we do that? That should be fairly simple. We all we need to do is instead of hard coding the response body, we just loop. No, we just open the index. HTML file in the current directory.
This of course assumes now that the executable resides in the same directory as the index html. Well, fine. For this project, we use read only permissions.
This returns a file descriptor int index or yeah, let's call it index file descriptor. And then we create a response body. Let's do it super inefficiently. The more inefficient it is, the more fun it is. Then let's just send individual bytes here. So we read we have open from the file descriptor and while we get characters from no we we get characters from the file descriptor um let's call it yeah C while C C equals equals get character from this file descriptor. Oh, no, that's not an integer up here.
That's a file pointer.
Get C from the index file pointer or index file.
And as long as we haven't reached the end of the file, we we just send that bite.
It's such a cheap way to do it, guys.
Oh god. But it's fun. If this works, I'm going to freak out. We're just going to send that individual byes. Of course, I know we could we could assemble all of it. I don't care. I just want to see it working now. And then we don't need to print f in the end here anymore because here we sent thousands of times individual bytes. So, let's try to compile it. That doesn't work. Response body is undeclared, of course. Where's response body? There it is. We we sent individual bytes over TCP IP. This compiles almost we send something's wrong here.
Well, the send function is of course expecting a buffer.
So we may maybe have to pass the address of that individual character that compiles. Now let's surf everything or let's surf everything after cleaning up this terminal. And if this works, this was going to be crazy. Now let's refresh.
Yes.
And as you can see, we have a working HTTP server. Guys, this server is serving a website. We have received the sample website which we have put in the index html before and we're accessing it over local host over port at80. How awesome is that? We have done it with zero capabilities. We have just hacked it like maniacs. Hacked everything together like maniacs. And now we have a working HTTP server. Of course, it's just accepting a single request and closing. And of course, it's just transferring everything inefficiently, but it's working. And that's what counts. We have learned something here and it's working. We have we are serving a working HTML website here.
Oh, I don't even know what to say now.
It's that's awesome. So, of of the 150 or 160 projects I already did on this channel, this is one of the the big ones that marks a milestone in my /c capabilities to get this working. We need to know socket programming. We need to know how to read files. We need to know about HTTP and TCP IP. So, this is not a trivial thing to do. Of course, we have done it in a totally trash manner and but but it works. It's awesome. And if you also want to become such a practical C programmer, then have a look at the non nononsense C programming course.
It's a course that I have created to teach you C in a practical way. No boring theory and it's linked in the description below the video. Check it out. It takes you from zero to capable C programmer.
in the most efficient way. Again, no boring theory, only practical lessons and exercises, all done in the terminal and all using real useful code, guaranteed. All right, this was super enjoyable. Let's just for one last time open the server and let's maybe from an incognito tab just to show that this is working on a fresh connection access local host on AT80 and it's really serving that website. How awesome is that? So we have a working HTTP server.
That's awesome. I'm going to enjoy the rest of my day now and I hope you will also enjoy your the rest of your day.
Um, put any ideas for future projects or video topics in the comments and then see you next time. Bye-bye.
Related Videos
Agentforce NOW AMA: Build with React and Salesforce Multi-Framework
SalesforceDevs
490 views•2026-05-28
How agent o11y differs from traditional o11y — Phil Hetzel, Braintrust
aiDotEngineer
450 views•2026-05-28
Re: 🗣️📍theprophedu📍2026 GST 103 CLASS (E-EXAM REVISION)
theprophedu
636 views•2026-06-04
WEB TECHNOLOGIES UNIT-2 | Degree 4th sem BCOM Computers web technologies unit-2 full explanation💯✅
LearnwithSahera
1K views•2026-05-29
More tests are always better? How to use AI to identify tests that bring little value
Alliance4Qualification
335 views•2026-05-29
Search Algorithms Explained in 60 Seconds! 🤖💨
samarthtuliofficial
218 views•2026-06-01
People of Game of Thrones using JavaScript DOM
AltCampus
296 views•2026-05-30
Instagram accounts got PWNed
EricParker
13K views•2026-06-03











