separate main tests in differents files and added comparison in readme

This commit is contained in:
hugogogo
2022-07-20 14:34:16 +02:00
parent 726bf36b17
commit fecf5411bc
10 changed files with 1145 additions and 130 deletions

View File

@@ -1,3 +1,30 @@
## VARIABLES
#NAME = my_program
#VPATH = srcs
#CXX = c++
#CXXFLAGS = -I ./srcs
#OBJS = $(SRCS:%.cpp=%.o)
#
#MAIN = main.cpp
#SRCS = $(MAIN) Webserv.cpp
#
## RULES
#all: $(NAME)
#
#hugo: MAIN = main_hugo.cpp
#hugo: re
#
#$(NAME) : $(OBJS)
# $(CXX) $(OBJS) -o $(NAME)
#clean:
# rm -f $(OBJS)
#fclean: clean
# rm -f $(NAME)
#re: fclean all
#.PHONY : all clean fclean re
NAME = webserv
CXX = c++
@@ -10,14 +37,19 @@ CXXFLAGS += -g
#SHELL = /bin/zsh
VPATH = $(DIR_SRCS)
DIR_SRCS = srcs
DIR_SRCS = srcs
HEADERS_D = ./srcs
HEADERS = Webserv.hpp
DEPENDENCIES = $(HEADERS:%=$(HEADERS_D)/%)
DEPENDENCIES = $(HEADERS:%=$(HEADERS_D)/%)
SRCS = main.cpp Webserv.cpp
SRCS = $(MAIN) Webserv.cpp
#MAIN = main.cpp
#MAIN = main_luke.cpp
#MAIN = main_hugo.cpp
#MAIN = main_poll.cpp
MAIN = main_select.cpp
DIR_OBJS = builds
OBJS = $(SRCS:%.cpp=$(DIR_OBJS)/%.o)
@@ -27,6 +59,8 @@ OBJS = $(SRCS:%.cpp=$(DIR_OBJS)/%.o)
# --------------------
all: $(NAME)
luke: Webserv.cpp main_luke.cpp
luke: re
$(DIR_OBJS)/%.o: %.cpp | $(DIR_OBJS)
$(CXX) $(CXXFLAGS) -c $< -o $@
@@ -51,3 +85,4 @@ re: fclean all
#run: all
.PHONY : all clean fclean re run

View File

