real-time python web: gevent and socket.io

Post on 08-May-2015

19.977 Views

Category:

Technology

3 Downloads

Preview:

Click to see full reader

TRANSCRIPT

© 2011Geeknet Inc

Real-Time Web: Gevent and Socket.io

Rick Copeland@rick446

rick@geek.net

- Getting started with Gevent

- AJAX, push, WebSockets, wha?

- ZeroMQ for fun and multiprocessing

- Putting it all together

© 2011Geeknet Inc

A (very) Brief Survey of Python Asynchronous Programming

AsynCore In stdlib, used for stdlib SMTP server Nobody cares about it anymore

Twisted Large community, vast amounts of code Callbacks hurt my brain

Stackless Cool, cooperative multithreading Needs a custom Python

Event-based green threads Like stackless, but in regular Python Know when you yield

© 2011Geeknet Inc

Let’s Go Green: Async that Doesn’t Hurt Your Brain

Greenlets: Cooperative, lightweight threads

Very forgiving – mutexes rarely needed

Use it for IO!

© 2011Geeknet Inc

Gevent: Greenlets

Spawn helpers spawn(my_python_function, *args, **kwargs) Also spawn_later(), spawn_link(), etc.

Greenlet class Like threads but cooperative Useful properties: .get(), .join(), .kill(), .link()

Timeouts Timeout(seconds, exception).start()

Pools: for limiting concurrency, use Pool.spawn

© 2011Geeknet Inc

Gevent: Communication

Event set() clear() wait()

Queue Modeled after Queue.Queue .get() .put() __iter__() PriorityQueue, LifoQueue, JoinableQueue

© 2011Geeknet Inc

Gevent: Networking

“Green” versions of sockets, select(), ssl, and dns Quick and dirty:

import gevent.monkey

gevent.monkey.patch_all()

© 2011Geeknet Inc

Gevent: Servers

Simple callback interface Creates one greenlet per connection (but remember,

that’s OK!)

def handle(socket, address):

print 'new connection!’

server = StreamServer(

('127.0.0.1', 1234), handle)

# creates a new server

server.start()

# start accepting new connections

© 2011Geeknet Inc

Gevent: WSGI

gevent.wsgi Fast (~4k requests/s) No streaming, pipelining, or ssl

gevent.pywsgi: Full featured Slower (“only” 3k requests/s)

from gevent import pywsgi

def hello_world(env, start_response):

start_response('200 OK', [('Content-Type', 'text/html')])

yield '<b>Hello world</b>’

server = pywsgi.WSGIServer(

('0.0.0.0', 8080), hello_world)

server.serve_forever()

© 2011Geeknet Inc

- Getting started with Gevent

- AJAX, push, WebSockets, wha?

- ZeroMQ for fun and multiprocessing

- Putting it all together

© 2011Geeknet Inc

What is the real-time web? No page refreshes

Server push

Examples: chat, realtime analytics, …

Implementation Flash (eww…) Polling (2x eww…) Long polling (wow – that’s clever :-/ ) HTML5 WebSockets (Super-easy! Awesome! Security

vulnerabilities! Immature spec!)

© 2011Geeknet Inc

SocketIO to the Rescue

“Socket.IO aims to make realtime apps possible in every

browser and mobile device, blurring the differences

between the different transport mechanisms.”

© 2011Geeknet Inc

Socket.io Example

