Thursday, September 23, 2010

Create TCP Echo Server using Libev

Libev provides APIs to write asynchronous socket programming. This article describes how to write a simple TCP echo server using libev.

Lets start to write simple asynchronous TCP echo server with the help of libev. The server accepts client connections, reads messages from connections and echo the same message back.

Steps to write TCP echo server:
  1. Create a server socket and bind to socket address
  2. Listen on server socket
  3. Create watcher to accept connection
  4. Write connection accept callback function
  5. Create and initialize watcher to read message from client
  6. Write callback function to read message
  7. Start event loop

1. Create a server socket and bind to socket address

First step is to create TCP internet socket and bind it to some port.
sd = socket(PF_INET, SOCK_STREAM, 0);

bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT_NO);
addr.sin_addr.s_addr = INADDR_ANY;

bind(sd, (struct sockaddr*) &addr, sizeof(addr));

2. Listen on server socket

To accept client connection, you need to start listing on server socket. Here we are using backlog of size 2.
listen(sd, 2);

3. Create watcher to accept connection

Now we accept client connection using 'accept' API. Before that, we create a watcher to keep watch on accept call. Initialize the watcher using server socket and connection accept callback function.
ev_io_init(&w_accept, accept_cb, sd, EV_READ);
ev_io_start(loop, &w_accept);

4. Write connection accept callback function

Write appropriate callback function to accept client connection.
void accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents)
{
...
client_sd = accept(watcher->fd, (struct sockaddr *)&client_addr, &client_len);
...
}

5. Create and initialize watcher to read message from client

A new client socket is created on accepting the connection. To read messages from client, create a new watcher. Initialize it with client socket and read callback function.

w_client = (struct ev_io*) malloc (sizeof(struct ev_io));
...
ev_io_init(w_client, read_cb, client_sd, EV_READ);
ev_io_start(loop, w_client);

6. Write callback function to read message

Write appropriate read callback function to receive message from client and echo the same message back.
void read_cb(struct ev_loop *loop, struct ev_io *watcher, int revents)
{
...
read = recv(watcher->fd, buffer, BUFFER_SIZE, 0);
send(watcher->fd, buffer, read, 0);
...
}

7. Start event loop

Final step is to run infinite event loop to wait for events.

while (1)
{
ev_loop(loop, 0);
}

Now we have done with server code.

Here is the complete code for the server.

#include <stdio.h>
#include <netinet/in.h>
#include <ev.h>

#define PORT_NO 3033
#define BUFFER_SIZE 1024

int total_clients = 0; // Total number of connected clients

void accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents);
void read_cb(struct ev_loop *loop, struct ev_io *watcher, int revents);

int main()
{
struct ev_loop *loop = ev_default_loop(0);
int sd;
struct sockaddr_in addr;
int addr_len = sizeof(addr);
struct ev_io w_accept;

// Create server socket
if( (sd = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
{
perror("socket error");
return -1;
}

bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT_NO);
addr.sin_addr.s_addr = INADDR_ANY;

// Bind socket to address
if (bind(sd, (struct sockaddr*) &addr, sizeof(addr)) != 0)
{
perror("bind error");
}

// Start listing on the socket
if (listen(sd, 2) < 0)
{
perror("listen error");
return -1;
}

// Initialize and start a watcher to accepts client requests
ev_io_init(&w_accept, accept_cb, sd, EV_READ);
ev_io_start(loop, &w_accept);

// Start infinite loop
while (1)
{
ev_loop(loop, 0);
}

return 0;
}

/* Accept client requests */
void accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents)
{
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_sd;
struct ev_io *w_client = (struct ev_io*) malloc (sizeof(struct ev_io));

if(EV_ERROR & revents)
{
perror("got invalid event");
return;
}

// Accept client request
client_sd = accept(watcher->fd, (struct sockaddr *)&client_addr, &client_len);

if (client_sd < 0)
{
perror("accept error");
return;
}

total_clients ++; // Increment total_clients count
printf("Successfully connected with client.\n");
printf("%d client(s) connected.\n", total_clients);

// Initialize and start watcher to read client requests
ev_io_init(w_client, read_cb, client_sd, EV_READ);
ev_io_start(loop, w_client);
}

/* Read client message */
void read_cb(struct ev_loop *loop, struct ev_io *watcher, int revents){
char buffer[BUFFER_SIZE];
ssize_t read;

if(EV_ERROR & revents)
{
perror("got invalid event");
return;
}

// Receive message from client socket
read = recv(watcher->fd, buffer, BUFFER_SIZE, 0);

if(read < 0)
{
perror("read error");
return;
}

if(read == 0)
{
// Stop and free watchet if client socket is closing
ev_io_stop(loop,watcher);
free(watcher);
perror("peer might closing");
total_clients --; // Decrement total_clients count
printf("%d client(s) connected.\n", total_clients);
return;
}
else
{
printf("message:%s\n",buffer);
}

// Send message bach to the client
send(watcher->fd, buffer, read, 0);
bzero(buffer, read);
}


Below is the complete code for basic client which connect to server and, send and receive message. You can also write your own client.
#include <stdio.h>
#include <netinet/in.h>

#define PORT_NO 3033
#define BUFFER_SIZE 1024

int main()
{
int sd;
struct sockaddr_in addr;
int addr_len = sizeof(addr);
char buffer[BUFFER_SIZE] = "";

// Create client socket
if( (sd = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
{
perror("socket error");
return -1;
}

bzero(&addr, sizeof(addr));

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

// Connect to server socket
if(connect(sd, (struct sockaddr *)&addr, sizeof addr) < 0)
{
perror("Connect error");
return -1;
}

while (strcmp(buffer, "q") != 0)
{
// Read input from user and send message to the server
gets(buffer);
send(sd, buffer, strlen(buffer), 0);

// Receive message from the server
recv(sd, buffer, BUFFER_SIZE, 0);
printf("message: %s\n", buffer);
}

return 0;
}