@@ -9,8 +9,8 @@
- **socket :** creates an endpoint for communication and returns a file descriptor that refers to that endpoint
- **listen :** marks a socket as a passive socket, that is, as a socket that will be used to accept incoming connection requests using accept()
- **accept :** used with connection-based socket types. It extracts the first connection request on the queue of pending connections for the listening socket, creates a new connected socket, and returns a new file descriptor referring to that socket. The newly created socket is not in the listening state. The original socket is unaffected by this call
- **send :** used to transmit a message to another socket. May be used only when the socket is in a connected state (so that the intended recipient is known). The only difference between send() and write() is the presence of flags. With a zero flags argument, send() is equivalent to write()
- **recv :** used to receive messages from a socket. May be used to receive data on both connectionless and connection-oriented sockets. The only difference between recv() and read() is the presence of flags. With a zero flags argument, recv() is generally equivalent to read()
- **send :** (~write) used to transmit a message to another socket. May be used only when the socket is in a connected state (so that the intended recipient is known). The only difference between send() and write() is the presence of flags. With a zero flags argument, send() is equivalent to write()
- **recv :** (~read) used to receive messages from a socket. May be used to receive data on both connectionless and connection-oriented sockets. The only difference between recv() and read() is the presence of flags. With a zero flags argument, recv() is generally equivalent to read()
- **bind :** associate a socket fd to a local address. When a socket is created with socket(), it exists in a name space (address family) but has no address assigned to it. It is normally necessary to assign a local address using bind() before a socket may receive connections (see accept())
- **connect :** connects a socket fd to a remote address
- **inet_addr :** converts the Internet host address cp from IPv4 numbers-and-dots notation into binary data in network byte order. Use of this function is problematic because in case of error it returns -1, wich is a valid address (255.255.255.255). Avoid its use in favor of inet_aton(), inet_pton(), or getaddrinfo()
@@ -77,3 +77,84 @@
- [bind() vs connect()](https://stackoverflow.com/questions/27014955/socket-connect-vs-bind)
- [INADDR_ANY for bind](https://stackoverflow.com/questions/16508685/understanding-inaddr-any-for-socket-programming)
## code architecture
```
______
sd = SOCKET() : create a listening socket descriptor
__________
SETSOCKOPT(sd) : allow socket descriptor to be reuseable
_____
IOCTL(sd) : set listening socket and all incoming socket to be non-blocking
____
BIND(port) : associate listening socket to a port
______
LISTEN(nb_queue) : queue the incoming connections to listening socket
up to a chosen number
____
POLL(fds[]) : wait for event on files descriptors
______
SELECT(fds[]) : wait for event on files descriptors
FD_SET() : add a fd to a set
FD_CLR() : remove a fd from a set
FD_ZERO() : clears a set
FD_ISSET() : test to see if a fd is part of the set
______
new_sd = ACCEPT() : extract first connection request in queue of listening socket
and creates a new socket that is connected
____
RECV(new_sd) : read data in socket created by accept()
____
SEND(new_sd) : write data in socket created by accept()
_____
CLOSE(new_sd) : close open file (here socket) descriptor
```
compare architectures :
```
POLL SELECT
______ ______ ______
lstn_sd = SOCKET() | lstn_sd = SOCKET() | lstn_sd = SOCKET()
| __________ | __________
| SETSOCKOPT() | SETSOCKOPT()
| _____ | _____
| IOCTL() | IOCTL()
____ | ____ | ____
BIND(port) | BIND(port) | BIND(port)
______ | ______ | ______
LISTEN(nb_queue) | LISTEN(nb_queue) | LISTEN(nb_queue)
| |
| fds[1] = lstn_sd | FD_SET(lstn_fd)
| |
| | max_sd = lstn_sd
| |
loop | loop | loop
. ____ | . ____ | . ______
. POLL() | . POLL(fds[]) | . SELECT(fds[])
. | . | .
. | . loop through fds[] | . loop i++ < max_fd
. | . . | . .
. | . . POLLIN && lstn_sd ? | . . FD_ISSET(i) & lstn_fd ?
. ______ | . . . ______ | . . . ______
. ACCEPT() | . . . new_sd = ACCEPT() | . . . new_sd = ACCEPT()
. | . . . | . . .
. | . . . fds[] += new_sd | . . . FD_SET new_sd in fds[]
. | . . | . . .
. | . . | . . . max_sd = new_sd
. | . . | . .
. | . . POLLIN ? | . . FD_ISSET ?
. ____ | . . . ____ | . . . ____
. RECV() | . . . RECV() | . . . RECV()
. ____ | . . . ____ | . . . ____
. SEND() | . . . SEND() | . . . SEND()
. | |
. | loop through fds[] | loop through fds[]
. _____ | . _____ | . _____
. CLOSE(fds[]) | . CLOSE(fds[]) | . CLOSE(fds[])
```

View File

@@ -4,7 +4,8 @@
Webserv::Webserv()
{
std::cout << "Server init\n";
_socket_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
// _socket_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
_socket_fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_socket_fd == -1)
{
::perror("err socket(): ");
@@ -12,11 +13,21 @@ Webserv::Webserv()
}
}
/* Webserv::Webserv(Webserv const &src)
{
} */
Webserv::~Webserv()
{
std::cout << "Server destroyed\n";
}
/* Webserv & Webserv::operator=(Webserv const &rhs)
{
} */
///////////////
// Functions //
@@ -56,36 +67,31 @@ void Webserv::start()
struct sockaddr_in addr;
socklen_t addr_len;
int accepted_fd;
// poll (or equivalent)
struct pollfd poll_s;
poll_s.fd = _socket_fd;
poll_s.events = POLLIN;
char buf[BUFSIZE]; // WIP buffer. need to try with std::vector or std::string.
int ret;
struct pollfd poll_s;
char buf[BUFSIZE]; // WIP buffer. need to try with std::vector or std::string.
int ret;
std::cout << "Server started\n";
while (1)
{
std::cout << "----------\n";
std::cout << "poll()\n"; // poll (or equivalent)
::poll(&poll_s, 1, -1);
std::cout << "accept()\n";
addr_len = sizeof addr;
accepted_fd = ::accept(_socket_fd, (sockaddr*)&addr, &addr_len);
if (accepted_fd == -1)
{
::perror("err accept(): ");
//throw WebservError();
continue;
}
std::cout << "accept()\n";
// "Your server must never block and the client can be bounced properly if necessary".
// NO-Block OK, but how to handle it ? Miss the bouncing part.
::fcntl(accepted_fd, F_SETFL, O_NONBLOCK);
std::cout << "poll()\n"; // poll (or equivalent)
poll_s.fd = accepted_fd;
poll_s.events = POLLIN; // We need a way to valid POLLOUT and POLLIN at the same time (both, not one of them)
::poll(&poll_s, 1, -1);
std::cout << "recv()\n";
ret = ::recv(accepted_fd, buf, BUFSIZE, 0);
if (ret == -1)

View File

@@ -1,7 +1,4 @@
/*
luke version
#include <iostream>
#include <exception>
#include <stdexcept>
@@ -13,8 +10,8 @@ int main(void)
{
Webserv serv;
// https://security.stackexchange.com/questions/169213/how-to-chose-a-port-to-run-an-application-on-localhost
serv.bind(8080);
// https://security.stackexchange.com/questions/169213/how-to-chose-a-port-to-run-an-application-on-localhost
serv.bind(4040);
serv.listen(512); // 512 max connections arbitrary
serv.start();
}
@@ -24,112 +21,46 @@ int main(void)
}
return (0);
}
*/
/*
wip hugo version
______
listen_fd = SOCKET() : create a listening socket
__________
SETSOCKOPT() : Allow socket descriptor to be reuseable
_____
IOCTL() : set listen_fd and all incoming socket to be non-blocking
____
BIND(port) : associate listen_fd to a port
______
LISTEN(nb_queue) : queue the incoming connections to listen_fd, up to a chosen number
fds[1] = listen_fd
loop
. ____
. POLL(fds[]) :
.
. loop through fds[]
. .
. . POLLIN && listen_fd ? : readable socket and this is the listening one
. . . ______
. . . new_fd = ACCEPT() : extract first connection request in queue of listen_fd
. . . and creates a new socket that is connected
. . .
. . . fds[] += new_fd
. .
. . POLLIN ? : readable socket and this is an active one
. . . ____
. . . RECV() : read data in socket created by accept()
. . . ____
. . . SEND() : write data in socket created by accept()
loop through fds[] :
. _____
. CLOSE(fds[])
*/
# include <map>
# include <exception>
# include <stdexcept>
# include <fcntl.h> // fcntl
# include <unistd.h> // close
# include <stdlib.h> // exit
# include <iostream> // cout, cin
# include <cerrno> // errno
# include <cstdio> // perror
# include <string.h> // memset
# include <sys/socket.h> // socket, accept, listen, send, recv, bind, connect, setsockopt, getsockname
# include <netinet/in.h> // sockaddr_in
# include <arpa/inet.h> // inet_ntoa, inet_addr, htonl, htons, ntohl, ntohs
# include <poll.h> // poll
# define INVALID -1
# define BACKLOG 20
// test with curl http://localhost:PORT (replace PORT by the port number you choose below)
# define PORT 4040
int main()
{
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
int sckt_fd;
int axpt_fd;
int lstn;
socklen_t addr_len;
// INIT SOCKET
sckt_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (sckt_fd == INVALID)
{
perror("err socket(): ");
return 0;
}
std::cout << "server init\n";
// BIND IT
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORT);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
bind(sckt_fd, (struct sockaddr *)&my_addr, sizeof(my_addr));
if (sckt_fd == INVALID)
{
perror("err bind(): ");
return 0;
}
std::cout << "socket bind to port: " << PORT << "\n";
// https://beej.us/guide/bgnet/html/index-wide.html#cb29 :
//
// Sometimes, you might 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 connected is still hanging around in the kernel, and its 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:
//
// int yes=1;
//
// // lose the pesky "Address already in use" error message
// if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof yes) == -1) {
// perror("setsockopt");
// exit(1);
// }
// LISTEN ON IT
lstn = listen(sckt_fd, BACKLOG);
if (lstn == INVALID)
{
perror("err listen(): ");
return 0;
}
std::cout << "server listening\n";
// ACCEPT INCOMING CONNECT
while (1)
{
addr_len = sizeof(their_addr);
axpt_fd = accept(sckt_fd, (sockaddr*)&their_addr, &addr_len);
if (axpt_fd == INVALID)
{
perror("err accept(): ");
continue;
}
std::cout << "server accepted a socket from: "
<< inet_ntoa(their_addr.sin_addr)
<< "\n";
// // with a fork
// if (!fork()) // child process
// {
// close(sckt_fd); // child doesn't need the listener
// if (send(axpt_fd, "hello world !", 13, 0) == INVALID)
// perror("err send(): ");
// close(axpt_fd);
// exit(0);
// }
// close(axpt_fd); // parent doesn't need this
}
return 0;
}

163
srcs/main_1.cpp Normal file
View File

@@ -0,0 +1,163 @@
# include <unistd.h> // close
# include <stdlib.h> // exit
# include <iostream> // cout, cin
# include <cerrno> // errno
# include <cstdio> // perror
# include <string.h> // memset
# include <sys/socket.h> // socket, accept, listen, send, recv, bind, connect, setsockopt, getsockname
# include <netinet/in.h> // sockaddr_in
# include <arpa/inet.h> // inet_ntoa, inet_addr, htonl, htons, ntohl, ntohs
# include <poll.h> // poll
# include <fcntl.h> // fcntl
# define INVALID -1
# define BACKLOG 20
// test with curl http://localhost:PORT (replace PORT by the port number you choose below)
# define PORT 4040
# define BUFSIZE 8192
# define MSG_TEST "Le Webserv / 20 =D\n"
# define MSG_BOUNCE "bounced properly ;)\n"
int main()
{
struct sockaddr_in my_addr, their_addr;
int sckt_fd, axpt_fd, lstn, ret;
socklen_t addr_len;
struct pollfd poll_s;
char buf[BUFSIZE];
// --------------------------------------------------------------------------- //
// SOCKET //
// //
// --------------------------------------------------------------------------- //
sckt_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (sckt_fd == INVALID)
{
perror("err socket()");
return 0;
}
std::cout << "server init\n";
// --------------------------------------------------------------------------- //
// BIND //
// //
// --------------------------------------------------------------------------- //
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORT);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
bind(sckt_fd, (struct sockaddr *)&my_addr, sizeof(my_addr));
if (sckt_fd == INVALID)
{
perror("err bind()");
return 0;
}
std::cout << "socket bind to port: " << PORT << "\n";
// https://beej.us/guide/bgnet/html/index-wide.html#cb29 :
//
// Sometimes, you might 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 connected is still hanging around in the kernel, and its 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:
//
// int yes=1;
//
// // lose the pesky "Address already in use" error message
// if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof yes) == -1) {
// perror("setsockopt");
// exit(1);
// }
// --------------------------------------------------------------------------- //
// LISTEN //
// mark the socket as passive, to receive incoming connection requests //
// --------------------------------------------------------------------------- //
lstn = listen(sckt_fd, BACKLOG);
if (lstn == INVALID)
{
perror("err listen()");
return 0;
}
std::cout << "server listening\n";
while (1)
{
// ----------------------------------------------------------------------- //
// ACCEPT //
// extract first connection request in the queue of the listening socket //
// and creates a new socket that is connected, and returns it //
// ----------------------------------------------------------------------- //
addr_len = sizeof(their_addr);
axpt_fd = accept(sckt_fd, (sockaddr*)&their_addr, &addr_len);
if (axpt_fd == INVALID)
{
perror("err accept()");
continue;
}
std::cout << "server accepted a socket from: "
<< inet_ntoa(their_addr.sin_addr)
<< "\n";
/////////////////////
// luke version
/////////////////////
// ----------------------------------------------------------------------- //
// FCNTL //
// manipulate the new socket fd to mark it as non-blocking //
// ----------------------------------------------------------------------- //
fcntl(axpt_fd, F_SETFL, O_NONBLOCK);
// ----------------------------------------------------------------------- //
// POLL //
// waits for an event on the axpt_fd : there is data to read //
// ----------------------------------------------------------------------- //
poll_s.fd = axpt_fd;
poll_s.events = POLLIN; // We need a way to valid POLLOUT and POLLIN at the same time (both, not one of them)
poll(&poll_s, 1, -1);
std::cout << "poll()\n";
// ----------------------------------------------------------------------- //
// RECV //
// act as read, read the data of the socket created by accept() //
// ----------------------------------------------------------------------- //
ret = recv(axpt_fd, buf, BUFSIZE, 0);
if (ret == -1)
{
perror("err recv()");
if (send(axpt_fd, MSG_BOUNCE, sizeof MSG_BOUNCE - 1, 0) == -1)
perror("err send()");
close(axpt_fd);
continue;
}
std::cout << "recv()\n";
buf[ret] = '\0';
// ----------------------------------------------------------------------- //
// SEND //
// act as write, write data in the socket created by accept() //
// ----------------------------------------------------------------------- //
std::cout << "send()\n";
if (send(axpt_fd, buf, ret, 0) == -1) // echo the read
perror("err send()");
if (send(axpt_fd, MSG_TEST, sizeof MSG_TEST - 1, 0) == -1)
perror("err send()");
// ----------------------------------------------------------------------- //
// CLOSE //
// close the file descriptor of the connected socket //
// ----------------------------------------------------------------------- //
close(axpt_fd);
}
return 0;
}

