fix client disconnection

This commit is contained in:
2024-11-26 17:16:04 +01:00
parent 718dbd50c0
commit 6cf2d5f198
13 changed files with 819 additions and 76 deletions

View File

@@ -6,7 +6,10 @@
- [epoll() - I/O Event Notification (Async)](#epoll---io-event-notification-async)
### Blocking
All the Unix networking functions are **blocking**. What does it mean? It means that if you write `accept()` or `recv()` it will wait until some data appears. So how we can avoid this? Making the socket non blocking so we can poll the socket for info:
All the Unix networking functions are **blocking**. What does it mean? It means that if you write `accept()` or `recv()`
it will wait until some data appears. So how we can avoid this? Making the socket non blocking so we can poll the socket
for info:
```c
fcntl(sockfd, F_SETFL, O_NONBLOCK);
@@ -16,7 +19,9 @@ fcntl(sockfd, F_SETFL, O_NONBLOCK);
`O_NONBLOCK`: make the fd non blocking
### poll() - Synchronous I/O Multiplexing
The plan is to have a `struct pollfd` with the info about which sockets fd we want to monitor. The Operating System will block on the `poll()` call until one event occurs (e.g. "socket ready to write/read").
The plan is to have a `struct pollfd` with the info about which sockets fd we want to monitor. The Operating System will
block on the `poll()` call until one event occurs (e.g. "socket ready to write/read").
```c
#include <poll.h>
@@ -37,13 +42,16 @@ struct pollfd {
```
`events` is a bitmap of the following values:
- `POLLIN` (alert when I can read data)
- `POLLOUT` (alert when I can send data)
**I won't continue this section, read below**
### epoll() - I/O Event Notification (Async)
It is similar to `poll()` but more efficient when dealing with lots of fds. The array is in the kernel space, no further copies. Nice explaination [here](https://copyconstruct.medium.com/the-method-to-epolls-madness-d9d2d6378642).
It is similar to `poll()` but more efficient when dealing with lots of fds. The array is in the kernel space, no further
copies. Nice explaination [here](https://copyconstruct.medium.com/the-method-to-epolls-madness-d9d2d6378642).
```c
#include <sys/epoll.h>
@@ -51,7 +59,8 @@ It is similar to `poll()` but more efficient when dealing with lots of fds. The
int epoll_create1(int flags);
```
Just pass 0 for the `flags`, it is an improved version of the `epoll_create()`. It creates a new epoll instance and returns the fd of that instance.
Just pass 0 for the `flags`, it is an improved version of the `epoll_create()`. It creates a new epoll instance and
returns the fd of that instance.
```c
#include <sys/epoll.h>

View File

@@ -1,4 +1,5 @@
# Basic Concepts
Before reading, this document could contain errors, please check everything you read.
- [Basic Concepts](#basic-concepts)
@@ -6,10 +7,10 @@ Before reading, this document could contain errors, please check everything you
- [Internet sockets](#internet-sockets)
- [Byte Order](#byte-order)
- [Structs](#structs)
- [struct addrinfo](#struct-addrinfo)
- [struct sockaddr](#struct-sockaddr)
- [struct sockaddr\_in](#struct-sockaddr_in)
- [struct sockaddr\_storage](#struct-sockaddr_storage)
- [struct addrinfo](#struct-addrinfo)
- [struct sockaddr](#struct-sockaddr)
- [struct sockaddr\_in](#struct-sockaddr_in)
- [struct sockaddr\_storage](#struct-sockaddr_storage)
- [IP Addresses](#ip-addresses)
- [getaddrinfo()](#getaddrinfo)
- [socket()](#socket-1)
@@ -23,23 +24,29 @@ Before reading, this document could contain errors, please check everything you
- [getpeername()](#getpeername)
- [gethostname()](#gethostname)
### Socket
When Unix programs do some I/O they do it reading/writing to a **file descriptor**. A file descriptor is an integer associated with an open file, it can be anything. To communicate over the internet using a file descriptor we'll make a call to the `socket()` system routine.
When Unix programs do some I/O they do it reading/writing to a **file descriptor**. A file descriptor is an integer
associated with an open file, it can be anything. To communicate over the internet using a file descriptor we'll make a
call to the `socket()` system routine.
### Internet sockets
There are two types of Internet sockets:
1. Stream Sockets (SOCK_STREAM) - error-free and a realiable two-way communication (TCP)
2. Datagram Sockets (SOCK_DGRAM) - connectionless (UDP)
### Byte Order
- Big-Endian (also called **Network Byte Order**)
- Little-Endian
Before making any transmission we have to convert the byte order to a Network Byte Order, we can do this with simple functions:
Before making any transmission we have to convert the byte order to a Network Byte Order, we can do this with simple
functions:
| Function | Description |
| -------- | --------------------- |
|----------|-----------------------|
| htons() | Host to Network Short |
| htonl() | Host to Network Long |
| ntohs() | Network to Host Short |
@@ -48,7 +55,9 @@ Before making any transmission we have to convert the byte order to a Network By
And convert the answer to the host byte order.
### Structs
#### struct addrinfo
This struct prepares the socket address strcutures for subsequent use.
```c
@@ -66,6 +75,7 @@ struct addrinfo {
```
#### struct sockaddr
Inside the struct we can see there is a pointer to the `struct sockaddr`, that is defined as follows:
```c
@@ -76,7 +86,9 @@ struct sockaddr {
```
#### struct sockaddr_in
But, we can avoid to pack manually the stuff inside this struct and use the `struct sockaddr_in` (or `struct sockaddr_in6` for IPv6) with a fast cast that is made for the Internet:
But, we can avoid to pack manually the stuff inside this struct and use the `struct sockaddr_in` (or
`struct sockaddr_in6` for IPv6) with a fast cast that is made for the Internet:
```c
struct sockaddr_in {
@@ -90,7 +102,10 @@ struct sockaddr_in {
`sin_zero` is used to pad the struct to the length of a sockaddr and it should be set to all zeros (`memset()`).
#### struct sockaddr_storage
This struct is designed to storage both IPv4 and IPv6 structures. Example, when a client is going to connect to your server you don't know if it is a IPv4 or IPv6 so you use this struct and then cast to what you need (check the `ss_family` first).
This struct is designed to storage both IPv4 and IPv6 structures. Example, when a client is going to connect to your
server you don't know if it is a IPv4 or IPv6 so you use this struct and then cast to what you need (check the
`ss_family` first).
```c
struct sockaddr_storage {
@@ -104,6 +119,7 @@ struct sockaddr_storage {
```
### IP Addresses
Here's a way to convert an IP address string into a struct.
```c
@@ -114,9 +130,11 @@ inet_pton(AF_INET, "192.168.0.1", &(sa.sin_addr));
inet_pton(AF_INET6, "2001:db8:63b3:1::3490", &(sa6.sin6_addr));
```
There is a very easy function called `inet_pton()`, *pton* stands for **Presentation to network**. If you want to do the same but from binary to string you have `inet_ntop()`.
There is a very easy function called `inet_pton()`, *pton* stands for **Presentation to network**. If you want to do the
same but from binary to string you have `inet_ntop()`.
### getaddrinfo()
```c
#include <sys/types.h>
#include <sys/socket.h>
@@ -128,10 +146,12 @@ int getaddrinfo(const char *node, // e.g. "www.example.com" or IP
struct addrinfo **res);
```
The `node` could be a host name or IP address. `service` could be a port number or a service found in `/etc/services` (e.g. "http" or "ftp" or "telnet").
The `node` could be a host name or IP address. `service` could be a port number or a service found in `/etc/services` (
e.g. "http" or "ftp" or "telnet").
`hints` points to a struct you already filled and `res` contains a linked list of results.
### socket()
```c
#include <sys/types.h>
#include <sys/socket.h>
@@ -139,10 +159,13 @@ The `node` could be a host name or IP address. `service` could be a port number
int socket(int domain, int type, int protocol);
```
`domain` could be `PF_INET` or `PF_INET6`, `type` instead TCP or UDP and `protocol` to 0. `PF_INET` is very close to `AF_INET`! However, we can avoid to put the stuff manually and use the results from `getaddrinfo()`.
`domain` could be `PF_INET` or `PF_INET6`, `type` instead TCP or UDP and `protocol` to 0. `PF_INET` is very close to
`AF_INET`! However, we can avoid to put the stuff manually and use the results from `getaddrinfo()`.
### bind()
Do this if you're going to listen on a port. The port is used by the kernel to match an incoming packet to a socket descriptor.
Do this if you're going to listen on a port. The port is used by the kernel to match an incoming packet to a socket
descriptor.
```c
#include <sys/types.h>
@@ -152,6 +175,7 @@ int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
```
### connect()
```c
#include <sys/types.h>
#include <sys/socket.h>
@@ -160,13 +184,16 @@ int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
```
### listen()
```c
int listen(int sockfd, int backlog);
```
`backlog` is the amount of max clients allowed on the incoming queue. A client will wait in the queue until you accept it.
`backlog` is the amount of max clients allowed on the incoming queue. A client will wait in the queue until you accept
it.
### accept()
```c
#include <sys/types.h>
#include <sys/socket.h>
@@ -177,21 +204,27 @@ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
After you accept a client, the function will return a new socket file descriptor used to communicate.
### send() and recv()
```c
int send(int sockfd, const void *msg, int len, int flags);
```
Just put `flags` to 0. It will return the bytes sent, but sometimes it could not match the len of the data sent, it's up to you to send the rest of the string (it should sent 1K of data without splitting).
Just put `flags` to 0. It will return the bytes sent, but sometimes it could not match the len of the data sent, it's up
to you to send the rest of the string (it should sent 1K of data without splitting).
```c
int recv(int sockfd, void *buf, int len, int flags);
```
Put 0 at `flags` (see the man page for more info). The `sockfd` is the file descriptor to read from. The function could return 0 (this means the remote side has closed the connection).
Put 0 at `flags` (see the man page for more info). The `sockfd` is the file descriptor to read from. The function could
return 0 (this means the remote side has closed the connection).
### sendto() and recvfrom()
It's the DGRAM equivalent of STREAM. Marked as *TODO*.
### close() and shutdown()
To close the connection just use the regular Unix file descriptor `close()` function:
```c
@@ -205,14 +238,15 @@ int shutdown(int sockfd, int how);
```
`how` is one of the following:
| how | Effect |
| how | Effect |
| --- | ------------------------ |
| 0 | No future receives |
| 1 | No future sends |
| 2 | No future receives/sends |
| 0 | No future receives |
| 1 | No future sends |
| 2 | No future receives/sends |
The 2 is like `close()`, use `close()`.
### getpeername()
This function is quite simple. It will tell you who is in the other side of the connection.
```c
@@ -222,6 +256,7 @@ int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
```
Example of getting client's IP:
```c
struct sockaddr_storage their;
socklen_t their_len = sizeof their;
@@ -234,6 +269,7 @@ inet_ntop(AF_INET, &client->sin_addr, client_ip, INET_ADDRSTRLEN);
```
### gethostname()
```c
#include <unistd.h>

View File

@@ -1,19 +1,25 @@
# Hash Table
Well, the moment has come. I never made a Hash Map algorithm, but in this scenario I have to save both fd and sockaddr (I could make a simply linked list, but it's a way to learn new things).
Well, the moment has come. I never made a Hash Map algorithm, but in this scenario I have to save both fd and sockaddr (
I could make a simply linked list, but it's a way to learn new things).
Let's start with a little bit of theory.
A *Hash Table* is a data structure also called **dictionary** or **map**. It maps *keys* to *values* thanks to a **hash function** that computes and *index* (**hash code**) into an array of **buckets**.
A *Hash Table* is a data structure also called **dictionary** or **map**. It maps *keys* to *values* thanks to a **hash
function** that computes and *index* (**hash code**) into an array of **buckets**.
One problem could be the *hash collision* where the hash function computes the same index for different values. A fix could be the **chaining** method, where for the same hash code you can make a linked list and append the index. Then the lookup function will go through the list and find the key.
One problem could be the *hash collision* where the hash function computes the same index for different values. A fix
could be the **chaining** method, where for the same hash code you can make a linked list and append the index. Then the
lookup function will go through the list and find the key.
In a Hash Map you can insert, delete and lookup (simply search).
### Hash function
The easiest way... don't judge me.
- Integer keys:
$$ hash(\text{key}) = \text{key} \mod \text{table\_dim} $$
$$ hash(\text{key}) = \text{key} \mod \text{table\_dim} $$
- String keys:
$$ hash(key) = \sum_{i=0}^{len(key) - 1} ascii\_value(key[i]) * prime\_number$$
$$ hash(key) = \sum_{i=0}^{len(key) - 1} ascii\_value(key[i]) * prime\_number$$

View File

@@ -1,4 +1,5 @@
# HTTP Request
This is an example of a basic HTTP request made from the browser:
```bash
@@ -10,12 +11,16 @@ Accept-Encoding: gzip, deflate
Connection: Keep-Alive
```
> Thanks tutorialspoint
The first line is a *request line*. It has:
- Method (GET, POST, HEAD, ...)
- Location (the request resource, file)
- HTTP version
# HTTP Response
```bash
HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n