Skip to content

Instantly share code, notes, and snippets.

@joshuashaffer
Last active March 3, 2024 16:49
Show Gist options
  • Save joshuashaffer/dbe29a2f530dc8a4ae43a4d1baa87032 to your computer and use it in GitHub Desktop.
Save joshuashaffer/dbe29a2f530dc8a4ae43a4d1baa87032 to your computer and use it in GitHub Desktop.
Dummy epoll server that accept connections and keeps them open by sending a single character every 15 seconds.
#include <array>
#include <atomic>
#include <csignal>
#include <unordered_set>
#include <arpa/inet.h>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <fcntl.h>
#include <string>
#include <sys/epoll.h>
#include <unistd.h>
#include <vector>
constexpr int MAX_EVENTS{100};
constexpr int MAX_BUFFER{1024};
namespace {
using std::array;
using std::string;
using std::unordered_set;
using std::vector;
using std::atomic;
atomic is_interrupted{false};
void sig_int_handler(const int) {
is_interrupted.store(true);
}
void set_non_blocking(const int sockfd) {
const int flags{fcntl(sockfd, F_GETFL, 0)};
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
}
int is_socket_open(const int fd) {
const int flags{fcntl(fd, F_GETFL)};
if (flags == -1) {
perror("Error getting file status flags");
return -1;
}
// Check if the file descriptor is associated with a socket
return (flags & O_ACCMODE) == O_RDWR || (flags & O_ACCMODE) == O_RDONLY ||
(flags & O_ACCMODE) == O_WRONLY;
}
} // namespace
int main(const int argc, const char *argv[]) {
const vector<string> args(argv, argv + argc);
if (args.size() <= 1) {
printf("Usage: %s port\n", args.at(0).c_str());
exit(EXIT_FAILURE);
}
const auto port{static_cast<uint16_t>(std::stoi(args.at(1)))};
socklen_t addrlen{sizeof(sockaddr_in)};
// Create a TCP socket
int server_fd;
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Set server socket to reuse address and port
if (constexpr int opt{1};
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt,
sizeof(opt))) {
perror("Setsockopt failed");
exit(EXIT_FAILURE);
}
// Initialize server address structure
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
// Bind the socket to the specified address and port
if (bind(server_fd, reinterpret_cast<sockaddr *>(&server_addr),
sizeof(server_addr)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
// Set the server socket to listen
if (listen(server_fd, MAX_EVENTS) < 0) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
// Create an epoll instance
const int epoll_fd{epoll_create1(0)};
if (epoll_fd == -1) {
perror("Epoll creation failed");
exit(EXIT_FAILURE);
}
// Add the server socket to the epoll event list
epoll_event ev{};
ev.events = EPOLLIN;
ev.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) {
perror("Epoll control failed");
exit(EXIT_FAILURE);
}
// setup signal handlers
if(signal(SIGINT, sig_int_handler) == SIG_ERR) {
perror("Error setting SIGINT handler");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", port);
time_t last_time{0};
unordered_set<int> open_fd;
array<epoll_event, MAX_EVENTS> events{};
array<uint8_t, MAX_BUFFER> buffer{};
while (!is_interrupted.load()) {
// Wait for events
const auto ready{epoll_wait(epoll_fd, events.data(), events.size(), 1)};
const auto current_time{time(nullptr)};
for (int i{0}; i < ready; i++) {
if (events.at(i).data.fd == server_fd) {
// Accept a new connection
sockaddr_in client_addr{};
auto client_fd{accept(
server_fd, reinterpret_cast<sockaddr *>(&client_addr), &addrlen)};
if (client_fd == -1) {
perror("Accept failed");
continue;
}
// Set the client socket to non-blocking
set_non_blocking(client_fd);
int optval{1};
if (constexpr socklen_t optlen{sizeof(optval)};
setsockopt(client_fd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) ==
-1) {
perror("Setsockopt failed");
close(client_fd);
continue;
}
// Add the client socket to the epoll event list
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) == -1) {
perror("Epoll control failed for client");
exit(EXIT_FAILURE);
}
printf("New connection from %s:%d\n", inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
open_fd.insert(client_fd);
} else {
auto client_fd{events.at(i).data.fd};
int interrupt_count{0};
INTERRUPTED:
if (const auto bytes_received = recv(
client_fd, buffer.data(),
(buffer.size() - 1) * (sizeof(decltype(buffer)::value_type)),
0);
bytes_received <= 0) {
if (bytes_received == -1) {
switch (errno) {
case EINTR:
if (interrupt_count < 10) {
// sleep for 1ms
usleep(50);
interrupt_count++;
goto INTERRUPTED;
}
break;
default:
// Fall through to close the socket.
continue;
}
printf("Error from Client %d disconnecting, bytes=%lu\n", client_fd,
bytes_received);
} else {
printf("Client %d disconnected, bytes=%lu\n", client_fd,
bytes_received);
}
close(client_fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, nullptr);
open_fd.erase(client_fd);
} else {
// print the message
buffer.at(bytes_received) = '\0';
for (int j{0}; j < bytes_received; j++) {
if (const auto ch = buffer.at(j); !isprint(ch)) {
buffer.at(j) = ' ';
}
}
printf("Received %lu bytes from client %d: %s\n", bytes_received,
client_fd, reinterpret_cast<char *>(buffer.data()));
}
}
}
// Print number of open sockets. Close zombies.
if (current_time - last_time > 15) {
int open_sockets{0};
unordered_set<int> to_delete{};
for (const auto client_fd : open_fd) {
if (is_socket_open(client_fd)) {
++open_sockets;
} else {
to_delete.insert(client_fd);
}
}
for (const auto client_fd : to_delete) {
close(client_fd);
open_fd.erase(client_fd);
}
printf("Sending single character to each open socket...");
array msg{'A'};
for (const auto client_fd : open_fd) {
if (send(client_fd, msg.data(), msg.size(), 0) == -1) {
perror("Error sending to client");
}
}
printf("Done: %d Connections\n", open_sockets);
last_time = current_time;
}
}
// Clean up
printf("Done\n");
for(const auto client_fd : open_fd) {
close(client_fd);
}
close(server_fd);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment