Skip to content

Instantly share code, notes, and snippets.

@msiedlarek
Created August 7, 2012 10:04
Show Gist options
  • Save msiedlarek/3284177 to your computer and use it in GitHub Desktop.
Save msiedlarek/3284177 to your computer and use it in GitHub Desktop.
Telnet echo server based on libevent2
all: server
server: server.c
clang -Wall -Wextra -std=c99 -pedantic -g -levent -o server server.c
clean:
rm -rf server server.dSYM
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define PR_UNUSED_VARIABLE(x) (void)(sizeof((x), 0))
typedef enum {
PR_ERROR_NO_ERROR = 0,
PR_ERROR_UNSUPPORTED_ADDRESS_FAMILY = 1,
PR_ERROR_SOCKET_ERROR = 2,
PR_ERROR_INTERNET_ADDRESS_PROBLEM = 3,
PR_ERROR_LIBEVENT_ERROR = 4
} pr_error_t;
/* Hostname, IP address or NULL to use any. */
const char * LISTENER_HOSTNAME = "localhost";
/* Port number, service name from /etc/services or NULL to use any. */
const char * LISTENER_SERVICE = "4000";
static const int LISTENER_QUEUE_LIMIT = 16;
static const int LINE_LENGTH_LIMIT = 2048;
void
read_callback(
struct bufferevent * buffer_event,
void * null_argument
) {
PR_UNUSED_VARIABLE(null_argument);
struct evbuffer * input_buffer = bufferevent_get_input(buffer_event);
struct evbuffer * output_buffer = bufferevent_get_output(buffer_event);
char * line;
size_t line_size;
while (1) {
line = evbuffer_readln(input_buffer, &line_size, EVBUFFER_EOL_LF);
if (line == NULL) {
break;
}
evbuffer_add(output_buffer, line, line_size);
evbuffer_add(output_buffer, "\n", 1);
free(line);
}
if (evbuffer_get_length(input_buffer) >= LINE_LENGTH_LIMIT) {
/* Next line is already too long; just process what there is and go
* on so that the buffer doesn't grow infinitely long. */
char buf[1024];
while (evbuffer_get_length(input_buffer)) {
int n = evbuffer_remove(input_buffer, buf, sizeof(buf));
evbuffer_add(output_buffer, buf, n);
}
evbuffer_add(output_buffer, "\n", 1);
}
}
void
event_callback(
struct bufferevent * buffer_event,
short event,
void * null_argument
) {
PR_UNUSED_VARIABLE(null_argument);
if (event & BEV_EVENT_EOF) {
fputs("Connection closed.\n", stdout);
} else if (event & BEV_EVENT_ERROR) {
fprintf(
stderr,
"Connection error: %s\n",
evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())
);
} else if (event & BEV_EVENT_TIMEOUT) {
fputs("Connection timeout.\n", stdout);
}
bufferevent_free(buffer_event);
}
void
do_accept(
evutil_socket_t listener_socket,
short event,
void * libevent_base_pointer
) {
PR_UNUSED_VARIABLE(event);
struct event_base * libevent_base = libevent_base_pointer;
struct sockaddr_storage socket_address;
socklen_t socket_address_size = sizeof(socket_address);
evutil_socket_t connection_socket = accept(
listener_socket,
(struct sockaddr *)(&socket_address),
&socket_address_size
);
if (connection_socket < 0) {
fprintf(
stderr,
"accept(): %s\n",
evutil_socket_error_to_string(
evutil_socket_geterror(listener_socket)
)
);
return;
}
if (connection_socket > FD_SETSIZE) {
evutil_closesocket(connection_socket);
return;
}
if (evutil_make_socket_nonblocking(connection_socket) == -1) {
fprintf(
stderr,
"evutil_make_socket_nonblocking(): %s\n",
evutil_socket_error_to_string(
evutil_socket_geterror(connection_socket)
)
);
evutil_closesocket(connection_socket);
return;
}
struct bufferevent * buffer_event = bufferevent_socket_new(
libevent_base,
connection_socket,
BEV_OPT_CLOSE_ON_FREE
);
bufferevent_setcb(
buffer_event,
read_callback,
NULL,
event_callback,
NULL
);
bufferevent_setwatermark(
buffer_event,
EV_READ,
0,
LINE_LENGTH_LIMIT
);
bufferevent_enable(buffer_event, EV_READ | EV_WRITE);
}
pr_error_t
pr_convert_addrinfo_to_strings(
const struct addrinfo * const service_address,
char (* const ip) [INET6_ADDRSTRLEN],
int * const port
) {
void * listener_address;
if (service_address->ai_family == AF_INET) {
/* Get address information for IPv4. */
listener_address = (void *)(&(
((struct sockaddr_in *)service_address->ai_addr)->sin_addr
));
(*port) = ntohs(
((struct sockaddr_in *)service_address->ai_addr)->sin_port
);
} else if (service_address->ai_family == AF_INET6) {
/* Get address information for IPv6. */
listener_address = (void *)(&(
((struct sockaddr_in6 *)service_address->ai_addr)->sin6_addr
));
(*port) = ntohs(
((struct sockaddr_in6 *)service_address->ai_addr)->sin6_port
);
} else {
return PR_ERROR_UNSUPPORTED_ADDRESS_FAMILY;
}
/* Convert IP address to it's printable representation. */
evutil_inet_ntop(
service_address->ai_family,
listener_address,
(*ip),
sizeof(*ip)
);
return PR_ERROR_NO_ERROR;
}
pr_error_t
pr_start_server(
const char * const listening_address,
const char * const listening_port
) {
struct addrinfo service_address_hints;
struct addrinfo * service_addresses;
struct addrinfo * service_address;
char listener_ip_string[INET6_ADDRSTRLEN];
int listener_port;
evutil_socket_t listener_socket = -1;
struct event_base * libevent_base;
struct event * listener_event;
libevent_base = event_base_new();
if (libevent_base == NULL) {
fputs("Cannot create libevent event base.\n", stderr);
return PR_ERROR_LIBEVENT_ERROR;
}
memset(&service_address_hints, 0, sizeof(service_address_hints));
/* Use IPv4 or IPv6. */
service_address_hints.ai_family = AF_UNSPEC;
/* Use TCP protocol. */
service_address_hints.ai_socktype = SOCK_STREAM;
/* Choose IP automatically. */
service_address_hints.ai_flags = AI_PASSIVE;
int eai_error = evutil_getaddrinfo(
listening_address,
listening_port,
&service_address_hints,
&service_addresses
);
if (eai_error) {
fprintf(
stderr,
"evutil_getaddrinfo(): %s\n",
evutil_gai_strerror(eai_error)
);
return PR_ERROR_INTERNET_ADDRESS_PROBLEM;
}
for (service_address = service_addresses; service_address != NULL;
service_address = service_address->ai_next) {
listener_socket = socket(
service_address->ai_family,
service_address->ai_socktype,
service_address->ai_protocol
);
if (listener_socket == -1) {
fprintf(
stderr,
"socket(): %s\n",
evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())
);
continue;
}
if (evutil_make_socket_nonblocking(listener_socket) == -1) {
fprintf(
stderr,
"evutil_make_socket_nonblocking(): %s\n",
evutil_socket_error_to_string(
evutil_socket_geterror(listener_socket)
)
);
evutil_freeaddrinfo(service_addresses);
evutil_closesocket(listener_socket);
return PR_ERROR_SOCKET_ERROR;
}
if (evutil_make_listen_socket_reuseable(listener_socket) == -1) {
fprintf(
stderr,
"evutil_make_listen_socket_reuseable(): %s\n",
evutil_socket_error_to_string(
evutil_socket_geterror(listener_socket)
)
);
evutil_freeaddrinfo(service_addresses);
evutil_closesocket(listener_socket);
return PR_ERROR_SOCKET_ERROR;
}
if (bind(
listener_socket,
service_address->ai_addr,
service_address->ai_addrlen
) == -1) {
fprintf(
stderr,
"bind(): %s\n",
evutil_socket_error_to_string(
evutil_socket_geterror(listener_socket)
)
);
evutil_closesocket(listener_socket);
continue;
}
break;
}
if (service_address == NULL) {
evutil_freeaddrinfo(service_addresses);
return PR_ERROR_INTERNET_ADDRESS_PROBLEM;
}
/* Convert IP address to it's printable representation. */
if (pr_convert_addrinfo_to_strings(
service_address,
&listener_ip_string,
&listener_port
) != PR_ERROR_NO_ERROR) {
fputs("Cannot convert address to string.", stderr);
evutil_freeaddrinfo(service_addresses);
evutil_closesocket(listener_socket);
return PR_ERROR_INTERNET_ADDRESS_PROBLEM;
}
evutil_freeaddrinfo(service_addresses);
if (listen(listener_socket, LISTENER_QUEUE_LIMIT) == -1) {
fprintf(
stderr,
"listen(): %s\n",
evutil_socket_error_to_string(
evutil_socket_geterror(listener_socket)
)
);
return PR_ERROR_SOCKET_ERROR;
}
printf("Listening on %s port %d\n", listener_ip_string, listener_port);
listener_event = event_new(
libevent_base,
listener_socket,
EV_READ | EV_PERSIST,
do_accept,
(void *)libevent_base
);
event_add(listener_event, NULL);
event_base_dispatch(libevent_base);
return PR_ERROR_NO_ERROR;
}
int
main(
void
) {
if (pr_start_server(LISTENER_HOSTNAME, LISTENER_SERVICE)
!= PR_ERROR_NO_ERROR) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment