VARUNA JAYASIRI

@vpj

TCP Echo Server Example in C++ Using Epoll

January 2, 2011

This example is a simple server which accepts connections and echos whatever data sent to the server. This example also demonstrates the use of epoll, which is efficient than poll. In epoll unlike poll all events that need to be monitored are not passed everytime the wait call is made. Epoll uses event registration where events to be watched can be added, modified or removed. This makes it efficient when there are a large number of events to be watched.

IOLoop

In this example the class IOLoop will deal with epoll interface and it will invoke relevant handlers based on events occurred.

class IOLoop {
...
 static IOLoop * getInstance();

 IOLoop() {
  this->epfd = epoll_create(this->EPOLL_EVENTS);

  if(this->epfd < 0) {
   log_error("Failed to create epoll");
   exit(1);
  }
...
 }

 void start() {
  for(;;) {
   int nfds = epoll_wait(this->epfd, this->events, this->MAX_EVENTS, -1 /* Timeout */);

   for(int i = 0; i < nfds; ++i) {
    int fd = this->events[i].data.fd;
    Handler *h = handlers[fd];
    h->handle(this->events[i]);
   }
  }
 }

 void addHandler(int fd, Handler *handler, unsigned int events) {
  handlers[fd] = handler;
  epoll_event e;
  e.data.fd = fd;
  e.events = events;

  if(epoll_ctl(this->epfd, EPOLL_CTL_ADD, fd, &e) < 0) {
   log_error("Failed to insert handler to epoll");
  }
 }

 void modifyHandler(int fd, unsigned int events);

 void removeHandler(int fd);
};

Handlers used in this example are ServerHandler and EchoHandler which derive from class Handler. Handlers have a member function handle which handles the event occurred.

ServerHandler

ServerHandler will create a server socket and handle in coming connections

class ServerHandler : Handler {
...