163
srcs/main_hugo.cpp Normal file
View File

@@ -0,0 +1,163 @@
# include <unistd.h> // close
# include <stdlib.h> // exit
# include <iostream> // cout, cin
# include <cerrno> // errno
# include <cstdio> // perror
# include <string.h> // memset
# include <sys/socket.h> // socket, accept, listen, send, recv, bind, connect, setsockopt, getsockname
# include <netinet/in.h> // sockaddr_in
# include <arpa/inet.h> // inet_ntoa, inet_addr, htonl, htons, ntohl, ntohs
# include <poll.h> // poll
# include <fcntl.h> // fcntl
# define INVALID -1
# define BACKLOG 20
// test with curl http://localhost:PORT (replace PORT by the port number you choose below)
# define PORT 4040
# define BUFSIZE 8192
# define MSG_TEST "Le Webserv / 20 =D\n"
# define MSG_BOUNCE "bounced properly ;)\n"
int main()
{
struct sockaddr_in my_addr, their_addr;
int sckt_fd, axpt_fd, lstn, ret;
socklen_t addr_len;
struct pollfd poll_s;
char buf[BUFSIZE];
// --------------------------------------------------------------------------- //
// SOCKET //
// //
// --------------------------------------------------------------------------- //
sckt_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (sckt_fd == INVALID)
{
perror("err socket()");
return 0;
}
std::cout << "server init\n";
// --------------------------------------------------------------------------- //
// BIND //
// //
// --------------------------------------------------------------------------- //
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORT);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
bind(sckt_fd, (struct sockaddr *)&my_addr, sizeof(my_addr));
if (sckt_fd == INVALID)
{
perror("err bind()");
return 0;
}
std::cout << "socket bind to port: " << PORT << "\n";
// https://beej.us/guide/bgnet/html/index-wide.html#cb29 :
//
// Sometimes, you might 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 connected is still hanging around in the kernel, and its 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:
//
// int yes=1;
//
// // lose the pesky "Address already in use" error message
// if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof yes) == -1) {
// perror("setsockopt");
// exit(1);
// }
// --------------------------------------------------------------------------- //
// LISTEN //
// mark the socket as passive, to receive incoming connection requests //
// --------------------------------------------------------------------------- //
lstn = listen(sckt_fd, BACKLOG);
if (lstn == INVALID)
{
perror("err listen()");
return 0;
}
std::cout << "server listening\n";
while (1)
{
// ----------------------------------------------------------------------- //
// ACCEPT //
// extract first connection request in the queue of the listening socket //
// and creates a new socket that is connected, and returns it //
// ----------------------------------------------------------------------- //
addr_len = sizeof(their_addr);
axpt_fd = accept(sckt_fd, (sockaddr*)&their_addr, &addr_len);
if (axpt_fd == INVALID)
{
perror("err accept()");
continue;
}
std::cout << "server accepted a socket from: "
<< inet_ntoa(their_addr.sin_addr)
<< "\n";
/////////////////////
// luke version
/////////////////////
// ----------------------------------------------------------------------- //
// FCNTL //
// manipulate the new socket fd to mark it as non-blocking //
// ----------------------------------------------------------------------- //
fcntl(axpt_fd, F_SETFL, O_NONBLOCK);
// ----------------------------------------------------------------------- //
// POLL //
// waits for an event on the axpt_fd : there is data to read //
// ----------------------------------------------------------------------- //
poll_s.fd = axpt_fd;
poll_s.events = POLLIN; // We need a way to valid POLLOUT and POLLIN at the same time (both, not one of them)
poll(&poll_s, 1, -1);
std::cout << "poll()\n";
// ----------------------------------------------------------------------- //
// RECV //
// act as read, read the data of the socket created by accept() //
// ----------------------------------------------------------------------- //
ret = recv(axpt_fd, buf, BUFSIZE, 0);
if (ret == -1)
{
perror("err recv()");
if (send(axpt_fd, MSG_BOUNCE, sizeof MSG_BOUNCE - 1, 0) == -1)
perror("err send()");
close(axpt_fd);
continue;
}
std::cout << "recv()\n";
buf[ret] = '\0';
// ----------------------------------------------------------------------- //
// SEND //
// act as write, write data in the socket created by accept() //
// ----------------------------------------------------------------------- //
std::cout << "send()\n";
if (send(axpt_fd, buf, ret, 0) == -1) // echo the read
perror("err send()");
if (send(axpt_fd, MSG_TEST, sizeof MSG_TEST - 1, 0) == -1)
perror("err send()");
// ----------------------------------------------------------------------- //
// CLOSE //
// close the file descriptor of the connected socket //
// ----------------------------------------------------------------------- //
close(axpt_fd);
}
return 0;
}

