Last active
March 3, 2024 16:49
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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