Rewrite to use libev
libuv is too immature so far and I'm not in the mood to try and link it statically via some horrible hack (no CMake support). Also libev is much closer to my understanding of event loops. The messaging model stays for when/if I want to return to libuv.
This commit is contained in:
parent
606c5f43af
commit
74965b0f66
|
@ -20,9 +20,13 @@ set (project_VERSION "${project_VERSION_MAJOR}")
|
|||
set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}")
|
||||
set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
|
||||
|
||||
# For custom modules
|
||||
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
||||
|
||||
# Dependencies
|
||||
find_package (PkgConfig REQUIRED)
|
||||
pkg_check_modules (dependencies REQUIRED ncursesw libuv>=0.11)
|
||||
pkg_check_modules (ncursesw REQUIRED ncursesw)
|
||||
find_package (LibEV REQUIRED)
|
||||
|
||||
if (USE_SYSTEM_TERMO)
|
||||
find_package (Termo REQUIRED)
|
||||
|
@ -38,11 +42,12 @@ else (USE_SYSTEM_TERMO)
|
|||
set (Termo_LIBRARIES termo-static)
|
||||
endif (USE_SYSTEM_TERMO)
|
||||
|
||||
include_directories (${dependencies_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS})
|
||||
include_directories (${ncursesw_INCLUDE_DIRS}
|
||||
${LIBEV_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS})
|
||||
|
||||
# Configuration
|
||||
include (CheckFunctionExists)
|
||||
set (CMAKE_REQUIRED_LIBRARIES ${dependencies_LIBRARIES})
|
||||
set (CMAKE_REQUIRED_LIBRARIES ${ncursesw_LIBRARIES})
|
||||
CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
|
||||
|
||||
# Project source files
|
||||
|
@ -50,7 +55,7 @@ set (project_sources ${PROJECT_NAME}.c)
|
|||
set (project_headers ${PROJECT_BINARY_DIR}/config.h)
|
||||
|
||||
# Project libraries
|
||||
set (project_libraries ${dependencies_LIBRARIES} termo-static)
|
||||
set (project_libraries ${ncursesw_LIBRARIES} ${LIBEV_LIBRARIES} termo-static)
|
||||
|
||||
# Generate a configuration file
|
||||
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
||||
|
|
2
README
2
README
|
@ -5,7 +5,7 @@ autistdraw
|
|||
|
||||
Building and Running
|
||||
--------------------
|
||||
Build dependencies: CMake, pkg-config, ncursesw, libuv>=0.11.x, termo (included)
|
||||
Build dependencies: CMake, pkg-config, ncursesw, libev, termo (included)
|
||||
|
||||
$ git clone https://github.com/pjanouch/autistdraw.git
|
||||
$ git submodule init
|
||||
|
|
454
autistdraw.c
454
autistdraw.c
|
@ -19,15 +19,37 @@
|
|||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <uv.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#ifndef NI_MAXHOST
|
||||
#define NI_MAXHOST 1025
|
||||
#endif // ! NI_MAXHOST
|
||||
|
||||
#ifndef NI_MAXSERV
|
||||
#define NI_MAXSERV 32
|
||||
#endif // ! NI_MAXSERV
|
||||
|
||||
#include <termios.h>
|
||||
#ifndef TIOCGWINSZ
|
||||
#include <sys/ioctl.h>
|
||||
#endif // ! TIOCGWINSZ
|
||||
|
||||
#include <ev.h>
|
||||
#include <curses.h>
|
||||
#include "termo.h"
|
||||
|
||||
|
@ -58,20 +80,15 @@ enum network_mode
|
|||
NETWORK_MODE_CLIENT ///< We're a client
|
||||
};
|
||||
|
||||
typedef struct write_req write_req_t;
|
||||
struct write_req
|
||||
{
|
||||
uv_write_t req; ///< libuv write request
|
||||
uv_buf_t buf; ///< The data to be written
|
||||
};
|
||||
|
||||
typedef struct client client_t;
|
||||
struct client
|
||||
{
|
||||
LIST_HEADER (client_t)
|
||||
|
||||
uv_tcp_t handle; ///< TCP connection handle
|
||||
int fd; ///< Client connection
|
||||
ev_io watcher; ///< Client connection watcher
|
||||
struct msg_reader msg_reader; ///< Client message reader
|
||||
write_queue_t write_queue; ///< Write queue
|
||||
};
|
||||
|
||||
#define BITMAP_PIXEL(app, x, y) (app)->bitmap[(y) * (app)->bitmap_w + (x)]
|
||||
|
@ -81,20 +98,21 @@ struct app_context
|
|||
{
|
||||
termo_t *tk; ///< Termo instance
|
||||
|
||||
uv_tty_t tty; ///< TTY
|
||||
uv_poll_t tty_watcher; ///< TTY input watcher
|
||||
uv_timer_t tty_timer; ///< TTY timeout timer
|
||||
uv_signal_t winch_watcher; ///< SIGWINCH watcher
|
||||
ev_io tty_watcher; ///< TTY input watcher
|
||||
ev_timer tty_timer; ///< TTY timeout timer
|
||||
ev_signal winch_watcher; ///< SIGWINCH watcher
|
||||
|
||||
network_mode_t mode; ///< Networking mode
|
||||
char read_buf[8192]; ///< Global read buffer for libuv
|
||||
|
||||
// Client:
|
||||
uv_tcp_t server_fd; ///< Connection to the server
|
||||
int server_fd; ///< Server connection
|
||||
ev_io server_watcher; ///< Server connection watcher
|
||||
struct msg_reader msg_reader; ///< Server message reader
|
||||
write_queue_t write_queue; ///< Server write queue
|
||||
|
||||
// Server:
|
||||
uv_tcp_t listen_fd; ///< Listening FD
|
||||
int listen_fd; ///< Listening FD
|
||||
ev_io listen_watcher; ///< Listening FD watcher
|
||||
client_t *clients; ///< Client connections
|
||||
|
||||
chtype palette[2 * 9]; ///< Attribute palette
|
||||
|
@ -128,6 +146,7 @@ app_init (app_context_t *self)
|
|||
{
|
||||
memset (self, 0, sizeof *self);
|
||||
msg_reader_init (&self->msg_reader);
|
||||
write_queue_init (&self->write_queue);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -141,66 +160,88 @@ app_free (app_context_t *self)
|
|||
|
||||
free (self->bitmap);
|
||||
msg_reader_free (&self->msg_reader);
|
||||
write_queue_free (&self->write_queue);
|
||||
}
|
||||
|
||||
// --- Server-client messaging -------------------------------------------------
|
||||
|
||||
static bool
|
||||
read_loop (EV_P_ ev_io *watcher,
|
||||
bool (*cb) (EV_P_ ev_io *, const void *, ssize_t))
|
||||
{
|
||||
char buf[8192];
|
||||
while (true)
|
||||
{
|
||||
ssize_t n_read = recv (watcher->fd, buf, sizeof buf, 0);
|
||||
if (n_read < 0)
|
||||
{
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
}
|
||||
if (n_read <= 0 || !cb (EV_A_ watcher, buf, n_read))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
flush_queue (write_queue_t *queue, ev_io *watcher)
|
||||
{
|
||||
struct iovec vec[queue->len], *vec_iter = vec;
|
||||
for (write_req_t *iter = queue->head; iter; iter = iter->next)
|
||||
*vec_iter++ = iter->data;
|
||||
|
||||
int new_events = EV_READ;
|
||||
ssize_t written;
|
||||
again:
|
||||
written = writev (watcher->fd, vec, N_ELEMENTS (vec));
|
||||
if (written < 0)
|
||||
{
|
||||
if (errno == EAGAIN)
|
||||
goto skip;
|
||||
if (errno == EINTR)
|
||||
goto again;
|
||||
return false;
|
||||
}
|
||||
|
||||
write_queue_processed (queue, written);
|
||||
|
||||
skip:
|
||||
if (!write_queue_is_empty (queue))
|
||||
new_events |= EV_WRITE;
|
||||
|
||||
ev_io_stop (EV_DEFAULT_ watcher);
|
||||
ev_io_set (watcher, watcher->fd, new_events);
|
||||
ev_io_start (EV_DEFAULT_ watcher);
|
||||
return true;
|
||||
}
|
||||
|
||||
static write_req_t *
|
||||
flush_writer (struct msg_writer *writer)
|
||||
{
|
||||
size_t len;
|
||||
void *data = msg_writer_flush (writer, &len);
|
||||
write_req_t *req = xcalloc (1, sizeof *req);
|
||||
req->buf = uv_buf_init (data, len);
|
||||
req->data.iov_base = msg_writer_flush (writer, &req->data.iov_len);
|
||||
return req;
|
||||
}
|
||||
|
||||
static void
|
||||
on_data_written_to_client (uv_write_t *req, int status)
|
||||
{
|
||||
write_req_t *wr = (write_req_t *) req;
|
||||
app_context_t *app = req->handle->loop->data;
|
||||
client_t *client = req->data;
|
||||
|
||||
if (status)
|
||||
// Write failed
|
||||
remove_client (app, client);
|
||||
|
||||
free (wr->buf.base);
|
||||
free (wr);
|
||||
}
|
||||
|
||||
static void
|
||||
flush_writer_to_client (struct msg_writer *writer, client_t *client)
|
||||
{
|
||||
write_req_t *wr = flush_writer (writer);
|
||||
wr->req.data = client;
|
||||
(void) uv_write (&wr->req, (uv_stream_t *) &client->handle,
|
||||
&wr->buf, 1, on_data_written_to_client);
|
||||
// XXX: should we put the request on a list so that we can get rid of it?
|
||||
}
|
||||
|
||||
static void
|
||||
on_data_written_to_server (uv_write_t *req, int status)
|
||||
{
|
||||
write_req_t *wr = (write_req_t *) req;
|
||||
app_context_t *app = req->handle->loop->data;
|
||||
|
||||
if (status)
|
||||
// Write failed
|
||||
on_server_disconnected (app);
|
||||
|
||||
free (wr->buf.base);
|
||||
free (wr);
|
||||
write_queue_add (&client->write_queue, flush_writer (writer));
|
||||
ev_io_stop (EV_DEFAULT_ &client->watcher);
|
||||
ev_io_set (&client->watcher, client->fd, EV_READ | EV_WRITE);
|
||||
ev_io_start (EV_DEFAULT_ &client->watcher);
|
||||
}
|
||||
|
||||
static void
|
||||
flush_writer_to_server (struct msg_writer *writer, app_context_t *app)
|
||||
{
|
||||
write_req_t *wr = flush_writer (writer);
|
||||
(void) uv_write (&wr->req, (uv_stream_t *) &app->server_fd,
|
||||
&wr->buf, 1, on_data_written_to_server);
|
||||
// XXX: should we put the request on a list so that we can get rid of it?
|
||||
write_queue_add (&app->write_queue, flush_writer (writer));
|
||||
ev_io_stop (EV_DEFAULT_ &app->server_watcher);
|
||||
ev_io_set (&app->server_watcher, app->server_fd, EV_READ | EV_WRITE);
|
||||
ev_io_start (EV_DEFAULT_ &app->server_watcher);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -873,26 +914,27 @@ on_key (app_context_t *app, termo_key_t *key)
|
|||
}
|
||||
|
||||
static void
|
||||
on_winch (uv_signal_t *handle, int signum)
|
||||
on_winch (EV_P_ ev_signal *handle, int revents)
|
||||
{
|
||||
app_context_t *app = handle->loop->data;
|
||||
(void) signum;
|
||||
app_context_t *app = ev_userdata (loop);
|
||||
(void) handle;
|
||||
(void) revents;
|
||||
|
||||
#ifdef HAVE_RESIZETERM
|
||||
int w, h;
|
||||
if (!uv_tty_get_winsize (&app->tty, &w, &h))
|
||||
#if defined (HAVE_RESIZETERM) && defined (TIOCGWINSZ)
|
||||
struct winsize size;
|
||||
if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
|
||||
{
|
||||
char *row = getenv ("LINES");
|
||||
char *col = getenv ("COLUMNS");
|
||||
unsigned long tmp;
|
||||
resizeterm (
|
||||
(row && xstrtoul (&tmp, row, 10)) ? (int) tmp : h,
|
||||
(col && xstrtoul (&tmp, col, 10)) ? (int) tmp : w);
|
||||
(row && xstrtoul (&tmp, row, 10)) ? (int) tmp : size.ws_row,
|
||||
(col && xstrtoul (&tmp, col, 10)) ? (int) tmp : size.ws_col);
|
||||
}
|
||||
#else // ! HAVE_RESIZETERM
|
||||
#else // ! HAVE_RESIZE_TERM || ! TIOCGWINSZ
|
||||
endwin ();
|
||||
refresh ();
|
||||
#endif // ! HAVE_RESIZETERM
|
||||
#endif // ! HAVE_RESIZE_TERM || ! TIOCGWINSZ
|
||||
|
||||
update_canvas_for_screen (app);
|
||||
redraw (app);
|
||||
|
@ -900,60 +942,51 @@ on_winch (uv_signal_t *handle, int signum)
|
|||
}
|
||||
|
||||
static void
|
||||
on_key_timer (uv_timer_t *handle)
|
||||
on_key_timer (EV_P_ ev_timer *handle, int revents)
|
||||
{
|
||||
app_context_t *app = handle->loop->data;
|
||||
app_context_t *app = ev_userdata (loop);
|
||||
(void) handle;
|
||||
(void) revents;
|
||||
|
||||
termo_key_t key;
|
||||
if (termo_getkey_force (app->tk, &key) == TERMO_RES_KEY)
|
||||
if (!on_key (app, &key))
|
||||
uv_stop (handle->loop);
|
||||
ev_break (EV_A_ EVBREAK_ONE);
|
||||
}
|
||||
|
||||
static void
|
||||
on_tty_readable (uv_poll_t *handle, int status, int events)
|
||||
on_tty_readable (EV_P_ ev_io *handle, int revents)
|
||||
{
|
||||
// Ignoring and hoping for the best
|
||||
(void) status;
|
||||
(void) events;
|
||||
(void) handle;
|
||||
(void) revents;
|
||||
|
||||
app_context_t *app = handle->loop->data;
|
||||
app_context_t *app = ev_userdata (loop);
|
||||
|
||||
uv_timer_stop (&app->tty_timer);
|
||||
ev_timer_stop (EV_A_ &app->tty_timer);
|
||||
termo_advisereadable (app->tk);
|
||||
|
||||
termo_key_t key;
|
||||
termo_result_t ret;
|
||||
while ((ret = termo_getkey (app->tk, &key)) == TERMO_RES_KEY)
|
||||
if (!on_key (app, &key))
|
||||
uv_stop (handle->loop);
|
||||
ev_break (EV_A_ EVBREAK_ONE);
|
||||
|
||||
if (ret == TERMO_RES_AGAIN)
|
||||
uv_timer_start (&app->tty_timer,
|
||||
on_key_timer, termo_get_waittime (app->tk), 0);
|
||||
}
|
||||
|
||||
static void
|
||||
app_uv_allocator (uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
|
||||
{
|
||||
// Let's just use a single "global" buffer
|
||||
(void) suggested_size;
|
||||
|
||||
app_context_t *app = handle->loop->data;
|
||||
buf->base = app->read_buf;
|
||||
buf->len = sizeof app->read_buf;
|
||||
ev_timer_start (EV_A_ &app->tty_timer);
|
||||
}
|
||||
|
||||
// --- Client-specific stuff ---------------------------------------------------
|
||||
|
||||
typedef void (*server_handler_fn) (app_context_t *, struct msg_unpacker *);
|
||||
typedef bool (*server_handler_fn) (app_context_t *, struct msg_unpacker *);
|
||||
|
||||
static void
|
||||
on_server_disconnected (app_context_t *app)
|
||||
{
|
||||
// TODO: cancel any write requests?
|
||||
// XXX: should we unref it?
|
||||
uv_close ((uv_handle_t *) &app->server_fd, NULL);
|
||||
xclose (app->server_fd);
|
||||
ev_io_stop (EV_DEFAULT_ &app->server_watcher);
|
||||
|
||||
display ("Disconnected!");
|
||||
beep (); // Beep beep! Made a boo-boo.
|
||||
|
@ -963,21 +996,20 @@ on_server_disconnected (app_context_t *app)
|
|||
app->mode = NETWORK_MODE_STANDALONE;
|
||||
}
|
||||
|
||||
static void
|
||||
static bool
|
||||
on_server_hello (app_context_t *app, struct msg_unpacker *unpacker)
|
||||
{
|
||||
(void) app;
|
||||
|
||||
uint8_t version;
|
||||
if (!msg_unpacker_u8 (unpacker, &version))
|
||||
return;
|
||||
|
||||
return false; // Not enough data
|
||||
if (version != PROTOCOL_VERSION)
|
||||
// XXX: possibly incompatible version, disconnect?
|
||||
return;
|
||||
return false; // Incompatible version
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
static bool
|
||||
on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker)
|
||||
{
|
||||
int32_t x, y;
|
||||
|
@ -986,11 +1018,11 @@ on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker)
|
|||
|| !msg_unpacker_i32 (unpacker, &y)
|
||||
|| !msg_unpacker_u64 (unpacker, &w)
|
||||
|| !msg_unpacker_u64 (unpacker, &h))
|
||||
return;
|
||||
return false; // Not enough data
|
||||
|
||||
size_t size = w * h;
|
||||
if ((h && w > SIZE_MAX / h) || w > SIZE_MAX || h > SIZE_MAX)
|
||||
return;
|
||||
return false; // The server is flooding us
|
||||
|
||||
uint8_t *bitmap = xcalloc (size, sizeof *app->bitmap);
|
||||
|
||||
|
@ -1015,9 +1047,10 @@ on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker)
|
|||
app->bitmap_h = h;
|
||||
|
||||
redraw_canvas (app);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
static bool
|
||||
on_server_put_point (app_context_t *app, struct msg_unpacker *unpacker)
|
||||
{
|
||||
int32_t x, y;
|
||||
|
@ -1026,22 +1059,19 @@ on_server_put_point (app_context_t *app, struct msg_unpacker *unpacker)
|
|||
if (!msg_unpacker_i32 (unpacker, &x)
|
||||
|| !msg_unpacker_i32 (unpacker, &y)
|
||||
|| !msg_unpacker_u8 (unpacker, &color))
|
||||
return;
|
||||
return false; // Not enough data
|
||||
|
||||
draw_point (app, x, y, color);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
|
||||
static bool
|
||||
on_server_data (EV_P_ ev_io *watcher, const void *buf, ssize_t n_read)
|
||||
{
|
||||
app_context_t *app = stream->loop->data;
|
||||
if (nread == UV_EOF || nread < 0)
|
||||
{
|
||||
on_server_disconnected (app);
|
||||
return;
|
||||
}
|
||||
app_context_t *app = ev_userdata (loop);
|
||||
(void) watcher;
|
||||
|
||||
msg_reader_feed (&app->msg_reader, buf->base, nread);
|
||||
msg_reader_feed (&app->msg_reader, buf, n_read);
|
||||
|
||||
static const server_handler_fn handlers[MESSAGE_COUNT] =
|
||||
{
|
||||
|
@ -1060,20 +1090,34 @@ on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
|
|||
uint8_t type;
|
||||
if (!msg_unpacker_u8 (&unpacker, &type)
|
||||
|| type >= MESSAGE_COUNT)
|
||||
// XXX: unknown message, disconnect?
|
||||
continue;
|
||||
return false; // Unknown message
|
||||
|
||||
server_handler_fn handler = handlers[type];
|
||||
if (!handler)
|
||||
// XXX: unknown message, disconnect?
|
||||
continue;
|
||||
|
||||
handler (app, &unpacker);
|
||||
|
||||
return false; // Unknown message
|
||||
if (!handler (app, &unpacker))
|
||||
return false; // Invalid message
|
||||
if (msg_unpacker_get_available (&unpacker) > 0)
|
||||
// XXX: overlong message, disconnect?
|
||||
continue;
|
||||
return false; // Overlong message
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
on_server_ready (EV_P_ ev_io *watcher, int revents)
|
||||
{
|
||||
app_context_t *app = ev_userdata (loop);
|
||||
|
||||
if (revents & EV_READ)
|
||||
if (!read_loop (EV_A_ watcher, on_server_data))
|
||||
goto error;
|
||||
if (revents & EV_WRITE)
|
||||
if (!flush_queue (&app->write_queue, watcher))
|
||||
goto error;
|
||||
return;
|
||||
|
||||
error:
|
||||
on_server_disconnected (app);
|
||||
}
|
||||
|
||||
// --- Server-specific stuff ---------------------------------------------------
|
||||
|
@ -1084,11 +1128,13 @@ typedef bool (*client_handler_fn)
|
|||
static void
|
||||
remove_client (app_context_t *app, client_t *client)
|
||||
{
|
||||
// TODO: stop any watchers?
|
||||
// TODO: cancel any write requests?
|
||||
// XXX: should we unref it?
|
||||
uv_close ((uv_handle_t *) &client->handle, NULL);
|
||||
xclose (client->fd);
|
||||
LIST_UNLINK (app->clients, client);
|
||||
msg_reader_free (&client->msg_reader);
|
||||
write_queue_free (&client->write_queue);
|
||||
free (client);
|
||||
}
|
||||
|
||||
|
@ -1137,15 +1183,13 @@ on_client_put_point (app_context_t *app, client_t *client,
|
|||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
on_client_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
|
||||
static bool
|
||||
on_client_data (EV_P_ ev_io *watcher, const void *buf, ssize_t n_read)
|
||||
{
|
||||
app_context_t *app = stream->loop->data;
|
||||
client_t *client = stream->data;
|
||||
if (nread == UV_EOF || nread < 0)
|
||||
goto disconnect; // Connection closed or error
|
||||
app_context_t *app = ev_userdata (loop);
|
||||
client_t *client = watcher->data;
|
||||
|
||||
msg_reader_feed (&client->msg_reader, buf->base, nread);
|
||||
msg_reader_feed (&client->msg_reader, buf, n_read);
|
||||
|
||||
static const client_handler_fn handlers[MESSAGE_COUNT] =
|
||||
{
|
||||
|
@ -1163,51 +1207,74 @@ on_client_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
|
|||
|
||||
uint8_t type;
|
||||
if (!msg_unpacker_u8 (&unpacker, &type))
|
||||
goto disconnect; // Invalid message
|
||||
return false; // Invalid message
|
||||
if (type >= MESSAGE_COUNT)
|
||||
goto disconnect; // Unknown message
|
||||
return false; // Unknown message
|
||||
|
||||
client_handler_fn handler = handlers[type];
|
||||
if (!handler)
|
||||
goto disconnect; // Unknown message
|
||||
return false; // Unknown message
|
||||
if (!handler (app, client, &unpacker))
|
||||
goto disconnect; // Invalid message
|
||||
return false; // Invalid message
|
||||
if (msg_unpacker_get_available (&unpacker) > 0)
|
||||
goto disconnect; // Overlong message data
|
||||
return false; // Overlong message data
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
on_client_ready (EV_P_ ev_io *watcher, int revents)
|
||||
{
|
||||
app_context_t *app = ev_userdata (loop);
|
||||
client_t *client = watcher->data;
|
||||
|
||||
if (revents & EV_READ)
|
||||
if (!read_loop (EV_A_ watcher, on_client_data))
|
||||
goto error;
|
||||
if (revents & EV_WRITE)
|
||||
if (!flush_queue (&client->write_queue, watcher))
|
||||
goto error;
|
||||
return;
|
||||
|
||||
disconnect:
|
||||
error:
|
||||
remove_client (app, client);
|
||||
}
|
||||
|
||||
static void
|
||||
on_new_client (uv_stream_t *server, int status)
|
||||
on_new_client (EV_P_ ev_io *watcher, int revents)
|
||||
{
|
||||
app_context_t *app = server->loop->data;
|
||||
if (status)
|
||||
return;
|
||||
app_context_t *app = ev_userdata (loop);
|
||||
(void) revents;
|
||||
|
||||
int err;
|
||||
client_t *client = xcalloc (1, sizeof *client);
|
||||
if ((err = uv_tcp_init (server->loop, &client->handle)))
|
||||
goto free_client;
|
||||
if ((err = uv_accept (server, (uv_stream_t *) &client->handle))
|
||||
|| (err = uv_read_start ((uv_stream_t *) &client->handle,
|
||||
app_uv_allocator, on_client_data)))
|
||||
// XXX: do we need to un-accept?
|
||||
goto free_handle;
|
||||
while (true)
|
||||
{
|
||||
int sock_fd = accept (watcher->fd, NULL, NULL);
|
||||
if (sock_fd == -1)
|
||||
{
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
if (errno == EINTR
|
||||
|| errno == ECONNABORTED)
|
||||
continue;
|
||||
|
||||
client->handle.data = client;
|
||||
msg_reader_init (&client->msg_reader);
|
||||
LIST_PREPEND (app->clients, client);
|
||||
return;
|
||||
// Stop accepting connections to prevent busy looping
|
||||
// TODO: indicate the error to the user
|
||||
ev_io_stop (EV_A_ watcher);
|
||||
break;
|
||||
}
|
||||
|
||||
free_handle:
|
||||
uv_close ((uv_handle_t *) &client->handle, NULL);
|
||||
// XXX: should we unref it?
|
||||
free_client:
|
||||
free (client);
|
||||
client_t *client = xcalloc (1, sizeof *client);
|
||||
client->fd = sock_fd;
|
||||
msg_reader_init (&client->msg_reader);
|
||||
write_queue_init (&client->write_queue);
|
||||
|
||||
set_blocking (sock_fd, false);
|
||||
ev_io_init (&client->watcher, on_client_ready, sock_fd, EV_READ);
|
||||
client->watcher.data = client;
|
||||
ev_io_start (EV_A_ &client->watcher);
|
||||
|
||||
LIST_PREPEND (app->clients, client);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Program startup ---------------------------------------------------------
|
||||
|
@ -1388,17 +1455,13 @@ initialize_client (app_context_t *app, struct addrinfo *address)
|
|||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int yes = 1;
|
||||
(void) setsockopt (sock_fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof yes);
|
||||
|
||||
set_blocking (sock_fd, false);
|
||||
if ((err = uv_tcp_init (uv_default_loop (), &app->server_fd))
|
||||
|| (err = uv_tcp_open (&app->server_fd, sock_fd))
|
||||
|| (err = uv_tcp_keepalive (&app->server_fd, true, 30))
|
||||
|| (err = uv_read_start ((uv_stream_t *) &app->server_fd,
|
||||
app_uv_allocator, on_server_data)))
|
||||
{
|
||||
fprintf (stderr, "%s: %s: %s\n",
|
||||
"error", "initialization failed", uv_strerror (err));
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
app->server_fd = sock_fd;
|
||||
ev_io_init (&app->server_watcher, on_server_ready, sock_fd, EV_READ);
|
||||
ev_io_start (EV_DEFAULT_ &app->server_watcher);
|
||||
|
||||
send_hello_request (app);
|
||||
send_get_bitmap_request (app);
|
||||
|
@ -1409,15 +1472,29 @@ initialize_server (app_context_t *app, struct addrinfo *address)
|
|||
{
|
||||
app->mode = NETWORK_MODE_SERVER;
|
||||
|
||||
int err;
|
||||
if ((err = uv_tcp_init (uv_default_loop (), &app->listen_fd))
|
||||
|| (err = uv_tcp_bind (&app->listen_fd, address->ai_addr, 0))
|
||||
|| (err = uv_listen ((uv_stream_t *) &app->listen_fd, 10, on_new_client)))
|
||||
{
|
||||
fprintf (stderr, "%s: %s: %s\n",
|
||||
"error", "initialization failed", uv_strerror (err));
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
int sock_fd = socket (address->ai_family,
|
||||
address->ai_socktype, address->ai_protocol);
|
||||
if (sock_fd == -1)
|
||||
goto fail;
|
||||
|
||||
if (bind (sock_fd, address->ai_addr, address->ai_addrlen)
|
||||
|| listen (sock_fd, 10))
|
||||
goto fail;
|
||||
|
||||
int yes = 1;
|
||||
(void) setsockopt (sock_fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof yes);
|
||||
(void) setsockopt (sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
|
||||
|
||||
set_blocking (sock_fd, false);
|
||||
app->listen_fd = sock_fd;
|
||||
ev_io_init (&app->listen_watcher, on_new_client, sock_fd, EV_READ);
|
||||
ev_io_start (EV_DEFAULT_ &app->listen_watcher);
|
||||
return;
|
||||
|
||||
fail:
|
||||
fprintf (stderr, "%s: %s: %s\n",
|
||||
"error", "initialization failed", strerror (errno));
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -1429,6 +1506,13 @@ main (int argc, char *argv[])
|
|||
app_context_t app;
|
||||
app_init (&app);
|
||||
|
||||
struct ev_loop *loop = EV_DEFAULT;
|
||||
if (!loop)
|
||||
{
|
||||
fprintf (stderr, "%s: %s\n", "error", "cannot initialize libev");
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
app_options_t options;
|
||||
app_options_init (&options);
|
||||
parse_program_arguments (&options, argc, argv);
|
||||
|
@ -1461,29 +1545,27 @@ main (int argc, char *argv[])
|
|||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
uv_loop_t *loop = uv_default_loop ();
|
||||
loop->data = &app;
|
||||
ev_set_userdata (loop, &app);
|
||||
|
||||
uv_signal_init (loop, &app.winch_watcher);
|
||||
uv_signal_start (&app.winch_watcher, on_winch, SIGWINCH);
|
||||
ev_signal_init (&app.winch_watcher, on_winch, SIGWINCH);
|
||||
ev_signal_start (EV_DEFAULT_ &app.winch_watcher);
|
||||
|
||||
uv_tty_init (loop, &app.tty, STDOUT_FILENO, false);
|
||||
ev_io_init (&app.tty_watcher, on_tty_readable, STDIN_FILENO, EV_READ);
|
||||
ev_io_start (EV_DEFAULT_ &app.tty_watcher);
|
||||
|
||||
uv_poll_init (loop, &app.tty_watcher, STDIN_FILENO);
|
||||
uv_poll_start (&app.tty_watcher, UV_READABLE, on_tty_readable);
|
||||
|
||||
uv_timer_init (loop, &app.tty_timer);
|
||||
ev_timer_init (&app.tty_timer, on_key_timer,
|
||||
termo_get_waittime (app.tk) / 1000., 0);
|
||||
|
||||
init_palette (&app);
|
||||
update_canvas_for_screen (&app);
|
||||
redraw (&app);
|
||||
redraw_canvas (&app);
|
||||
|
||||
uv_run (loop, UV_RUN_DEFAULT);
|
||||
ev_run (loop, 0);
|
||||
endwin ();
|
||||
|
||||
app_free (&app);
|
||||
uv_loop_close (loop);
|
||||
ev_loop_destroy (loop);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Public Domain
|
||||
|
||||
# The author of libev is a dick and doesn't want to add support for pkg-config,
|
||||
# forcing us to include this pointless file in the distribution.
|
||||
|
||||
# Some distributions do add it, though
|
||||
find_package (PkgConfig REQUIRED)
|
||||
pkg_check_modules (LIBEV libev)
|
||||
|
||||
if (NOT LIBEV_FOUND)
|
||||
find_path (LIBEV_INCLUDE_DIRS ev.h)
|
||||
find_library (LIBEV_LIBRARIES NAMES ev)
|
||||
|
||||
if (LIBEV_INCLUDE_DIRS AND LIBEV_LIBRARIES)
|
||||
set (LIBEV_FOUND TRUE)
|
||||
endif (LIBEV_INCLUDE_DIRS AND LIBEV_LIBRARIES)
|
||||
endif (NOT LIBEV_FOUND)
|
||||
|
91
utils.c
91
utils.c
|
@ -104,6 +104,24 @@ xrealloc (void *o, size_t n)
|
|||
(link)->next->prev = (link)->prev; \
|
||||
BLOCK_END
|
||||
|
||||
#define LIST_APPEND_WITH_TAIL(head, tail, link) \
|
||||
BLOCK_START \
|
||||
(link)->prev = (tail); \
|
||||
(link)->next = NULL; \
|
||||
if ((link)->prev) \
|
||||
(link)->prev->next = (link); \
|
||||
else \
|
||||
(head) = (link); \
|
||||
(tail) = (link); \
|
||||
BLOCK_END
|
||||
|
||||
#define LIST_UNLINK_WITH_TAIL(head, tail, link) \
|
||||
BLOCK_START \
|
||||
if ((tail) == (link)) \
|
||||
(tail) = (link)->prev; \
|
||||
LIST_UNLINK ((head), (link)); \
|
||||
BLOCK_END
|
||||
|
||||
// --- Dynamically allocated strings -------------------------------------------
|
||||
|
||||
// Basically a string builder to abstract away manual memory management.
|
||||
|
@ -236,11 +254,14 @@ static bool
|
|||
set_blocking (int fd, bool blocking)
|
||||
{
|
||||
int flags = fcntl (fd, F_GETFL);
|
||||
|
||||
bool prev = !(flags & O_NONBLOCK);
|
||||
if (blocking)
|
||||
flags &= ~O_NONBLOCK;
|
||||
else
|
||||
flags |= O_NONBLOCK;
|
||||
|
||||
(void) fcntl (fd, F_SETFL, flags);
|
||||
return prev;
|
||||
}
|
||||
|
||||
|
@ -284,6 +305,76 @@ xstrtoul (unsigned long *out, const char *s, int base)
|
|||
return errno == 0 && !*end && end != s;
|
||||
}
|
||||
|
||||
// --- libuv-style write adaptor -----------------------------------------------
|
||||
|
||||
// Makes it possible to use iovec to write multiple data chunks at once.
|
||||
|
||||
typedef struct write_req write_req_t;
|
||||
struct write_req
|
||||
{
|
||||
LIST_HEADER (write_req_t)
|
||||
struct iovec data; ///< Data to be written
|
||||
};
|
||||
|
||||
typedef struct write_queue write_queue_t;
|
||||
struct write_queue
|
||||
{
|
||||
write_req_t *head; ///< The head of the queue
|
||||
write_req_t *tail; ///< The tail of the queue
|
||||
size_t head_offset; ///< Offset into the head
|
||||
size_t len;
|
||||
};
|
||||
|
||||
static void
|
||||
write_queue_init (struct write_queue *self)
|
||||
{
|
||||
self->head = self->tail = NULL;
|
||||
self->head_offset = 0;
|
||||
self->len = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
write_queue_free (struct write_queue *self)
|
||||
{
|
||||
for (write_req_t *iter = self->head, *next; iter; iter = next)
|
||||
{
|
||||
next = iter->next;
|
||||
free (iter->data.iov_base);
|
||||
free (iter);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
write_queue_add (struct write_queue *self, write_req_t *req)
|
||||
{
|
||||
LIST_APPEND_WITH_TAIL (self->head, self->tail, req);
|
||||
self->len++;
|
||||
}
|
||||
|
||||
static void
|
||||
write_queue_processed (struct write_queue *self, size_t len)
|
||||
{
|
||||
while (self->head
|
||||
&& self->head_offset + len >= self->head->data.iov_len)
|
||||
{
|
||||
write_req_t *head = self->head;
|
||||
len -= (head->data.iov_len - self->head_offset);
|
||||
self->head_offset = 0;
|
||||
|
||||
LIST_UNLINK_WITH_TAIL (self->head, self->tail, head);
|
||||
self->len--;
|
||||
free (head->data.iov_base);
|
||||
free (head);
|
||||
}
|
||||
self->head_offset += len;
|
||||
}
|
||||
|
||||
static bool
|
||||
write_queue_is_empty (struct write_queue *self)
|
||||
{
|
||||
return self->head == NULL;
|
||||
}
|
||||
|
||||
// --- Message reader ----------------------------------------------------------
|
||||
|
||||
struct msg_reader
|
||||
|
|
Loading…
Reference in New Issue