Skip to content

Commit

Permalink
stage-1, stage-2 polished
Browse files Browse the repository at this point in the history
  • Loading branch information
aadhavanpl committed Mar 26, 2024
1 parent ec3b3bc commit 13e0da0
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 104 deletions.
9 changes: 5 additions & 4 deletions docs/.vitepress/theme/custom.css
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/* CUSTOM CSS */
.main h1.name span.clip, .main p.text {
.main h1.name span.clip,
.main p.text {
letter-spacing: -0.17rem;
}
}

.main .tagline {
.main .tagline {
letter-spacing: -0.045rem;
}
}

/**
* Colors: Solid
Expand Down
4 changes: 2 additions & 2 deletions docs/roadmap/phase-0/stage-0.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ Any distribution of Linux can be used.

### C compiler - gcc

Since we will be building the project primarily in C programming language, we will need a C compiler. We will stick with the gcc compiler for this purpose.
Since we will be building the project primarily in C programming language, we will need a C compiler. We will stick with the popular `gcc` compiler for this purpose.

### GDB
### GNU Debugger - GDB

Read the section on [GDB](https://nitcbase.github.io/docs/Misc/GDB) to learn how to use the GNU debugger.

Expand Down
124 changes: 70 additions & 54 deletions docs/roadmap/phase-0/stage-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,31 @@

## Recap

In Phase 0 overview,

- We learnt about the Client-Server architecture.
- We learnt about the TCP protocol.
- We learnt how sockets facilitate the communication between clients and servers.

## Introduction

How does a server function? It listens for any connections, i.e. clients that are trying to connect to the server, and based on the request from the client, it does some specific operation.
How does a server function? It listens for any connections, i.e. clients that are trying to connect to the server, and based on the request from the client it does some specific operation.

To be able to listen for connections, it needs **listening sockets**. Listening sockets are bound to a specific IP address and a port. If any client wants to connect to the server, they have to direct their request to this particular IP:port combo that the server is listening to.
To be able to listen for connections, it needs **listening sockets**. Listening \*\*\*\*sockets are bound to a specific IP address (interface) and a port. If any client wants to connect to the server, they have to direct their request to this particular IP:port combo that the server is listening on.

For eg. Let us assume you have a TCP server ‘running’ on your computer on port 8000. Running signifies that the server is ‘listening’ for any connections on port 8000. If a client wants to connect to the server, they would have to direct their request to `<IP_address_of_computer>:8000`.
For example, let us assume you have a TCP server ‘running’ on your computer on port 8080. Running signifies that the server is ‘listening’ for any connections on port 8080. If a client wants to connect to the server, they would have to direct their request to `<IP_address_of_computer>:8080`.

Using this knowledge, let us build a simple TCP server from the ground up. Since this is just Stage 1, you will be guided throughout the implementation with all the code given in the form of snippets.

Create an illustration of the working of server.

## Implementation

![implementation.png](/assets/stage-1/implementation.png)

All the code from this stage will be written in `stage1/tcp_server.c`.
Now that we are going to start coding, lets create a `expserver` folder. All files related to our project will go inside it. Create another folder `phase_0` inside `expserver` which will contain the files we will be creating in Phase 0.

For this stage, as we are creating a tcp server, create a file `expserver/phase_0/tcp_server.c`

The header files required for this stage are given below.
Let’s start by adding all the header includes and defines. The use of each header will be explained as we proceed further.

```c
#include <arpa/inet.h>
Expand All @@ -36,15 +38,16 @@ The header files required for this stage are given below.
#include <sys/socket.h>
#include <unistd.h>

#define SERVER_PORT 8080
#define PORT 8080
#define BUFF_SIZE 10000
#define MAX_ACCEPT_BACKLOG 5
```
For the clients to be able to connect to the server, let us create a listening socket using the `socket()` function from the `<sys/socket.h>` library.
The first step is to create a listening socket for the clients to be able to connect to the server. This is done using the `socket()` function from the `<sys/socket.h>` header.
```c
int main() {
// creating a listening socket
// Creating listening sock
int listen_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
```

Expand All @@ -53,55 +56,58 @@ The `socket()` function creates a socket, and upon successful creation, returns
The function takes three arguments:

- domain: `AF_INET` - IPv4
- type: `SOCK_STREAM` - TCP
- protocol: `0` - allows system to choose the appropriate protocol based on domain and type
- type: `SOCK_STREAM` - Socket of type stream (used for TCP)
- protocol: `0` - allows system to choose the appropriate protocol based on domain and type (TCP)

```c
// setting sock opt reuse addr
// Setting sock opt reuse addr
int enable = 1;
setsockopt(listen_sock_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
```
This is optional but it will help us bypass the `TIME_WAIT` state that the address and port combination enters when the server is shutdown, thus allowing us to reuse the same address and port.
This will help us bypass the `TIME_WAIT` state that the address and port combination enters when the server is shutdown, thus allowing us to reuse the same address and port.
Now that we have created a socket, we need to specify the address that it needs to listen to. For this we will use an object of `struct socketaddr_in` provided by the `<netinet/in.h>` library.
Now that we have created a socket, we need to specify the address that it needs to listen to. For this we will be using an object of `struct sockaddr_in` provided by the `<netinet/in.h>` header.
```c
// creating an object of struct socketaddr_in
// Creating an object of struct socketaddr_in
struct sockaddr_in server_addr;
// setting up server addr
// Setting up server addr
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
```

- `server_addr.sin_family = AF_INET` - socket will use the IPv4 address format
- `server_addr.sin_addr.s_addr = htonl(INADDR_ANY)` - allows the server to accept connections from any client (explain htonl)
- `server_addr.sin_port = htons(PORT)` - sets the port number (explain htons)
- `server_addr.sin_addr.s_addr = htonl(INADDR_ANY)` - INADDR*ANY constant stands for \_any* IP address. When using this for the server address, this lets the server bind to all network interfaces available on the machine. `htonl()` is a function that will convert from host byte order to network byte order for `long` type.
- `server_addr.sin_port = htons(PORT)` - sets the port number. `htons()` converts from host byte order to network byte order for `short` type.

```c
// binding listening sock to port
// Binding listening sock to port
bind(listen_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));

// starting to listen
// Starting to listen
listen(listen_sock_fd, MAX_ACCEPT_BACKLOG);
printf("[INFO] Server listening on port %d\n", PORT);
```
The `bind()`function, provided by the `<sys/socket.h>` library, will bind the listening socket to the provided port. The `listen()` function marks the socket as a passive socket, ready to accept incoming connection requests.
The `bind()`function, provided by the `<sys/socket.h>` header, will bind the listening socket to the provided port. The `listen()` function marks the socket as a passive socket, ready to accept incoming connection requests.
---
::: danger QUESTION
Now that the server is listening on the port, what happens when a client tries to connect to the server?
:::
We need to get the address of the clients that are connecting to the server. For this lets create another object of `struct sockaddr_in`.
```c
// creating an object of struct socketaddr_in
// Creating an object of struct socketaddr_in
struct sockaddr_in client_addr;
int client_addr_len;
// Accept client connection
int conn_sock_fd = accept(listen_sock_fd, (struct sockaddr *)&client_addr, &client_addr_len);
printf("[INFO] Client connected to server\n");
```
Expand All @@ -115,6 +121,8 @@ Let’s pause for a bit and recap what just happened.
- The socket is made to listen for connections on that port using `listen()` function
- The socket accepts a connection from a client with the `accept()` function

---

### Milestone #1

We can now do a small test and check how the code performs.
Expand All @@ -133,11 +141,15 @@ To start the server, use the following command:

On running the TCP server, it will display the following message:

```Server
```bash
[INFO] Server listening on port 8080
```

But what/who is going to connect to the server? Since we have not created a TCP client yet, lets use a networking utility tool called _netcat_. netcat is a versatile tool that has a wide range of functionalities including the ability to act as a TCP client.
But what/who is going to connect to the server? Since we have not created a TCP client yet, lets use a networking utility tool called _netcat_.

::: info
netcat is a versatile tool that has a wide range of functionalities including the ability to act as a TCP client.
:::

netcat takes an IP address and a port to connect to. In our case, since the server is running on the same machine, we can use `[localhost](http://localhost)` as the IP address and 8080 as the port number.

Expand All @@ -149,7 +161,7 @@ nc localhost 8080

When the client connection to the server is successful, it will show the following message:

```Server
```bash
[INFO] Server listening on port 8080
[INFO] Client connected to server
```
Expand All @@ -166,14 +178,15 @@ Initialize a `char` buffer to store the client message.

```c
while (1) {
// Create buffer to store client message
char buff[BUFF_SIZE];
memset(buff, 0, BUFF_SIZE);
```
The `memset` function is initialize the value of `buff` to 0.
```c
// read message from client to buffer
// Read message from client to buffer
int read_n = recv(conn_sock_fd, buff, sizeof(buff), 0);
```

Expand All @@ -182,21 +195,21 @@ The `recv()` function is used to receive data from the connected socket. The rec
Let’s take care of some error handling in case of unexpected failure.

```c
// client closed connection or error occurred
// Client closed connection or error occurred
if (read_n <= 0) {
printf("[INFO] Client disconnected. Closing server\n");
close(conn_sock_fd);
exit(1);
}

// print message from cilent
printf("[CLIENT MESSAGE] %s\n", buff);
// Print message from client
printf("[CLIENT MESSAGE] %s", buff);
```
`buff` now contains the message sent by the client. The server has to reverse the message string and send it back to the client. Let us write a quick and simple string reversal function to take care of this and place it outside of the main function.
```c
// function to reverse a string
// Function to reverse a string
void strrev(char *str) {
for (int start = 0, end = strlen(str) - 2; start < end; start++, end--) {
char temp = str[start];
Expand All @@ -206,13 +219,13 @@ void strrev(char *str) {
}
```

Now that `buff` has the reversed string, its time to send it to the client. We can use the `send()` function provided by the `<sys/socket.h>` library to achieve this.
Now that `buff` has the reversed string, its time to send it to the client. We can use the `send()` function provided by the `<sys/socket.h>` header to achieve this.

```c
// string reverse
// Sting reverse
strrev(buff);

// sending client message to server
// Sending reversed string to client
send(conn_sock_fd, buff, read_n, 0);
}
}
Expand All @@ -222,7 +235,7 @@ Now that `buff` has the reversed string, its time to send it to the client. We c
The final code should look like this.
::: details Code
::: details expserver/phase_0/tcp_server.c
```c
#include <arpa/inet.h>
Expand All @@ -235,10 +248,10 @@ The final code should look like this.
#include <unistd.h>
#define PORT 8080
#define BUFF_SIZE 10096
#define BUFF_SIZE 10000
#define MAX_ACCEPT_BACKLOG 5
// function to reverse a string
// Function to reverse a string
void strrev(char *str) {
for (int start = 0, end = strlen(str) - 2; start < end; start++, end--) {
char temp = str[start];
Expand All @@ -248,62 +261,65 @@ void strrev(char *str) {
}
int main() {
// creating listening sock
// Creating listening sock
int listen_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
// setting sock opt reuse addr
// Setting sock opt reuse addr
int enable = 1;
setsockopt(listen_sock_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
// creating an object of struct socketaddr_in
// Creating an object of struct socketaddr_in
struct sockaddr_in server_addr;
// setting up server addr
// Setting up server addr
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
// binding listening sock to port
// Binding listening sock to port
bind(listen_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
// starting to listen
// Starting to listen
listen(listen_sock_fd, MAX_ACCEPT_BACKLOG);
printf("[INFO] Server listening on port %d\n", PORT);
// creating an object of struct socketaddr_in
// Creating an object of struct socketaddr_in
struct sockaddr_in client_addr;
int client_addr_len;
// Accept client connection
int conn_sock_fd = accept(listen_sock_fd, (struct sockaddr *)&client_addr, &client_addr_len);
printf("[INFO] Client connected to server\n");
while (1) {
// create buffer to store client message
// Create buffer to store client message
char buff[BUFF_SIZE];
memset(buff, 0, BUFF_SIZE);
// read message from client to buffer
// Read message from client to buffer
int read_n = recv(conn_sock_fd, buff, sizeof(buff), 0);
// client closed connection or error occurred
// Client closed connection or error occurred
if (read_n <= 0) {
printf("[INFO] Client disconnected. Closing server\n");
close(conn_sock_fd);
exit(1);
}
// print message from client
// Print message from client
printf("[CLIENT MESSAGE] %s", buff);
// sting reverse
// Sting reverse
strrev(buff);
// sending client message to server
// Sending reversed string to client
send(conn_sock_fd, buff, read_n, 0);
}
}
```

---

:::

### Milestone #2
Expand All @@ -312,26 +328,26 @@ It’s time to test the server! Similar to last time, open 2 terminals, one for

Upon successful connection of the client to the server, the server terminal should look like this:

```Server
```bash
[INFO] Server listening on port 8080
[INFO] Client connected to server
```

Let us try to send a string from the client terminal:

```Client
```bash
hello
```

The server will receive the message sent by the client, and should send a response back to the client with the reversed string.

```Server
```bash
[CLIENT MESSAGE] hello
```

The client will receive the reversed string.

```Client
```bash
olleh
```

Expand Down
Loading

0 comments on commit 13e0da0

Please sign in to comment.