libuv, NodeJS and everything in between
-
Upload
saul-ibarra-corretge -
Category
Technology
-
view
734 -
download
1
Transcript of libuv, NodeJS and everything in between
LIBUV, NODEJS AND EVERYTHING IN BETWEENSaúl Ibarra Corretgé @saghul
LIBUV, NODE AND EVERYTHING IN BETWEEN
HIYA!
▸ @saghul
▸ Bilbao — Amsterdam — Liverpool
▸ libuv core janitor
▸ Nodejs collaborator
▸ Recently obsessed with 3D printing
▸ AMA!
ASYNC I/O 101
LIBUV, NODE AND EVERYTHING IN BETWEEN
ASYNC I/O 101
from __future__ import print_function
import socket
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) server.bind(('127.0.0.1', 1234)) server.listen(10) print("Server listening on: {}".format(server.getsockname()))
client, addr = server.accept() print("Client connected: {}".format(addr))
while True: data = client.recv(4096) if not data: print("Client has disconnected") break client.send(data.upper())
server.close()
LIBUV, NODE AND EVERYTHING IN BETWEEN
CRAP, IT BLOCKS!
▸ Only one client at a time!
▸ Scaling up
▸ Threads
▸ I/O multiplexing
▸ http://www.kegel.com/c10k.html
▸ http://www.slideshare.net/saghul/how-do-eventloops-work-in-python
LIBUV, NODE AND EVERYTHING IN BETWEEN
THREADS?
▸ Overhead: stack size, scheduling
▸ Synchronisation
▸ It’s still a good solution in some cases
LIBUV, NODE AND EVERYTHING IN BETWEEN
I/O MULTIPLEXING TO THE RESCUE
▸ Single thread
▸ Examine and wait for i/o in multiple sockets at once
▸ When a socket is ready the operation won’t block
▸ Good for i/o bound tasks, bad for CPU intensive tasks
LIBUV, NODE AND EVERYTHING IN BETWEEN
I/O MULTIPLEXING 101
1. Put the fd in non-blocking mode
2. Add the fd to the i/o multiplexor
3. Wait for some time
4. Perform read / writes on the fd, it won’t block!
5. Profit?
LIBUV, NODE AND EVERYTHING IN BETWEEN
I/O MULTIPLEXING APIS
▸ Different APIs: select, poll, epoll, kqueue, …
▸ Unix shares a common model: readiness
▸ Windows uses a completion model
▸ IOCP is The Good Stuff
LIBUV
LIBUV, NODE AND EVERYTHING IN BETWEEN
LIBUV
▸ Small (relatively) C library: ~30K LOC (without tests)
▸ Extensive test suite
▸ Designed for C programs that miss the joy of JavaScript callback hell
▸ Used by Node and many other projects:https://github.com/libuv/libuv/wiki/Projects-that-use-libuv
LIBUV, NODE AND EVERYTHING IN BETWEEN
LIBUV: FEATURES
▸ Event loop backed by epoll, kqueue, IOCP, event ports, etc.
▸ Timers
▸ Async TCP / UDP sockets
▸ Async named pipes
▸ Async filesystem operations
▸ Async signal handling
▸ Child processes
▸ ANSI escaped TTY
▸ Threading utilities
▸ Coolest logo ever
LIBUV, NODE AND EVERYTHING IN BETWEEN
LIBUV: ARCHITECTURE
LIBUV, NODE AND EVERYTHING IN BETWEEN
LIBUV: THE EVENT LOOP
LIBUV, NODE AND EVERYTHING IN BETWEEN
LIBUV: FILESYSTEM APIS
▸ Follow the Unix style
▸ Executed in a thread pool
▸ http://blog.libtorrent.org/2012/10/asynchronous-disk-io/
LIBUV, NODE AND EVERYTHING IN BETWEEN
LIBUV: A WORD ON THREADS
▸ We only use threads for file i/o and getaddrinfo
▸ Default thread pool size is 4(runtime env var: UV_THREADPOOL_SIZE)
▸ NOT FOR NETWORK I/O
▸ NOT FOR NETWORK I/O
▸ NOT FOR NETWORK I/O
▸ NOT FOR NETWORK I/O
THE LIBUV EVENT LOOP IS SINGLE THREADED. THE THREAD POOL IS ONLY USED FOR FILE I/O.
The libuv police
LIBUV, NODE AND EVERYTHING IN BETWEEN
A CHAT APP, OF COURSE
LIBUV, NODE AND EVERYTHING IN BETWEEN
A LIBUV CHAT APPLICATION
▸ Simple TCP server application
▸ A twist on libuv-chat by Ben Noordhuis in 2011
▸ Multiple users, single “chat room”
▸ Pokemon, because why not?
▸ https://github.com/saghul/libuv-chat
THE MAKEFILE
all: build
deps/libuv: git clone --depth 1 https://github.com/libuv/libuv deps/libuv
build/gyp: git clone --depth 1 https://chromium.googlesource.com/external/gyp build/gyp
out/Makefile: deps/libuv build/gyp build/gyp/gyp -Duv_library=static_library --depth=$(PWD) --generator-output=$(PWD)/out -Goutput_dir=$(PWD)/out -f make build.gyp
build: out/Makefile $(MAKE) -C out
clean: rm -rf out
distclean: rm -rf build deps out
.PHONY: clean distclean
THE GYP BUILD FILE
# Copyright (c) 2016, Saúl Ibarra Corretgé <[email protected]> # Copyright (c) 2012, Ben Noordhuis <[email protected]> # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# gyp -Duv_library=static_library --depth=$PWD --generator-output=$PWD/out -Goutput_dir=$PWD/out -f make build.gyp
{ 'targets': [ { 'target_name': 'chat-server', 'type': 'executable', 'dependencies': ['deps/libuv/uv.gyp:libuv'], 'sources': ['src/main.c', 'src/queue.h', 'src/pokemon_names.h'], } ] }
INITIALISE AND RUN SERVERstruct user { QUEUE queue; uv_tcp_t handle; char id[32]; };
static QUEUE users;
int main(void) { QUEUE_INIT(&users);
srand(1234);
int r;
uv_tcp_t server_handle; uv_tcp_init(uv_default_loop(), &server_handle);
struct sockaddr_in addr; uv_ip4_addr(SERVER_ADDR, SERVER_PORT, &addr);
r = uv_tcp_bind(&server_handle, (const struct sockaddr*) &addr, 0); if (r < 0) fatal("uv_tcp_bind", r);
const int backlog = 128; r = uv_listen((uv_stream_t*) &server_handle, backlog, on_connection); if (r < 0) fatal("uv_listen", r);
printf("Listening at %s:%d\n", SERVER_ADDR, SERVER_PORT); uv_run(uv_default_loop(), UV_RUN_DEFAULT);
return 0; }
HANDLE INCOMING CONNECTIONS
static void on_connection(uv_stream_t* server_handle, int status) { assert(status == 0); int r;
// hurray, a new user! struct user *user = xmalloc(sizeof(*user)); uv_tcp_init(uv_default_loop(), &user->handle);
r = uv_accept(server_handle, (uv_stream_t*) &user->handle); if (r < 0) fatal("uv_accept", r);
// add him to the list of users QUEUE_INSERT_TAIL(&users, &user->queue); make_user_id(user);
// now tell everyone, including yourself (to know your name!) broadcast(NULL, "* A wild %s appeared from %s\n", user->id, addr_and_port(user));
// start accepting messages from the user uv_read_start((uv_stream_t*) &user->handle, on_alloc, on_read); }
READ INCOMING DATA
static void on_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { buf->base = xmalloc(suggested_size); buf->len = suggested_size; }
static void on_read(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) { struct user *user = QUEUE_DATA(handle, struct user, handle);
if (nread == UV_EOF) { // user disconnected QUEUE_REMOVE(&user->queue); uv_close((uv_handle_t*) &user->handle, on_close); broadcast(NULL, "* %s fled!\n", user->id); } else if (nread > 0) { // broadcast message broadcast(user, "%s said: %.*s", user->id, (int) nread, buf->base); } else { fprintf(stderr, "on_read: %s\n", uv_strerror(nread)); }
free(buf->base); }
BROADCAST DATA
static void broadcast(const struct user* sender, const char *fmt, ...) { QUEUE *q; char msg[512]; va_list ap;
va_start(ap, fmt); vsnprintf(msg, sizeof(msg), fmt, ap); va_end(ap);
QUEUE_FOREACH(q, &users) { struct user *user = QUEUE_DATA(q, struct user, queue); if (user != sender) { unicast(user, msg); } } }
static void unicast(struct user *user, const char *msg) { size_t len = strlen(msg); uv_write_t *req = xmalloc(sizeof(*req) + len); void *addr = req + 1; memcpy(addr, msg, len); uv_buf_t buf = uv_buf_init(addr, len); uv_write(req, (uv_stream_t*) &user->handle, &buf, 1, on_write); }
static void on_write(uv_write_t *req, int status) { free(req); }
MISCELLANEOUS FUNCTIONS
static void on_close(uv_handle_t* handle) { struct user *user = QUEUE_DATA(handle, struct user, handle); free(user); }
static void make_user_id(struct user *user) { snprintf(user->id, sizeof(user->id), "%s", pokemon_names[rand() % ARRAY_SIZE(pokemon_names)]); }
NODE
LIBUV, NODE AND EVERYTHING IN BETWEEN
NODEJS
▸ Server side JavaScript
▸ V8 JavaScript engine
▸ Web inspired async model
▸ Auxiliary libraries: libuv, OpenSSL, c-ares, http_parser, …
LIBUV, NODE AND EVERYTHING IN BETWEEN
PLATFORM LAYER REQUIREMENTS
▸ ASYNC ALL THE THINGS
▸ Completion based (callbacks)
▸ Consistent API for asynchronous network i/o
▸ Filesystem operations
▸ Basic name resolution (getaddrinfo / getnameinfo)
LIBUV, NODE AND EVERYTHING IN BETWEEN
ARCHITECTURE IN NODE 0.4
BINDINGS / WRAPS
JS STANDARD LIBRARY
USER APPLICATIONS
V8 OPENSSL LIBEV LIBEIO …
LIBUV, NODE AND EVERYTHING IN BETWEEN
ARCHITECTURE IN NODE 0.6
BINDINGS / WRAPS
JS STANDARD LIBRARY
USER APPLICATIONS
V8 OPENSSL LIBUV …LIBEV LIBEIO
LIBUV, NODE AND EVERYTHING IN BETWEEN
ARCHITECTURE IN NODE 0.10 AND ONWARDS
BINDINGS / WRAPS
JS STANDARD LIBRARY
USER APPLICATIONS
V8 OPENSSL LIBUV …
LIBUV IN NODE
LIBUV, NODE AND EVERYTHING IN BETWEEN
LIBUV: THE EVENT LOOPREMEMBER?
LIBUV, NODE AND EVERYTHING IN BETWEEN
NODE EVENT LOOPCOALESCED, 1 NODE TIMER != 1 LIBUV
TIMER
RUN ON A CHECK + IDLE
HANDLENEXT TICK CALLBACKS RUN FROM NODE::MAKECALLBACK
LIBUV, NODE AND EVERYTHING IN BETWEEN
ONION ARCHITECTURE (TM)net.Socket
TCPWrap
uv_tcp_t
Socket._handle
TCPWrap.handle_
fd / HANDLE
QUESTIONS?
libuv.org docs.libuv.org #libuv on IRC
libuv Google Group #libuv on StackOverflow