 ServerHandler(int port) {
  memset(&addr, 0, sizeof(addr));

  if ((fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
   log_error("Failed to create server socket");
   exit(1);
  }

  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = htonl(INADDR_ANY);
  addr.sin_port = htons(port);

  if (bind(fd, (struct sockaddr *) &addr,
                               sizeof(addr)) < 0) {
   log_error("Failed to bind server socket");
   exit(1);
  }

  if (listen(fd, MAX_PENDING) < 0) {
   log_error("Failed to listen on server socket");
   exit(1);
  }
  setnonblocking(fd);

  IOLoop::getInstance()->addHandler(fd, this, EPOLLIN);
 }

 virtual int handle(epoll_event e) {
  sockaddr_in client_addr;
  socklen_t ca_len = sizeof(client_addr);

  int client = accept(fd, (struct sockaddr *) &client_addr,
                  &ca_len);

  if(client < 0) {
   log_error("Error accepting connection");
   return -1;
  }

  cout << "Client connected: " << inet_ntoa(client_addr.sin_addr) << endl;
  new EchoHandler(client, client_addr);
  return 0;
 }
};

Set Non-blocking

Function setnonblocking sets the file descriptor setting to non-clocking.

flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

EchoHandler:handle

EchoHandler will write whatever it reads from the socket

virtual int handle(epoll_event e) {
 if(e.events & EPOLLHUP) {
  IOLoop::getInstance()->removeHandler(fd);
  return -1;
 }

 if(e.events & EPOLLERR) {
  return -1;
 }

 if(e.events & EPOLLOUT) {
  if(received > 0) {
   cout << "Writing: " << buffer << endl;
   if (send(fd, buffer, received, 0) != received) {
    log_error("Error writing to socket");
   }
  }

  IOLoop::getInstance()->modifyHandler(fd, EPOLLIN);
 }

 if(e.events & EPOLLIN) {
  if ((received = recv(fd, buffer, BUFFER_SIZE, 0)) < 0) {
   log_error("Error reading from socket");
  } else if(received > 0) {
   buffer[received] = 0;
   cout << "Reading: " << buffer << endl;
  }

  if(received > 0) {
   IOLoop::getInstance()->modifyHandler(fd, EPOLLOUT);
  } else {
   IOLoop::getInstance()->removeHandler(fd);
  }
 }

 return 0;
}

Error checking in this code is minimal so it will probably fail unexpectedly in certain scenarios which I have not come across yet. And please leave a comment if you do find any errors or if there are things that could be improved.

///TCP Echo Server Example in C++ Using Epoll This example is a simple server which accepts connections and echos whatever data sent to the server. This example also demonstrates the use of **epoll**, which is efficient than **poll**. In epoll unlike poll all events that need to be monitored are not passed everytime the wait call is made. Epoll uses event registration where events to be watched can be added, modified or removed. This makes it efficient when there are a large number of events to be watched. ##IOLoop In this example the class IOLoop will deal with epoll interface and it will invoke relevant handlers based on events occurred. ```c class IOLoop { ... static IOLoop * getInstance(); IOLoop() { this->epfd = epoll_create(this->EPOLL_EVENTS); if(this->epfd < 0) { log_error("Failed to create epoll"); exit(1); } ... } void start() { for(;;) { int nfds = epoll_wait(this->epfd, this->events, this->MAX_EVENTS, -1 /* Timeout */); for(int i = 0; i < nfds; ++i) { int fd = this->events[i].data.fd; Handler *h = handlers[fd]; h->handle(this->events[i]); } } } void addHandler(int fd, Handler *handler, unsigned int events) { handlers[fd] = handler; epoll_event e; e.data.fd = fd; e.events = events; if(epoll_ctl(this->epfd, EPOLL_CTL_ADD, fd, &e) < 0) { log_error("Failed to insert handler to epoll"); } } void modifyHandler(int fd, unsigned int events); void removeHandler(int fd); }; Handlers used in this example are ServerHandler and EchoHandler which derive from class Handler. Handlers have a member function handle which handles the event occurred. ## ServerHandler ServerHandler will create a server socket and handle in coming connections ```c class ServerHandler : Handler { ... ServerHandler(int port) { memset(&addr, 0, sizeof(addr)); if ((fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { log_error("Failed to create server socket"); exit(1); } addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { log_error("Failed to bind server socket"); exit(1); } if (listen(fd, MAX_PENDING) < 0) { log_error("Failed to listen on server socket"); exit(1); } setnonblocking(fd); IOLoop::getInstance()->addHandler(fd, this, EPOLLIN); } virtual int handle(epoll_event e) { sockaddr_in client_addr; socklen_t ca_len = sizeof(client_addr); int client = accept(fd, (struct sockaddr *) &client_addr, &ca_len); if(client < 0) { log_error("Error accepting connection"); return -1; } cout << "Client connected: " << inet_ntoa(client_addr.sin_addr) << endl; new EchoHandler(client, client_addr); return 0; } }; ## Set Non-blocking Function setnonblocking sets the file descriptor setting to non-clocking. ```c flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); ## EchoHandler:handle EchoHandler will write whatever it reads from the socket ```c virtual int handle(epoll_event e) { if(e.events & EPOLLHUP) { IOLoop::getInstance()->removeHandler(fd); return -1; } if(e.events & EPOLLERR) { return -1; } if(e.events & EPOLLOUT) { if(received > 0) { cout << "Writing: " << buffer << endl; if (send(fd, buffer, received, 0) != received) { log_error("Error writing to socket"); } } IOLoop::getInstance()->modifyHandler(fd, EPOLLIN); } if(e.events & EPOLLIN) { if ((received = recv(fd, buffer, BUFFER_SIZE, 0)) < 0) { log_error("Error reading from socket"); } else if(received > 0) { buffer[received] = 0; cout << "Reading: " << buffer << endl; } if(received > 0) { IOLoop::getInstance()->modifyHandler(fd, EPOLLOUT); } else { IOLoop::getInstance()->removeHandler(fd); } } return 0; } Error checking in this code is minimal so it will probably fail unexpectedly in certain scenarios which I have not come across yet. And please leave a comment if you do find any errors or if there are things that could be improved.