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;
}

14 comments:

  1. Awesome post.. very usefull.. keep on posting such great articles...

    ReplyDelete
  2. I was looking for an example like this and couldn't find one so I created my own (but using unix sockets):

    http://github.com/coolaj86/libev-examples

    I'll take a closer look through yours later and see if there's some things I can use to make my example simpler.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. And how to change this so that incoming messages are brodcasted to ALL connected clients?

    ReplyDelete
  5. Use 'sendto' system call to broadcast the message to all clients.

    ReplyDelete
  6. 1) "SOCK_STREAM" & broadcast? - nonsense
    2.1) look to your "read_cb". if ("read" < 0) it is'nt means that all is so bad. check (errno == EAGAIN)
    2.2) stop watchers if you really have a error in (2.1)
    2.3) what happens if "send" can not write data imediately??!!
    4) RTFM please, rewrite code or die

    ReplyDelete
  7. There is no any improvement from my side.

    Note: This article describes how to use libev with socket programming. It does not elaborate on socket programming.

    ReplyDelete
  8. Please add a close socket

    CHANGE THIS
    ev_io_stop(loop,watcher);
    free(watcher);

    to
    ev_io_stop(loop,watcher);
    close(watcher->fd);
    free(watcher);

    ReplyDelete
  9. I believe you can replace (in the server):

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

    with:

    ev_loop(loop, 0);


    No infinite needed?

    ---John Chludzinski

    ReplyDelete
    Replies
    1. You can, and it's most likely the preferable way to do it. There isn't an easy way to exit out of the application after handling a signal for example, since we're stuck in the infinite loop.

      Delete
  10. As client a simple

    telnet 127.0.0.1 3303

    can conveniently be used.

    ReplyDelete
  11. Top 10 Casinos Near Harrah's Casino & Spa, Philadelphia
    Harrah's Philadelphia 여주 출장안마 Casino & Spa. Harrah's Philadelphia is one of the 익산 출장안마 largest, 안양 출장마사지 most popular hotels 경산 출장마사지 in the area. It is located in the marina area, 청주 출장안마 near

    ReplyDelete