25
srcs/main_luke.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include <iostream>
#include <exception>
#include <stdexcept>
#include "Webserv.hpp"
int main(void)
{
try
{
Webserv serv;
// https://security.stackexchange.com/questions/169213/how-to-chose-a-port-to-run-an-application-on-localhost
//serv.bind(4040);
serv.bind(4040);
serv.listen(512); // 512 max connections arbitrary
serv.start();
}
catch (std::exception& e)
{
std::cout << e.what() << '\n';
}
return (0);
}

308
srcs/main_poll.cpp Normal file
View File

@@ -0,0 +1,308 @@
// https://www.ibm.com/docs/en/i/7.2?topic=designs-using-poll-instead-select
# include <unistd.h> // close
# include <stdlib.h> // exit
# include <iostream> // cout, cin
# include <cerrno> // errno
# include <cstdio> // perror
# include <string.h> // memset
# include <sys/socket.h> // socket, accept, listen, send, recv, bind, connect, setsockopt, getsockname
# include <netinet/in.h> // sockaddr_in
# include <arpa/inet.h> // inet_ntoa, inet_addr, htonl, htons, ntohl, ntohs
# include <poll.h> // poll
# include <fcntl.h> // fcntl
# include <sys/ioctl.h> // ioctl
# define FALSE 0
# define TRUE 1
# define SERVER_PORT 4040
int main ()
{
int len, rc, on = 1;
int listen_sd = -1, new_sd = -1;
int end_server = FALSE, compress_array = FALSE;
int close_conn;
char buffer[80];
struct sockaddr_in addr;
struct pollfd fds[200];
// struct pollfd fds[1];
// struct pollfd fds;
int nfds = 1, current_size = 0, i, j;
// *************************************************************
// * Create an AF_INET stream socket to receive incoming *
// * connections on *
// *************************************************************
listen_sd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sd < 0)
{
perror("socket() failed");
exit(-1);
}
// *************************************************************
// * Allow socket descriptor to be reuseable *
// *************************************************************
rc = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
if (rc < 0)
{
perror("setsockopt() failed");
close(listen_sd);
exit(-1);
}
// *************************************************************
// * Set socket to be nonblocking. All of the sockets for *
// * the incoming connections will also be nonblocking since *
// * they will inherit that state from the listening socket. *
// *************************************************************
rc = ioctl(listen_sd, FIONBIO, (char *)&on);
if (rc < 0)
{
perror("ioctl() failed");
close(listen_sd);
exit(-1);
}
// *************************************************************
// * Bind the socket *
// *************************************************************
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
// memcpy(&addr.sin_addr, &inaddr_any, sizeof(inaddr_any));
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(SERVER_PORT);
rc = bind(listen_sd, (struct sockaddr *)&addr, sizeof(addr));
if (rc < 0)
{
perror("bind() failed");
close(listen_sd);
exit(-1);
}
// *************************************************************
// * Set the listen back log *
// *************************************************************
rc = listen(listen_sd, 32);
if (rc < 0)
{
perror("listen() failed");
close(listen_sd);
exit(-1);
}
// *************************************************************
// * Initialize the pollfd structure *
// *************************************************************
memset(fds, 0 , sizeof(fds));
// *************************************************************
// * Set up the initial listening socket *
// *************************************************************
fds[0].fd = listen_sd;
fds[0].events = POLLIN;
// *************************************************************
// * Loop waiting for incoming connects or for incoming data *
// * on any of the connected sockets. *
// *************************************************************
do
{
// ***********************************************************
// * Call poll() *
// ***********************************************************
poll(fds, nfds, -1);
// ***********************************************************
// * One or more descriptors are readable. Need to *
// * determine which ones they are. *
// ***********************************************************
current_size = nfds;
for (i = 0; i < current_size; i++)
{
// *********************************************************
// * Loop through to find the descriptors that returned *
// * POLLIN and determine whether it's the listening *
// * or the active connection. *
// *********************************************************
if(fds[i].revents == 0)
continue;
// *********************************************************
// * If revents is not POLLIN, it's an unexpected result, *
// * log and end the server. *
// *********************************************************
if(fds[i].revents != POLLIN)
{
printf(" Error! revents = %d\n", fds[i].revents);
end_server = TRUE;
break;
}
if (fds[i].fd == listen_sd)
{
// *******************************************************
// * Listening descriptor is readable. *
// *******************************************************
printf(" Listening socket is readable\n");
// *******************************************************
// * Accept all incoming connections that are *
// * queued up on the listening socket before we *
// * loop back and call poll again. *
// *******************************************************
do
{
// *****************************************************
// * Accept each incoming connection. If *
// * accept fails with EWOULDBLOCK, then we *
// * have accepted all of them. Any other *
// * failure on accept will cause us to end the *
// * server. *
// *****************************************************
new_sd = accept(listen_sd, NULL, NULL);
if (new_sd < 0)
{
if (errno != EWOULDBLOCK)
{
perror(" accept() failed");
end_server = TRUE;
}
break;
}
// *****************************************************
// * Add the new incoming connection to the *
// * pollfd structure *
// *****************************************************
printf(" New incoming connection - %d\n", new_sd);
fds[nfds].fd = new_sd;
fds[nfds].events = POLLIN;
nfds++;
// *****************************************************
// * Loop back up and accept another incoming *
// * connection *
// *****************************************************
} while (new_sd != -1);
}
// *********************************************************
// * This is not the listening socket, therefore an *
// * existing connection must be readable *
// *********************************************************
else
{
printf(" Descriptor %d is readable\n", fds[i].fd);
close_conn = FALSE;
// *******************************************************
// * Receive all incoming data on this socket *
// * before we loop back and call poll again. *
// *******************************************************
do
{
// *****************************************************
// * Receive data on this connection until the *
// * recv fails with EWOULDBLOCK. If any other *
// * failure occurs, we will close the *
// * connection. *
// *****************************************************
rc = recv(fds[i].fd, buffer, sizeof(buffer), 0);
if (rc < 0)
{
if (errno != EWOULDBLOCK)
{
perror(" recv() failed");
close_conn = TRUE;
}
break;
}
// *****************************************************
// * Check to see if the connection has been *
// * closed by the client *
// *****************************************************
if (rc == 0)
{
printf(" Connection closed\n");
close_conn = TRUE;
break;
}
// *****************************************************
// * Data was received *
// *****************************************************
len = rc;
printf(" %d bytes received\n", len);
// *****************************************************
// * Echo the data back to the client *
// *****************************************************
rc = send(fds[i].fd, buffer, len, 0);
if (rc < 0)
{
perror(" send() failed");
close_conn = TRUE;
break;
}
} while(TRUE);
// *******************************************************
// * If the close_conn flag was turned on, we need *
// * to clean up this active connection. This *
// * clean up process includes removing the *
// * descriptor. *
// *******************************************************
if (close_conn)
{
close(fds[i].fd);
fds[i].fd = -1;
compress_array = TRUE;
}
} // End of existing connection is readable
} // End of loop through pollable descriptors
// ***********************************************************
// * If the compress_array flag was turned on, we need *
// * to squeeze together the array and decrement the number *
// * of file descriptors. We do not need to move back the *
// * events and revents fields because the events will always*
// * be POLLIN in this case, and revents is output. *
// ***********************************************************
if (compress_array)
{
compress_array = FALSE;
for (i = 0; i < nfds; i++)
{
if (fds[i].fd == -1)
{
for(j = i; j < nfds; j++)
{
fds[j].fd = fds[j+1].fd;
}
i--;
nfds--;
}
}
}
} while (end_server == FALSE); // End of serving running.
// *************************************************************
// * Clean up all of the sockets that are open *
// *************************************************************
for (i = 0; i < nfds; i++)
{
if(fds[i].fd >= 0)
close(fds[i].fd);
}
}

