Skip to content

Instantly share code, notes, and snippets.

@michelesr
Last active August 8, 2024 20:19
Show Gist options
  • Save michelesr/8b18eb4929d7e50eefa3c849738068cb to your computer and use it in GitHub Desktop.
Save michelesr/8b18eb4929d7e50eefa3c849738068cb to your computer and use it in GitHub Desktop.
Example epoll event loop based TCP server
#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;
}
@michelesr
Copy link
Author

michelesr commented Aug 7, 2024

This version was mainly generated by a LLM, but nevertheless it's working. Calls to read() and write() 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.

@michelesr
Copy link
Author

michelesr commented Aug 8, 2024

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