<script src="/socket.io/socket.io.js"></script><script> var socket = io.connect('http://localhost'); socket.on('news', function (data) { console.log(data); socket.emit('my other event’, { my:'data' }); });</script>

© 2011Geeknet Inc

gevent_socketio

def hello_world(environ, start_response): if not environ['PATH_INFO'].startswith( '/socket.io'): return serve_file(environ, start_response) socketio = environ['socketio'] while True: socketio.send('Hello, world') gevent.sleep(2)

© 2011Geeknet Inc

- Getting started with Gevent

- AJAX, push, WebSockets, wha?

- ZeroMQ for fun and multiprocessing

- Putting it all together

© 2011Geeknet Inc

ZeroMQ Overview

C library with Python bindings

ZMQ “sockets” are message based, delivery is via a dedicated communication thread

ZMQ transports (tcp, inproc, unix, multicast)

ZMQ socket types REQ/RES PUSH/PULL PUB/SUB …

© 2011Geeknet Inc

pyzmq and gevent_zmq

pyzmq works great for threading

gevent_zmq is necessary for gevent (single-threaded)

Be careful when forking or otherwise using multiprocessing!

Gevent has a global “hub” of greenlets ZeroMQ has a global thread & “context” for communication Best to wait till done forking before initializing ZeroMQ

© 2011Geeknet Inc

ZeroMQ: bind/connect and pub/sub

from gevent_zeromq import zmq

context = zmq.Context()

sock_queue = context.socket(zmq.PUB)

sock_queue.bind('inproc://chat')

zmq_sock = context.socket(zmq.SUB)

zmq_sock.setsockopt(zmq.SUBSCRIBE, "")

zmq_sock.connect('inproc://chat')

© 2011Geeknet Inc

- Getting started with Gevent

- AJAX, push, WebSockets, wha?

- ZeroMQ for fun and multiprocessing

- Putting it all together

© 2011Geeknet Inc

WebChat: Design

IncomingGreenlet

ZMQ send

OutgoingGreenlet

Socket.io

ZMQ recv

JSON Messages JSON Messages

Socket.io

© 2011Geeknet Inc

WebChat: HTML

<h1>Socket.io Chatterbox</h1><div id="status" style="border:1px solid black;"> Disconnected</div><form> <input id="input" style="width: 35em;"></form><div id="data" style="border:1px solid black;"></div><script src="/js/jquery.min.js"></script><script src="/js/socket.io.js"></script><script src="/js/test.js"></script>

© 2011Geeknet Inc

WebChat: JavascriptSetup

(function() { // Create and connect socket var socket = new io.Socket('localhost'); socket.connect(); // Socket status var $status = $('#status'); socket.on('connect', function() { $status.html('<b>Connected: ' + socket.transport.type + '</b>'); }); socket.on('error', function() { $status.html('<b>Error</b>'); }); socket.on('disconnect', function() { $status.html('<b>Closed</b>'); });

© 2011Geeknet Inc

WebChat: JavascriptCommunication

// Send data to the server var $form = $('form'); var $input = $('#input'); $form.bind('submit', function() { socket.send($input.val()); $input.val(''); return false; }); // Get data back from the server var $data = $('#data'); socket.on('message', function(msg) { msg = $.parseJSON(msg); var u = msg.u || 'SYSTEM’; $data.prepend($( '<em>' + u + '</em>: ' + msg.m + '<br/>')); });})();

© 2011Geeknet Inc

WebChat: Server

def chat(environ, start_response): if not environ['PATH_INFO'].startswith( '/socket.io): return serve_file(environ, start_response) socketio = environ['socketio'] #... handle auth ... zmq_sock = context.socket(zmq.SUB) zmq_sock.setsockopt(zmq.SUBSCRIBE, "") zmq_sock.connect('inproc://chat') greenlets = [ gevent.spawn(incoming, uname, socketio), gevent.spawn(outgoing, zmq_sock, socketio) ] gevent.joinall(greenlets)

© 2011Geeknet Inc

WebChat: Greenlets

def incoming(uname, socketio): while True: for part in socketio.recv(): sock_queue.send(json.dumps(dict( u=uname, m=part)))

def outgoing(zmq_sock, socketio): while True: socketio.send(zmq_sock.recv())

© 2011Geeknet Inc

Get the Code!

Socket.iohttp://socket.ioMIT License

Chatterboxhttp://sf.net/u/rick446/

pygothamApache License

ZeroMQhttp://www.zeromq.org

LGPL License

Geventhttp://gevent.org

MIT License

© 2011Geeknet Inc

Rick Copeland@rick446

rick@geek.net

top related