303
srcs/main_select.cpp Normal file
View File

@@ -0,0 +1,303 @@
# include <unistd.h> // close
# include <string.h>
# include <stdio.h>
# include <stdlib.h>
# include <sys/ioctl.h>
# include <sys/socket.h>
# include <sys/time.h>
# include <netinet/in.h>
# include <errno.h>
#define SERVER_PORT 4040
#define TRUE 1
#define FALSE 0
int main ()
{
int i, len, rc, on = 1;
int listen_sd, max_sd, new_sd;
int desc_ready, end_server = FALSE;
int close_conn;
char buffer[80];
struct sockaddr_in6 addr;
struct timeval timeout;
fd_set master_set, working_set;
/*************************************************************/
/* Create an AF_INET6 stream socket to receive incoming */
/* connections on */
/*************************************************************/
listen_sd = socket(AF_INET6, SOCK_STREAM, 0);
if (listen_sd < 0)
{
perror("socket() failed");
exit(-1);
}
/*************************************************************/
/* Allow socket descriptor to be reuseable */
/*************************************************************/
rc = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR,
(char *)&on, sizeof(on));
if (rc < 0)
{
perror("setsockopt() failed");
close(listen_sd);
exit(-1);
}
/*************************************************************/
/* Set socket to be nonblocking. All of the sockets for */
/* the incoming connections will also be nonblocking since */
/* they will inherit that state from the listening socket. */
/*************************************************************/
rc = ioctl(listen_sd, FIONBIO, (char *)&on);
if (rc < 0)
{
perror("ioctl() failed");
close(listen_sd);
exit(-1);
}
/*************************************************************/
/* Bind the socket */
/*************************************************************/
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
memcpy(&addr.sin6_addr, &in6addr_any, sizeof(in6addr_any));
addr.sin6_port = htons(SERVER_PORT);
rc = bind(listen_sd,
(struct sockaddr *)&addr, sizeof(addr));
if (rc < 0)
{
perror("bind() failed");
close(listen_sd);
exit(-1);
}
/*************************************************************/
/* Set the listen back log */
/*************************************************************/
rc = listen(listen_sd, 32);
if (rc < 0)
{
perror("listen() failed");
close(listen_sd);
exit(-1);
}
/*************************************************************/
/* Initialize the master fd_set */
/*************************************************************/
FD_ZERO(&master_set);
max_sd = listen_sd;
FD_SET(listen_sd, &master_set);
/*************************************************************/
/* Initialize the timeval struct to 3 minutes. If no */
/* activity after 3 minutes this program will end. */
/*************************************************************/
timeout.tv_sec = 3 * 60;
timeout.tv_usec = 0;
/*************************************************************/
/* Loop waiting for incoming connects or for incoming data */
/* on any of the connected sockets. */
/*************************************************************/
do
{
/**********************************************************/
/* Copy the master fd_set over to the working fd_set. */
/**********************************************************/
memcpy(&working_set, &master_set, sizeof(master_set));
/**********************************************************/
/* Call select() and wait 3 minutes for it to complete. */
/**********************************************************/
printf("Waiting on select()...\n");
rc = select(max_sd + 1, &working_set, NULL, NULL, &timeout);
/**********************************************************/
/* Check to see if the select call failed. */
/**********************************************************/
if (rc < 0)
{
perror(" select() failed");
break;
}
/**********************************************************/
/* Check to see if the 3 minute time out expired. */
/**********************************************************/
if (rc == 0)
{
printf(" select() timed out. End program.\n");
break;
}
/**********************************************************/
/* One or more descriptors are readable. Need to */
/* determine which ones they are. */
/**********************************************************/
desc_ready = rc;
for (i=0; i <= max_sd && desc_ready > 0; ++i)
{
/*******************************************************/
/* Check to see if this descriptor is ready */
/*******************************************************/
if (FD_ISSET(i, &working_set))
{
/****************************************************/
/* A descriptor was found that was readable - one */
/* less has to be looked for. This is being done */
/* so that we can stop looking at the working set */
/* once we have found all of the descriptors that */
/* were ready. */
/****************************************************/
desc_ready -= 1;
/****************************************************/
/* Check to see if this is the listening socket */
/****************************************************/
if (i == listen_sd)
{
printf(" Listening socket is readable\n");
/*************************************************/
/* Accept all incoming connections that are */
/* queued up on the listening socket before we */
/* loop back and call select again. */
/*************************************************/
do
{
/**********************************************/
/* Accept each incoming connection. If */
/* accept fails with EWOULDBLOCK, then we */
/* have accepted all of them. Any other */
/* failure on accept will cause us to end the */
/* server. */
/**********************************************/
new_sd = accept(listen_sd, NULL, NULL);
if (new_sd < 0)
{
if (errno != EWOULDBLOCK)
{
perror(" accept() failed");
end_server = TRUE;
}
break;
}
/**********************************************/
/* Add the new incoming connection to the */
/* master read set */
/**********************************************/
printf(" New incoming connection - %d\n", new_sd);
FD_SET(new_sd, &master_set);
if (new_sd > max_sd)
max_sd = new_sd;
/**********************************************/
/* Loop back up and accept another incoming */
/* connection */
/**********************************************/
} while (new_sd != -1);
}
/****************************************************/
/* This is not the listening socket, therefore an */
/* existing connection must be readable */
/****************************************************/
else
{
printf(" Descriptor %d is readable\n", i);
close_conn = FALSE;
/*************************************************/
/* Receive all incoming data on this socket */
/* before we loop back and call select again. */
/*************************************************/
do
{
/**********************************************/
/* Receive data on this connection until the */
/* recv fails with EWOULDBLOCK. If any other */
/* failure occurs, we will close the */
/* connection. */
/**********************************************/
rc = recv(i, buffer, sizeof(buffer), 0);
if (rc < 0)
{
if (errno != EWOULDBLOCK)
{
perror(" recv() failed");
close_conn = TRUE;
}
break;
}
/**********************************************/
/* Check to see if the connection has been */
/* closed by the client */
/**********************************************/
if (rc == 0)
{
printf(" Connection closed\n");
close_conn = TRUE;
break;
}
/**********************************************/
/* Data was received */
/**********************************************/
len = rc;
printf(" %d bytes received\n", len);
/**********************************************/
/* Echo the data back to the client */
/**********************************************/
rc = send(i, buffer, len, 0);
if (rc < 0)
{
perror(" send() failed");
close_conn = TRUE;
break;
}
} while (TRUE);
/*************************************************/
/* If the close_conn flag was turned on, we need */
/* to clean up this active connection. This */
/* clean up process includes removing the */
/* descriptor from the master set and */
/* determining the new maximum descriptor value */
/* based on the bits that are still turned on in */
/* the master set. */
/*************************************************/
if (close_conn)
{
close(i);
FD_CLR(i, &master_set);
if (i == max_sd)
{
while (FD_ISSET(max_sd, &master_set) == FALSE)
max_sd -= 1;
}
}
} /* End of existing connection is readable */
} /* End of if (FD_ISSET(i, &working_set)) */
} /* End of loop through selectable descriptors */
} while (end_server == FALSE);
/*************************************************************/
/* Clean up all of the sockets that are open */
/*************************************************************/
for (i=0; i <= max_sd; ++i)
{
if (FD_ISSET(i, &master_set))
close(i);
}
}

BIN
webserv

Binary file not shown.