add epoll
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# cws
|
# cws
|
||||||
A simple Web Server written in C (learning purposes)
|
A simple Web Server written in C (learning purposes), it works only on Linux systems
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- [meson](https://mesonbuild.com/index.html)
|
- [meson](https://mesonbuild.com/index.html)
|
||||||
@@ -13,9 +13,10 @@ $ meson compile
|
|||||||
And then run `cws`!
|
And then run `cws`!
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
- [ ] Understading basic web server concepts
|
- [x] Understading basic web server concepts
|
||||||
- [ ] Basic server
|
- [ ] Basic server
|
||||||
- [ ] Enhance web server
|
- [ ] Enhance web server
|
||||||
|
- [ ] IPv6 compatible
|
||||||
- [ ] Request parser (methods and headers)
|
- [ ] Request parser (methods and headers)
|
||||||
- [ ] Serve static files
|
- [ ] Serve static files
|
||||||
- [ ] Multithreading (non blocking I/O with `epoll`)
|
- [ ] Multithreading (non blocking I/O with `epoll`)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
int test_client_connection(const char *hostname, const char *port);
|
int test_client_connection(const char *hostname, const char *port);
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,21 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
#define PORT 3030
|
#define PORT 3030
|
||||||
// how many pending connections the queue will hold
|
|
||||||
#define BACKLOG 10
|
#define BACKLOG 10
|
||||||
|
#define EPOLL_MAXEVENTS 10
|
||||||
|
#define EPOLL_TIMEOUT -1
|
||||||
|
|
||||||
int start_server(const char *hostname, const char *service);
|
int start_server(const char *hostname, const char *service);
|
||||||
|
void setup_hints(struct addrinfo *hints, size_t len, const char *hostname);
|
||||||
|
void handle_clients(int sockfd);
|
||||||
|
void epoll_ctl_add(int epfd, int sockfd, uint32_t events);
|
||||||
|
void setnonblocking(int sockfd);
|
||||||
|
void handle_new_client(int sockfd);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
89
notes/advanced-techniques.md
Normal file
89
notes/advanced-techniques.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# Advanced Techniques
|
||||||
|
|
||||||
|
- [Advanced Techniques](#advanced-techniques)
|
||||||
|
- [Blocking](#blocking)
|
||||||
|
- [poll() - Synchronous I/O Multiplexing](#poll---synchronous-io-multiplexing)
|
||||||
|
- [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:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fcntl(sockfd, F_SETFL, O_NONBLOCK);
|
||||||
|
```
|
||||||
|
|
||||||
|
`F_SETFL`: set the file descriptor flags
|
||||||
|
`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").
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
|
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
|
||||||
|
```
|
||||||
|
|
||||||
|
`fds` is our array of info (which sockets to monitor)
|
||||||
|
`nfds` the elements in the array
|
||||||
|
`timeout` in milliseconds (-1 to wait forever)
|
||||||
|
|
||||||
|
```c
|
||||||
|
struct pollfd {
|
||||||
|
int fd; // the socket descriptor
|
||||||
|
short events; // bitmap of events we're interested in
|
||||||
|
short revents; // when poll() returns, bitmap of events that occurred
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
`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).
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
|
||||||
|
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 nad returns the fd of that instance.
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
|
||||||
|
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
|
||||||
|
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
|
||||||
|
```
|
||||||
|
|
||||||
|
- `epoll_ctl()` is used to register new fds and add them to the interest list:
|
||||||
|
- `epfd` epoll file descriptor
|
||||||
|
- `op` the operation: `EPOLL_CTL_ADD`, `EPOLL_CTL_MOD` or `EPOLL_CTL_DEL`
|
||||||
|
- `fd` the file descriptor to check
|
||||||
|
- `event` a pointer to the `epoll_event` struct
|
||||||
|
|
||||||
|
- `epoll_wait()` waits for I/O events, blocking the calling thread if no events are availables.
|
||||||
|
- `epfd` epoll file descriptor
|
||||||
|
- `events` array where there will be saved ready events
|
||||||
|
- `maxevents` max events per array
|
||||||
|
- `timeout` in milliseconds (-1 forever)
|
||||||
|
|
||||||
|
```c
|
||||||
|
struct epoll_event {
|
||||||
|
uint32_t events; // bitmap
|
||||||
|
epoll_data_t data;
|
||||||
|
};
|
||||||
|
|
||||||
|
union epoll_data {
|
||||||
|
void *ptr;
|
||||||
|
int fd; // just use this
|
||||||
|
uint32_t u32;
|
||||||
|
uint64_t u64;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**TODO: improve the notes for epoll()**
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Basic concepts
|
# Basic Concepts
|
||||||
Before reading, this document could contain errors, please check everything you read.
|
Before reading, this document could contain errors, please check everything you read.
|
||||||
|
|
||||||
- [Basic concepts](#basic-concepts)
|
- [Basic Concepts](#basic-concepts)
|
||||||
- [Socket](#socket)
|
- [Socket](#socket)
|
||||||
- [Internet sockets](#internet-sockets)
|
- [Internet sockets](#internet-sockets)
|
||||||
- [Byte Order](#byte-order)
|
- [Byte Order](#byte-order)
|
||||||
@@ -18,6 +18,10 @@ Before reading, this document could contain errors, please check everything you
|
|||||||
- [listen()](#listen)
|
- [listen()](#listen)
|
||||||
- [accept()](#accept)
|
- [accept()](#accept)
|
||||||
- [send() and recv()](#send-and-recv)
|
- [send() and recv()](#send-and-recv)
|
||||||
|
- [sendto() and recvfrom()](#sendto-and-recvfrom)
|
||||||
|
- [close() and shutdown()](#close-and-shutdown)
|
||||||
|
- [getpeername()](#getpeername)
|
||||||
|
- [gethostname()](#gethostname)
|
||||||
|
|
||||||
|
|
||||||
### Socket
|
### Socket
|
||||||
@@ -114,6 +118,10 @@ There is a very easy function called `inet_pton()`, *pton* stands for **Presenta
|
|||||||
|
|
||||||
### getaddrinfo()
|
### getaddrinfo()
|
||||||
```c
|
```c
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
int getaddrinfo(const char *node, // e.g. "www.example.com" or IP
|
int getaddrinfo(const char *node, // e.g. "www.example.com" or IP
|
||||||
const char *service, // e.g. "http" or port number
|
const char *service, // e.g. "http" or port number
|
||||||
const struct addrinfo *hints,
|
const struct addrinfo *hints,
|
||||||
@@ -125,6 +133,9 @@ The `node` could be a host name or IP address. `service` could be a port number
|
|||||||
|
|
||||||
### socket()
|
### socket()
|
||||||
```c
|
```c
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
int socket(int domain, int type, int protocol);
|
int socket(int domain, int type, int protocol);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -134,11 +145,17 @@ int socket(int domain, int type, int protocol);
|
|||||||
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
|
```c
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
|
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
|
||||||
```
|
```
|
||||||
|
|
||||||
### connect()
|
### connect()
|
||||||
```c
|
```c
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
|
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -151,6 +168,9 @@ int listen(int sockfd, int backlog);
|
|||||||
|
|
||||||
### accept()
|
### accept()
|
||||||
```c
|
```c
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
|
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -161,4 +181,63 @@ After you accept a client, the function will return a new socket file descriptor
|
|||||||
int send(int sockfd, const void *msg, int len, int flags);
|
int send(int sockfd, const void *msg, int len, int flags);
|
||||||
```
|
```
|
||||||
|
|
||||||
Just put `flags` to 0. It will return the bytes sent.
|
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).
|
||||||
|
|
||||||
|
### 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
|
||||||
|
close(sockfd);
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want more control over how the socket closes there is `shutdown()`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int shutdown(int sockfd, int how);
|
||||||
|
```
|
||||||
|
|
||||||
|
`how` is one of the following:
|
||||||
|
| how | Effect |
|
||||||
|
| --- | ------------------------ |
|
||||||
|
| 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
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
int clientfd = accept(sockfd, (struct sockaddr *)&their, &their_len);
|
||||||
|
getpeername(clientfd, (struct sockaddr *)&their, &their_len);
|
||||||
|
struct sockaddr_in *client = (struct sockaddr_in *)&their;
|
||||||
|
char client_ip[INET_ADDRSTRLEN];
|
||||||
|
inet_ntop(AF_INET, &client->sin_addr, client_ip, INET_ADDRSTRLEN);
|
||||||
|
```
|
||||||
|
|
||||||
|
### gethostname()
|
||||||
|
```c
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
int gethostname(char *hostname, size_t size);
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns the name of the computer that your program is running on.
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ int test_client_connection(const char *hostname, const char *port) {
|
|||||||
buf);
|
buf);
|
||||||
|
|
||||||
freeaddrinfo(res);
|
freeaddrinfo(res);
|
||||||
|
close(sockfd);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
108
src/server.c
108
src/server.c
@@ -4,13 +4,8 @@
|
|||||||
int start_server(const char *hostname, const char *service) {
|
int start_server(const char *hostname, const char *service) {
|
||||||
struct addrinfo hints;
|
struct addrinfo hints;
|
||||||
struct addrinfo *res;
|
struct addrinfo *res;
|
||||||
struct sockaddr_storage their_sa; // incoming clients
|
|
||||||
socklen_t theirsa_size = sizeof their_sa;
|
|
||||||
|
|
||||||
memset(&hints, 0, sizeof hints);
|
setup_hints(&hints, sizeof hints, hostname);
|
||||||
hints.ai_family = AF_UNSPEC; // IPv4 or IPv6
|
|
||||||
hints.ai_socktype = SOCK_STREAM; // TCP
|
|
||||||
hints.ai_flags = AI_PASSIVE; // fill in IP for me
|
|
||||||
|
|
||||||
int status = getaddrinfo(hostname, service, &hints, &res);
|
int status = getaddrinfo(hostname, service, &hints, &res);
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
@@ -20,7 +15,6 @@ int start_server(const char *hostname, const char *service) {
|
|||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// socket file descriptor
|
|
||||||
int sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
int sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||||||
|
|
||||||
status = bind(sockfd, res->ai_addr, res->ai_addrlen);
|
status = bind(sockfd, res->ai_addr, res->ai_addrlen);
|
||||||
@@ -37,13 +31,103 @@ int start_server(const char *hostname, const char *service) {
|
|||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
char *msg = "Hello there!";
|
handle_clients(sockfd);
|
||||||
int msg_len = strlen(msg);
|
|
||||||
int newfd = accept(sockfd, (struct sockaddr *)&their_sa, &theirsa_size);
|
|
||||||
int bytes_sent = send(newfd, msg, msg_len, 0);
|
|
||||||
fprintf(stdout, BLUE "[server] Sent %d bytes\n" RESET, bytes_sent);
|
|
||||||
|
|
||||||
freeaddrinfo(res);
|
freeaddrinfo(res);
|
||||||
|
close(sockfd);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setup_hints(struct addrinfo *hints, size_t len, const char *hostname) {
|
||||||
|
memset(hints, 0, len);
|
||||||
|
hints->ai_family = AF_UNSPEC; // IPv4 or IPv6
|
||||||
|
hints->ai_socktype = SOCK_STREAM; // TCP
|
||||||
|
if (hostname == NULL) {
|
||||||
|
hints->ai_flags = AI_PASSIVE; // fill in IP for me
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_clients(int sockfd) {
|
||||||
|
struct sockaddr_storage their_sa;
|
||||||
|
socklen_t theirsa_size = sizeof their_sa;
|
||||||
|
|
||||||
|
int epfd = epoll_create1(0);
|
||||||
|
setnonblocking(sockfd);
|
||||||
|
epoll_ctl_add(epfd, sockfd, EPOLLIN | EPOLLET);
|
||||||
|
|
||||||
|
struct epoll_event *revents =
|
||||||
|
malloc(EPOLL_MAXEVENTS * sizeof(struct epoll_event));
|
||||||
|
int nfds;
|
||||||
|
|
||||||
|
char *msg = "Hello there!";
|
||||||
|
size_t msg_len = strlen(msg);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
nfds =
|
||||||
|
epoll_wait(epfd, revents, EPOLL_MAXEVENTS, EPOLL_TIMEOUT);
|
||||||
|
|
||||||
|
for (int i = 0; i < nfds; ++i) {
|
||||||
|
if (revents[i].data.fd == sockfd) {
|
||||||
|
int client_fd =
|
||||||
|
accept(sockfd, (struct sockaddr *)&their_sa,
|
||||||
|
&theirsa_size);
|
||||||
|
|
||||||
|
if (client_fd == -1) {
|
||||||
|
if (errno != EWOULDBLOCK) {
|
||||||
|
fprintf(stderr,
|
||||||
|
RED BOLD "[server] "
|
||||||
|
"accept(): "
|
||||||
|
"%s\n" RESET,
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
setnonblocking(client_fd);
|
||||||
|
|
||||||
|
struct sockaddr_in *client =
|
||||||
|
(struct sockaddr_in *)&their_sa;
|
||||||
|
char client_ip[INET_ADDRSTRLEN];
|
||||||
|
inet_ntop(AF_INET, &client->sin_addr, client_ip,
|
||||||
|
INET_ADDRSTRLEN);
|
||||||
|
fprintf(stdout,
|
||||||
|
BLUE "[server] Incoming "
|
||||||
|
"client, ip: %s\n" RESET,
|
||||||
|
client_ip);
|
||||||
|
|
||||||
|
int bytes_sent =
|
||||||
|
send(client_fd, msg, msg_len, 0);
|
||||||
|
fprintf(stdout,
|
||||||
|
BLUE "[server] Sent %d bytes\n" RESET,
|
||||||
|
bytes_sent);
|
||||||
|
close(client_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epoll_ctl_add(int epfd, int sockfd, uint32_t events) {
|
||||||
|
struct epoll_event event;
|
||||||
|
event.events = events;
|
||||||
|
event.data.fd = sockfd;
|
||||||
|
int status = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
|
||||||
|
if (status != 0) {
|
||||||
|
fprintf(stderr, RED BOLD "[server] epoll_ctl(): %s\n" RESET,
|
||||||
|
gai_strerror(status));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setnonblocking(int sockfd) {
|
||||||
|
int status = fcntl(sockfd, F_SETFL, O_NONBLOCK);
|
||||||
|
if (status == -1) {
|
||||||
|
fprintf(stderr, RED BOLD "[server] fcntl(): %s\n" RESET,
|
||||||
|
gai_strerror(status));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_new_client(int sockfd) {
|
||||||
|
// handle here new clients
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user