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:
- Create a server socket and bind to socket address
- Listen on server socket
- Create watcher to accept connection
- Write connection accept callback function
- Create and initialize watcher to read message from client
- Write callback function to read message
- 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;
}
Awesome post.. very usefull.. keep on posting such great articles...
ReplyDeleteI was looking for an example like this and couldn't find one so I created my own (but using unix sockets):
ReplyDeletehttp://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.
This comment has been removed by the author.
ReplyDeleteAnd how to change this so that incoming messages are brodcasted to ALL connected clients?
ReplyDeleteUse 'sendto' system call to broadcast the message to all clients.
ReplyDelete1) "SOCK_STREAM" & broadcast? - nonsense
ReplyDelete2.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
O.o Any Improvements on this?
ReplyDeleteThere is no any improvement from my side.
ReplyDeleteNote: This article describes how to use libev with socket programming. It does not elaborate on socket programming.
There is a leak in accept_cb
ReplyDeletePlease add a close socket
ReplyDeleteCHANGE THIS
ev_io_stop(loop,watcher);
free(watcher);
to
ev_io_stop(loop,watcher);
close(watcher->fd);
free(watcher);
I believe you can replace (in the server):
ReplyDeletewhile (1)
{
ev_loop(loop, 0);
}
with:
ev_loop(loop, 0);
No infinite needed?
---John Chludzinski
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.
DeleteAs client a simple
ReplyDeletetelnet 127.0.0.1 3303
can conveniently be used.
Top 10 Casinos Near Harrah's Casino & Spa, Philadelphia
ReplyDeleteHarrah'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