cpp_server/sockets/epoll.hpp
2025-06-12 19:01:53 -05:00

157 lines
3.8 KiB
C++

#pragma once
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <array>
#include <functional>
class EpollSocket {
public:
using ConnectionHandler = std::function<void(int client_fd)>;
using DataHandler = std::function<void(int client_fd)>;
using DisconnectHandler = std::function<void(int client_fd)>;
explicit EpollSocket(uint16_t port = 8080) : port_(port) {}
~EpollSocket() {
if (server_fd_ != -1) close(server_fd_);
if (epoll_fd_ != -1) close(epoll_fd_);
}
bool start() {
if (!create_server_socket()) return false;
if (!create_epoll()) return false;
if (!add_server_to_epoll()) return false;
return true;
}
void run() {
std::array<epoll_event, MAX_EVENTS> events;
while (running_) {
int num_events = epoll_wait(epoll_fd_, events.data(), MAX_EVENTS, -1);
if (num_events == -1) break;
for (int i = 0; i < num_events; ++i) {
if (events[i].data.fd == server_fd_) {
accept_connections();
} else {
handle_client_event(events[i].data.fd);
}
}
}
}
void stop() { running_ = false; }
bool add_client(int client_fd) {
epoll_event event{};
event.events = EPOLLIN | EPOLLET;
event.data.fd = client_fd;
return epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, client_fd, &event) != -1;
}
void remove_client(int client_fd) {
epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, client_fd, nullptr);
close(client_fd);
}
// Event handlers
void on_connection(ConnectionHandler handler) { on_connection_ = std::move(handler); }
void on_data(DataHandler handler) { on_data_ = std::move(handler); }
void on_disconnect(DisconnectHandler handler) { on_disconnect_ = std::move(handler); }
private:
static constexpr int MAX_EVENTS = 1024;
uint16_t port_;
int server_fd_ = -1;
int epoll_fd_ = -1;
bool running_ = true;
ConnectionHandler on_connection_;
DataHandler on_data_;
DisconnectHandler on_disconnect_;
bool create_server_socket() {
server_fd_ = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
if (server_fd_ == -1) return false;
int opt = 1;
if (setsockopt(server_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
return false;
}
// Enable port reuse for load balancing
if (setsockopt(server_fd_, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) == -1) {
return false;
}
// Disable Nagle's algorithm for lower latency
if (setsockopt(server_fd_, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) == -1) {
return false;
}
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port_);
if (bind(server_fd_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) {
return false;
}
return listen(server_fd_, SOMAXCONN) != -1;
}
bool create_epoll() {
epoll_fd_ = epoll_create1(EPOLL_CLOEXEC);
return epoll_fd_ != -1;
}
bool add_server_to_epoll() {
epoll_event event{};
event.events = EPOLLIN | EPOLLET;
event.data.fd = server_fd_;
return epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, server_fd_, &event) != -1;
}
inline void accept_connections() {
while (true) {
sockaddr_in client_addr{};
socklen_t client_len = sizeof(client_addr);
// Use accept4 to set non-blocking atomically
int client_fd = accept4(server_fd_,
reinterpret_cast<sockaddr*>(&client_addr),
&client_len,
SOCK_NONBLOCK | SOCK_CLOEXEC);
if (client_fd == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) break;
continue;
}
// Set TCP_NODELAY for client connections
int opt = 1;
setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
if (add_client(client_fd)) {
if (on_connection_) on_connection_(client_fd);
} else {
close(client_fd);
}
}
}
inline void handle_client_event(int client_fd) {
if (on_data_) on_data_(client_fd);
}
};