287 lines
6.8 KiB
C++
287 lines
6.8 KiB
C++
/* ************************************************************************** */
|
|
/* */
|
|
/* ::: :::::::: */
|
|
/* Server.cpp :+: :+: :+: */
|
|
/* +:+ +:+ +:+ */
|
|
/* By: aortigos <aortigos@student.42malaga.com +#+ +:+ +#+ */
|
|
/* +#+#+#+#+#+ +#+ */
|
|
/* Created: 2026/05/06 17:19:12 by iherman- #+# #+# */
|
|
/* Updated: 2026/05/15 22:16:34 by aortigos ### ########.fr */
|
|
/* */
|
|
/* ************************************************************************** */
|
|
|
|
#include "Server.hpp"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <poll.h>
|
|
|
|
#include <cerrno>
|
|
#include <sstream>
|
|
|
|
const int Server::kConnectionQueueLimit = 10;
|
|
|
|
Server::Server() :
|
|
port_(PORT_DEFAULT),
|
|
password_("password")
|
|
{
|
|
serverSocket_ = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (serverSocket_ < 0)
|
|
throw std::runtime_error("Failed to create socket");
|
|
|
|
struct sockaddr_in addr;
|
|
std::memset(&addr, 0, sizeof(addr));
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(port_);
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
if (bind(serverSocket_, (struct sockaddr*)&addr, sizeof(addr)))
|
|
throw std::runtime_error("Failed to bind");
|
|
|
|
if (listen(serverSocket_, kConnectionQueueLimit))
|
|
throw std::runtime_error("Failed to listen");
|
|
|
|
|
|
|
|
std::cout << "Server port: " << port_
|
|
<< "\nServer Password: " << password_
|
|
<< std::endl;
|
|
}
|
|
|
|
Server::Server(const Server& other)
|
|
{
|
|
port_ = other.port_;
|
|
serverSocket_ = other.serverSocket_;
|
|
password_ = other.password_;
|
|
sockets_ = other.sockets_;
|
|
}
|
|
|
|
Server::Server(int port, const std::string& password) :
|
|
port_(port),
|
|
password_(password)
|
|
{
|
|
if (port_ < 1 || port_ > 65535)
|
|
throw std::runtime_error("Invalid port");
|
|
|
|
if (password_.empty())
|
|
throw std::runtime_error("Empty password");
|
|
|
|
serverSocket_ = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (serverSocket_ < 0)
|
|
throw std::runtime_error("Failed to create socket");
|
|
|
|
int opt = 1;
|
|
if (setsockopt(serverSocket_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
|
|
{
|
|
close(serverSocket_);
|
|
throw std::runtime_error("setsockopt failed");
|
|
}
|
|
|
|
struct sockaddr_in addr;
|
|
std::memset(&addr, 0, sizeof(addr));
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(port_);
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
if (bind(serverSocket_, (struct sockaddr*)&addr, sizeof(addr)))
|
|
{
|
|
close(serverSocket_);
|
|
throw std::runtime_error("Failed to bind");
|
|
}
|
|
|
|
if (listen(serverSocket_, kConnectionQueueLimit))
|
|
{
|
|
close(serverSocket_); // maybe make an fd class that cleans up automatically using the destructor
|
|
throw std::runtime_error("Failed to listen");
|
|
}
|
|
|
|
// Add all new commands to commands_ here
|
|
commands_["PASS"] = &Server::pass_cmd;
|
|
commands_["NICK"] = &Server::nick_cmd;
|
|
commands_["USER"] = &Server::user_cmd;
|
|
commands_["JOIN"] = &Server::join_cmd;
|
|
commands_["PRIVMSG"] = &Server::privmsg_cmd;
|
|
|
|
std::cout << "Server port: " << port_
|
|
<< "\nServer Password: " << password_
|
|
<< std::endl;
|
|
}
|
|
|
|
Server::~Server()
|
|
{
|
|
for (std::map<int, User>::iterator it = clients_.begin(); it != clients_.end(); ++it)
|
|
close(it->first);
|
|
|
|
close(serverSocket_);
|
|
}
|
|
|
|
Server &Server::operator=(const Server& other)
|
|
{
|
|
if (this != &other)
|
|
{
|
|
port_ = other.port_;
|
|
serverSocket_ = other.serverSocket_;
|
|
password_ = other.password_;
|
|
sockets_ = other.sockets_;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
void Server::parseCommand(User& client)
|
|
{
|
|
std::istringstream args(client.getBuffer());
|
|
std::string command;
|
|
|
|
args >> command;
|
|
|
|
std::map<std::string, void (Server::*)(User&, std::istringstream&)>::iterator it = commands_.find(command);
|
|
if (it == commands_.end())
|
|
{
|
|
std::string message = "Error: command not found!\n";
|
|
send(client.getFd(), message.c_str(), message.size(), 0);
|
|
client.clearBuffer();
|
|
return ;
|
|
}
|
|
(this->*(it->second))(client, args);
|
|
client.clearBuffer();
|
|
}
|
|
|
|
bool Server::handleClient(User& client)
|
|
{
|
|
const std::size_t kBufferSize = 1024;
|
|
char buffer[kBufferSize] = {0};
|
|
|
|
int recv_amount = recv(client.getFd(), buffer, kBufferSize, 0);
|
|
|
|
if (recv_amount == 0)
|
|
return true;
|
|
|
|
if (recv_amount < 0)
|
|
{
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
client.appendBuffer(std::string(buffer, recv_amount));
|
|
|
|
if (client.getBuffer().find('\n') != std::string::npos)
|
|
parseCommand(client);
|
|
|
|
return false;
|
|
}
|
|
|
|
static struct pollfd newPollfd(int fd)
|
|
{
|
|
pollfd pfd;
|
|
|
|
pfd.fd = fd;
|
|
pfd.events = POLLIN;
|
|
pfd.revents = 0;
|
|
return pfd;
|
|
}
|
|
|
|
void Server::addClient()
|
|
{
|
|
int newClientSocket = accept(serverSocket_, NULL, NULL);
|
|
|
|
if (newClientSocket == -1)
|
|
{
|
|
// Not sure if program should terminate
|
|
std::cerr << "Problem occured accepting client" << std::endl;
|
|
return ;
|
|
}
|
|
|
|
fcntl(newClientSocket, F_SETFL, O_NONBLOCK);
|
|
sockets_.push_back(newPollfd(newClientSocket));
|
|
clients_[newClientSocket] = User(newClientSocket);
|
|
std::cout << "Client with fd: " << newClientSocket << " connected" << std::endl;
|
|
}
|
|
|
|
std::vector<struct pollfd>::iterator Server::removeClient(std::vector<struct pollfd>::iterator& client)
|
|
{
|
|
close(client->fd);
|
|
|
|
std::cout << "Client with fd: " << client->fd << " disconnected " << std::endl;
|
|
|
|
clients_.erase(client->fd);
|
|
return (sockets_.erase(client));
|
|
}
|
|
|
|
void Server::run()
|
|
{
|
|
// add serverSocket_ to sockets_
|
|
sockets_.push_back(newPollfd(serverSocket_));
|
|
|
|
while (true)
|
|
{
|
|
int ret = poll(sockets_.data(), sockets_.size(), -1);
|
|
|
|
if (ret <= 0)
|
|
{
|
|
// not sure if program should terminate
|
|
std::cerr << "Poll failed" << std::endl;
|
|
continue ;
|
|
}
|
|
|
|
for (std::vector<struct pollfd>::iterator it = sockets_.begin(); it != sockets_.end(); )
|
|
{
|
|
if (it->revents & (POLLERR | POLLHUP | POLLNVAL))
|
|
{
|
|
it = removeClient(it);
|
|
continue ;
|
|
}
|
|
|
|
if (!(it->revents & POLLIN))
|
|
{
|
|
it++;
|
|
continue ;
|
|
}
|
|
|
|
std::cout << "Checking client with fd: " << it->fd << std::endl;
|
|
|
|
// check if listening port is open, if so add client
|
|
if (it->fd == serverSocket_)
|
|
{
|
|
addClient();
|
|
it++;
|
|
break ;
|
|
}
|
|
|
|
// call handleClient for each other socket
|
|
std::map<int, User>::iterator clientIt = clients_.find(it->fd);
|
|
if (clientIt == clients_.end())
|
|
{
|
|
std::cerr << "Unknown fd in poll list: " << it->fd << std::endl;
|
|
close(it->fd);
|
|
it = sockets_.erase(it);
|
|
continue;
|
|
}
|
|
|
|
bool disconnected = handleClient(clientIt->second);
|
|
|
|
if (disconnected)
|
|
it = removeClient(it);
|
|
else
|
|
it++;
|
|
}
|
|
}
|
|
}
|