Last active
August 8, 2024 20:19
-
-
Save michelesr/8b18eb4929d7e50eefa3c849738068cb to your computer and use it in GitHub Desktop.
Example epoll event loop based TCP server
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 <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#include <arpa/inet.h> | |
#include <unistd.h> | |
#include <sys/epoll.h> | |
#define BUFFER_SIZE 256 | |
#define PORT 4000 | |
#define IP_ADDRESS "127.0.0.1" | |
#define MAX_EVENTS 10 | |
int main() { | |
int server_fd, client_fd, epoll_fd; | |
struct sockaddr_in server_addr, client_addr; | |
socklen_t client_len = sizeof(client_addr); | |
char buffer[BUFFER_SIZE]; | |
// Create a socket | |
server_fd = socket(AF_INET, SOCK_STREAM, 0); | |
if (server_fd < 0) { | |
perror("socket creation failed"); | |
exit(EXIT_FAILURE); | |
} | |
// Set address and port number for the server | |
server_addr.sin_family = AF_INET; | |
server_addr.sin_port = htons(PORT); | |
inet_pton(AF_INET, IP_ADDRESS, &server_addr.sin_addr); | |
// Bind the socket to the address and port | |
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { | |
perror("bind failed"); | |
exit(EXIT_FAILURE); | |
} | |
// Listen for incoming connections | |
if (listen(server_fd, 3) < 0) { | |
perror("listen failed"); | |
exit(EXIT_FAILURE); | |
} | |
printf("Server listening on %s:%d...\n", IP_ADDRESS, PORT); | |
// Create an epoll instance | |
epoll_fd = epoll_create1(0); | |
if (epoll_fd < 0) { | |
perror("epoll creation failed"); | |
exit(EXIT_FAILURE); | |
} | |
// Add the server socket to the epoll instance | |
struct epoll_event event; | |
event.events = EPOLLIN; | |
event.data.fd = server_fd; | |
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) { | |
perror("epoll_ctl failed"); | |
exit(EXIT_FAILURE); | |
} | |
while (1) { | |
// Wait for events on the epoll instance | |
struct epoll_event events[MAX_EVENTS]; | |
int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); | |
if (num_events < 0) { | |
perror("epoll_wait failed"); | |
exit(EXIT_FAILURE); | |
} | |
// Handle each event | |
for (int i = 0; i < num_events; i++) { | |
if (events[i].data.fd == server_fd) { | |
// Accept a new connection | |
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len); | |
if (client_fd < 0) { | |
perror("accept failed"); | |
continue; | |
} | |
printf("Connection accepted from client IP address %s and port %d...\n", | |
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); | |
// Add the client socket to the epoll instance | |
event.events = EPOLLIN; | |
event.data.fd = client_fd; | |
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) < 0) { | |
perror("epoll_ctl failed"); | |
continue; | |
} | |
} else { | |
// Read from the client | |
int bytes_read = read(events[i].data.fd, buffer, BUFFER_SIZE); | |
if (bytes_read < 0) { | |
perror("read failed"); | |
continue; | |
} else if (bytes_read == 0) { | |
printf("Client disconnected...\n"); | |
close(events[i].data.fd); | |
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL); | |
continue; | |
} | |
printf("Received message from client: %s\n", buffer); | |
// Check for newline character | |
if (buffer[0] == '\n') { | |
printf("Client sent newline, closing connection...\n"); | |
close(events[i].data.fd); | |
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL); | |
continue; | |
} | |
// Write back to the client | |
int bytes_written = write(events[i].data.fd, buffer, bytes_read); | |
if (bytes_written < 0) { | |
perror("write failed"); | |
continue; | |
} | |
} | |
} | |
} | |
return 0; | |
} | |
Before line 105 it should probably terminate the string before printing it to avoid printing after the end of the message:
buffer[bytes_read] = '\0';
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This version was mainly generated by a LLM, but nevertheless it's working. Calls to
read()
andwrite()
are blocking, but this is acceptable for this simple example... production server probably will want to make the sockets file descriptors non blocking to improve performance, but this will increase complexity as error handling is different for async write/read, and if you defer processing data you'll need to dynamically allocate a buffer for each client in order to avoid overwriting data that isn't processed yet.