Skip to content

Instantly share code, notes, and snippets.

@ishworgurung
Created May 22, 2012 09:19
Show Gist options
  • Save ishworgurung/2767784 to your computer and use it in GitHub Desktop.
Save ishworgurung/2767784 to your computer and use it in GitHub Desktop.
What the heck? How do I write high-performance evented I/O on Linux using C? Relax. Use this skeleton. Be happy :)
/*
Description:
An example / skeleton of a high-performance socket server on Linux. Feel free
to use this code to write your own evented servers.
The code is essentially a echo server that send(2) whatever it recv(2) from the
client, back to the client socket.
The code is high-performing due to the use of epoll(7) in a non-blocking mode
with edge-triggered distribution that scales well on a large set of file
descriptors. In other words, having large set of clients connected to this
server does not deteriorate the throughput of other connected clients. Using
evented I/O is the most simplistic and economic way to achieve scalability in a
high-throughput and high-performance environment. In terms of performance, this
is the best we can get from a Linux kernel without having to resort
to kernel module, assembly code or hardware-based solutions.
epoll(7) is a scalable solution and heaps faster as it uses vector I/O (one
syscall per multiple contiguous buffers like `readv()`, `writev()`) under the
hood. It is also fast because it can be made to use edge-triggered distribution.
Author: Ishwor Gurung <ishwor@develworx.com>
Licensed under 3 clause BSD
**/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <netdb.h>
#include <fcntl.h>
#define PORT "59000"
/*
Ignored on linux > 2.6.19 though have to pass in for compatibility purpose.
*/
#define MAX_EVENTS 10
/*
The buffer to hold the message.
Note: Increasing the buffer size leads to fewer recv(2) and send(2) system
calls. However, due care is necessary to define the right buffer size for your
protocol. If the buffer size is larger than the message sizes of the protocol,
then the transmitted packets have to be padded with NULLs (or suitable values)
leading to increase in the packet size and wasted bandwidth.
*/
char buf[256];
/*
Handle a single client connection. Called once for each accept(2)'ed connection
from the event loop.
*/
void handle_connection(int client_fd) {
int nbytes;
while( (nbytes = recv(client_fd, buf, sizeof(buf), 0)) > 0){
if (send(client_fd, buf, sizeof(buf)/sizeof(char), 0) == -1){
perror("send");
}
/*
zero out the buff
*/
memset(&buf, 0, sizeof(buf));
}
if (nbytes == 0){ /* connection closed by the client */
printf("epollsrv: closed by %d\n", client_fd);
}
}
int main(int argc, char* argv[]){
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
struct addrinfo hints, *ai, *p;
struct sockaddr_storage local;
socklen_t addrlen;
int yes, rv, n;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
yes = 1; /* reuse socket */
if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0){
perror("getaddrinfo");
exit(1);
}
for(p = ai; p != NULL; p = p->ai_next) {
listen_sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (listen_sock < 0) {
continue;
}
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
if (bind(listen_sock, p->ai_addr, p->ai_addrlen) < 0) {
close(listen_sock);
continue;
}
break;
}
if (p == NULL) {
fprintf(stderr, "epollsrv: failed to bind\n");
exit(2);
}
freeaddrinfo(ai);
/* listen */
if (listen(listen_sock, 10) == -1){
perror("listen");
exit(3);
}
/* MAX_EVENTS is ignored by the kernel though */
epollfd = epoll_create(MAX_EVENTS);
if (epollfd == -1) {
perror("epoll_create");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
}
for (;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
if (errno == EINTR){ continue; }
else{
perror("epoll_pwait");
exit(EXIT_FAILURE);
}
}
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
/*
setup epoll fd; adding the accept()'ed socket fd to the epoll fd.
*/
addrlen = sizeof(local);
conn_sock = accept( listen_sock,
(struct sockaddr *) &local,
&addrlen);
if (conn_sock == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
/* set to non-blocking mode */
fcntl(conn_sock, F_SETFL, O_NONBLOCK);
/* edge-triggered; also we are interested in
read operations to the associated fd */
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) {
perror("epoll_ctl: conn_sock");
exit(EXIT_FAILURE);
}
} else {
/* handle the new connection */
handle_connection(events[n].data.fd);
/* close the connection */
shutdown(events[n].data.fd, SHUT_RDWR);
close(events[n].data.fd);
}
}
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment