Created
August 7, 2012 10:04
-
-
Save msiedlarek/3284177 to your computer and use it in GitHub Desktop.
Telnet echo server based on libevent2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
all: server | |
server: server.c | |
clang -Wall -Wextra -std=c99 -pedantic -g -levent -o server server.c | |
clean: | |
rm -rf server server.dSYM |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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