Upgrade to Pro — share decks privately, control downloads, hide ads and more …

WebSockets

Igor Wiedler
January 28, 2012

 WebSockets

Talk on WebSockets for the PHPBenelux Conference 2012.

Igor Wiedler

January 28, 2012
Tweet

More Decks by Igor Wiedler

Other Decks in Programming

Transcript

  1. • phpBB
    • Symfony2
    • Silex
    igorw

    View full-size slide

  2. Application HTTP
    Presentation
    TCP
    Session TCP
    Transport
    TCP
    Network IP
    Data Link
    Physical

    View full-size slide

  3. Application HTTP
    Presentation
    TCP
    Session TCP
    Transport
    TCP
    Network IP
    Data Link
    Physical the web
    stack

    View full-size slide

  4. request
    client

    View full-size slide

  5. reponse
    client
    request
    web is built
    around
    client-server

    View full-size slide

  6. THIS IS A
    GOOD
    THING

    View full-size slide

  7. • Headers
    • Content-Types
    • Caching
    • Authentication
    • Transactions
    • Stateless
    this provides
    you with lots
    of useful
    tools

    View full-size slide

  8. NOT FOR
    EVERYTHING
    event-driven
    apps
    games streaming

    View full-size slide

  9. UNIDIRECTIONAL

    View full-size slide

  10. are we there
    yet?
    polling

    View full-size slide

  11. LATENCY
    lots of
    overhead and
    processing

    View full-size slide

  12. STATELESS
    sessions are
    re-
    implemented
    with cookies

    View full-size slide

  13. Application HTTP
    Presentation
    TCP
    Session TCP
    Transport
    TCP
    Network IP
    Data Link
    Physical

    View full-size slide

  14. Application HTTP
    Presentation
    TCP
    Session TCP
    Transport
    TCP
    Network IP
    Data Link
    Physical tcp has
    sessions
    http throws
    them away

    View full-size slide

  15. SOCKETS
    persistent
    connection
    between two
    machines

    View full-size slide

  16. WEBSOCKETS
    server-push

    View full-size slide

  17. PROTOCOL API

    View full-size slide

  18. THE
    WEBSOCKET
    PROTOCOL

    View full-size slide

  19. browser
    browser-
    server

    View full-size slide

  20. bi-directional

    View full-size slide

  21. • Handshake / Upgrade
    • TCP
    • Proxies
    • Supports TLS (SSL)

    View full-size slide

  22. COMET
    legacy push
    hacks
    websockets
    before they
    were cool

    View full-size slide

  23. THE
    WEBSOCKET
    API

    View full-size slide

  24. browser
    app-browser

    View full-size slide

  25. var ws = new WebSocket('ws://example.org:8080/updates');
    ws.onopen = function () {
    ws.send('hello');
    };
    ws.onmessage = function (message) {
    console.log(message.data);
    };

    View full-size slide

  26. JS INTERFACE
    • WebSocket
    • onopen
    • onmessage
    • onerror
    • onclose
    • binaryType
    • MessageEvent
    • data

    View full-size slide

  27. browser
    API PROTOCOL

    View full-size slide

  28. • built on node.js
    • abstracts socket communication
    • supports fallback transports

    View full-size slide

  29. • session storage
    • namespacing
    • acknowledgement
    • broadcasting
    • reconnection

    View full-size slide

  30. • json serialization
    • heartbeats
    • connection ids
    • io.sockets collection

    View full-size slide

  31. • browser support
    flash
    fallback
    supports
    everything!

    View full-size slide

  32. Y U NO PHP?
    apache and
    php suck at
    long-running
    connections

    View full-size slide

  33. zero-broker
    messaging

    View full-size slide

  34. $context = new ZMQContext();
    $sock = $context->getSocket(ZMQ::SOCKET_PUB);
    $sock->connect('tcp://127.0.0.1:5555');
    $msg = json_encode([
    'type' => 'debug',
    'data' => ['foo', 'bar', 'baz']
    ]);
    $sock->send($msg); basic php
    zmq client

    View full-size slide

  35. $context = new ZMQContext();
    $sock = $context->getSocket(ZMQ::SOCKET_PUB);
    $sock->connect('tcp://127.0.0.1:5555');
    $msg = json_encode([
    'type' => 'debug',
    'data' => ['foo', 'bar', 'baz']
    ]);
    $sock->send($msg);

    View full-size slide

  36. $context = new ZMQContext();
    $sock = $context->getSocket(ZMQ::SOCKET_PUB);
    $sock->connect('tcp://127.0.0.1:5555');
    $msg = json_encode([
    'type' => 'debug',
    'data' => ['foo', 'bar', 'baz']
    ]);
    $sock->send($msg);

    View full-size slide

  37. $context = new ZMQContext();
    $sock = $context->getSocket(ZMQ::SOCKET_PUB);
    $sock->connect('tcp://127.0.0.1:5555');
    $msg = json_encode([
    'type' => 'debug',
    'data' => ['foo', 'bar', 'baz']
    ]);
    $sock->send($msg);

    View full-size slide

  38. var io = require('socket.io').listen(8080),
    zmq = require('zmq'),
    sock = zmq.socket('sub');
    sock.subscribe('');
    sock.bind('tcp://*:5555');
    sock.on('message', function (msg) {
    var event = JSON.parse(msg);
    io.sockets.emit(event.type, event.data);
    });

    View full-size slide

  39. var io = require('socket.io').listen(8080),
    zmq = require('zmq'),
    sock = zmq.socket('sub');
    sock.subscribe('');
    sock.bind('tcp://*:5555');
    sock.on('message', function (msg) {
    var event = JSON.parse(msg);
    io.sockets.emit(event.type, event.data);
    });
    listens for
    socket.io on
    port 8080

    View full-size slide

  40. var io = require('socket.io').listen(8080),
    zmq = require('zmq'),
    sock = zmq.socket('sub');
    sock.subscribe('');
    sock.bind('tcp://*:5555');
    sock.on('message', function (msg) {
    var event = JSON.parse(msg);
    io.sockets.emit(event.type, event.data);
    });

    View full-size slide

  41. var io = require('socket.io').listen(8080),
    zmq = require('zmq'),
    sock = zmq.socket('sub');
    sock.subscribe('');
    sock.bind('tcp://*:5555');
    sock.on('message', function (msg) {
    var event = JSON.parse(msg);
    io.sockets.emit(event.type, event.data);
    });
    listens for
    zmq on port
    5555

    View full-size slide

  42. var io = require('socket.io').listen(8080),
    zmq = require('zmq'),
    sock = zmq.socket('sub');
    sock.subscribe('');
    sock.bind('tcp://*:5555');
    sock.on('message', function (msg) {
    var event = JSON.parse(msg);
    io.sockets.emit(event.type, event.data);
    });

    View full-size slide

  43. var io = require('socket.io').listen(8080),
    zmq = require('zmq'),
    sock = zmq.socket('sub');
    sock.subscribe('');
    sock.bind('tcp://*:5555');
    sock.on('message', function (msg) {
    var event = JSON.parse(msg);
    io.sockets.emit(event.type, event.data);
    });
    pass through
    messages
    from zmq to
    socket.io

    View full-size slide

  44. <br/>
    <br/>var socket = io.connect('http://localhost:8080');<br/>socket.on('debug', function (data) {<br/>console.log(data);<br/>});<br/>

    View full-size slide

  45. LE CODE
    •blog comments
    •chat
    •canvas

    View full-size slide

  46. LE CODE
    •blog comments
    •chat
    •canvas

    View full-size slide

  47. class CommentController extends Controller
    {
    public function createAction(Post $post)
    {
    $comment = new Comment();
    $comment->setPost($post);
    $request = $this->getRequest();
    $form = $this->createForm(new CommentType(), $comment);
    $form->bindRequest($request);
    if ($form->isValid()) {
    $em = $this->getDoctrine()->getEntityManager();
    $em->persist($comment);
    $em->flush();
    $renderedRow = $this->renderView(
    'AcmeBlogBundle:Comment:row.html.twig',
    array('comment' => $comment)
    );
    return new Response($renderedRow, 201);
    }
    return new Response('{}', 400, array('Content-Type' => 'application/json'));
    }
    }

    View full-size slide

  48. class CommentController extends Controller
    {
    public function createAction(Post $post)
    {
    $comment = new Comment();
    $comment->setPost($post);
    $request = $this->getRequest();
    $form = $this->createForm(new CommentType(), $comment);
    $form->bindRequest($request);
    if ($form->isValid()) {
    $em = $this->getDoctrine()->getEntityManager();
    $em->persist($comment);
    $em->flush();
    $renderedRow = $this->renderView(
    'AcmeBlogBundle:Comment:row.html.twig',
    array('comment' => $comment)
    );
    return new Response($renderedRow, 201);
    }
    return new Response('{}', 400, array('Content-Type' => 'application/json'));
    }
    }
    ajax response

    View full-size slide

  49. var renderComment = function (renderedRow) {
    $('.comments').append(renderedRow);
    };
    $('.comment-form form').submit(function (event) {
    event.preventDefault();
    var form = $(this),
    action = form.attr('action'),
    data = form.serialize();
    $.post(action, data, function (comment) {
    renderComment(comment);
    resetForm(form);
    });
    });

    View full-size slide

  50. var renderComment = function (renderedRow) {
    $('.comments').append(renderedRow);
    };
    $('.comment-form form').submit(function (event) {
    event.preventDefault();
    var form = $(this),
    action = form.attr('action'),
    data = form.serialize();
    $.post(action, data, function (comment) {
    renderComment(comment);
    resetForm(form);
    });
    });

    View full-size slide

  51. var renderComment = function (renderedRow) {
    $('.comments').append(renderedRow);
    };
    $('.comment-form form').submit(function (event) {
    event.preventDefault();
    var form = $(this),
    action = form.attr('action'),
    data = form.serialize();
    $.post(action, data, function (comment) {
    renderComment(comment);
    resetForm(form);
    });
    });

    View full-size slide

  52. var renderComment = function (renderedRow) {
    $('.comments').append(renderedRow);
    };
    $('.comment-form form').submit(function (event) {
    event.preventDefault();
    var form = $(this),
    action = form.attr('action'),
    data = form.serialize();
    $.post(action, data, function (comment) {
    renderComment(comment);
    resetForm(form);
    });
    });

    View full-size slide

  53. Secret Sauce

    View full-size slide

  54. private function publishCommentCreate(Comment $comment)
    {
    $context = new \ZMQContext();
    $sock = $context->getSocket(\ZMQ::SOCKET_PUB);
    $sock->connect('tcp://127.0.0.1:5555');
    $renderedRow = $this->renderView(
    'AcmeBlogBundle:Comment:row.html.twig',
    array('comment' => $comment)
    );
    $postId = $comment->getPost()->getId();
    $msg = json_encode(array(
    'type' => "post.$postId.comment.create",
    'data' => $renderedRow,
    ));
    $sock->send($msg);
    }

    View full-size slide

  55. private function publishCommentCreate(Comment $comment)
    {
    $context = new \ZMQContext();
    $sock = $context->getSocket(\ZMQ::SOCKET_PUB);
    $sock->connect('tcp://127.0.0.1:5555');
    $renderedRow = $this->renderView(
    'AcmeBlogBundle:Comment:row.html.twig',
    array('comment' => $comment)
    );
    $postId = $comment->getPost()->getId();
    $msg = json_encode(array(
    'type' => "post.$postId.comment.create",
    'data' => $renderedRow,
    ));
    $sock->send($msg);
    }

    View full-size slide

  56. private function publishCommentCreate(Comment $comment)
    {
    $context = new \ZMQContext();
    $sock = $context->getSocket(\ZMQ::SOCKET_PUB);
    $sock->connect('tcp://127.0.0.1:5555');
    $renderedRow = $this->renderView(
    'AcmeBlogBundle:Comment:row.html.twig',
    array('comment' => $comment)
    );
    $postId = $comment->getPost()->getId();
    $msg = json_encode(array(
    'type' => "post.$postId.comment.create",
    'data' => $renderedRow,
    ));
    $sock->send($msg);
    }
    per-post
    event

    View full-size slide

  57. $this->publishCommentCreate($comment);

    View full-size slide

  58. var socket = io.connect('http://localhost:8080');
    socket.on('post.'+postId+'.comment.create', function (data) {
    renderComment(data);
    });

    View full-size slide

  59. LE CODE
    •blog comments
    •chat
    •canvas

    View full-size slide

  60. var socket = io.connect('http://localhost:8080'),
    messages = $('.messages');
    socket.on('message', function (message) {
    var messageHtml = $('').text(message.body);
    messages.append(messageHtml);
    messages.prop('scrollTop', 100000);
    });

    View full-size slide

  61. var socket = io.connect('http://localhost:8080'),
    messages = $('.messages');
    socket.on('message', function (message) {
    var messageHtml = $('').text(message.body);
    messages.append(messageHtml);
    messages.prop('scrollTop', 100000);
    });

    View full-size slide

  62. var form = $('form');
    form.submit(function (event) {
    event.preventDefault();
    var input = form.find('*[name=message]'),
    body = input.val();
    if (!body.length) {
    return;
    }
    socket.emit('message', { name: name, body: body });
    input.val('');
    });

    View full-size slide

  63. var form = $('form');
    form.submit(function (event) {
    event.preventDefault();
    var input = form.find('*[name=message]'),
    body = input.val();
    if (!body.length) {
    return;
    }
    socket.emit('message', { name: name, body: body });
    input.val('');
    });

    View full-size slide

  64. var io = require('socket.io').listen(8080);
    io.sockets.on('connection', function (socket) {
    socket.on('message', function (message) {
    io.sockets.emit('message', message);
    });
    });

    View full-size slide

  65. var io = require('socket.io').listen(8080),
    messages;
    messages = [];
    io.sockets.on('connection', function (socket) {
    messages.forEach(function (message) {
    socket.emit('message', message);
    });
    socket.on('message', function (message) {
    io.sockets.emit('message', message);
    messages.push(message);
    });
    });

    View full-size slide

  66. var io = require('socket.io').listen(8080),
    messages;
    messages = [];
    io.sockets.on('connection', function (socket) {
    messages.forEach(function (message) {
    socket.emit('message', message);
    });
    socket.on('message', function (message) {
    io.sockets.emit('message', message);
    messages.push(message);
    });
    });
    buffer
    messages
    and send
    them

    View full-size slide

  67. var io = require('socket.io').listen(8080),
    bufferLimit = 15,
    messages;
    messages = [];
    io.sockets.on('connection', function (socket) {
    messages.forEach(function (message) {
    socket.emit('message', message);
    });
    socket.on('message', function (message) {
    io.sockets.emit('message', message);
    messages.push(message);
    if (messages.length > bufferLimit) {
    messages = messages.splice(-bufferLimit, bufferLimit);
    }
    });
    });

    View full-size slide

  68. var io = require('socket.io').listen(8080),
    bufferLimit = 15,
    messages;
    messages = [];
    io.sockets.on('connection', function (socket) {
    messages.forEach(function (message) {
    socket.emit('message', message);
    });
    socket.on('message', function (message) {
    io.sockets.emit('message', message);
    messages.push(message);
    if (messages.length > bufferLimit) {
    messages = messages.splice(-bufferLimit, bufferLimit);
    }
    });
    });
    limit the size
    of the buffer
    to not blow
    up the server

    View full-size slide

  69. LE CODE
    •blog comments
    •chat
    •canvas

    View full-size slide

  70. Paintbrush Canvas
    mousedown
    mousemove
    mouseup
    WS
    mousedown
    mousemove
    mouseup
    Paintbrush
    mousedown
    mousemove
    mouseup

    View full-size slide

  71. var io = require('socket.io').listen(8080);
    io.sockets.on('connection', function (socket) {
    var color = '#'+Math.floor(Math.random()*16777215).toString(16);
    socket.emit('color', color);
    socket.on('mousedown', function (event) {
    event.source = socket.id;
    event.color = color;
    socket.broadcast.emit('mousedown', event);
    });
    socket.on('mousemove', function (event) {
    event.source = socket.id;
    socket.broadcast.emit('mousemove', event);
    });
    socket.on('mouseup', function (event) {
    event.source = socket.id;
    socket.broadcast.emit('mouseup', event);
    });
    });

    View full-size slide

  72. var io = require('socket.io').listen(8080);
    io.sockets.on('connection', function (socket) {
    var color = '#'+Math.floor(Math.random()*16777215).toString(16);
    socket.emit('color', color);
    socket.on('mousedown', function (event) {
    event.source = socket.id;
    event.color = color;
    socket.broadcast.emit('mousedown', event);
    });
    socket.on('mousemove', function (event) {
    event.source = socket.id;
    socket.broadcast.emit('mousemove', event);
    });
    socket.on('mouseup', function (event) {
    event.source = socket.id;
    socket.broadcast.emit('mouseup', event);
    });
    });

    View full-size slide

  73. var io = require('socket.io').listen(8080);
    io.sockets.on('connection', function (socket) {
    var color = '#'+Math.floor(Math.random()*16777215).toString(16);
    socket.emit('color', color);
    socket.on('mousedown', function (event) {
    event.source = socket.id;
    event.color = color;
    socket.broadcast.emit('mousedown', event);
    });
    socket.on('mousemove', function (event) {
    event.source = socket.id;
    socket.broadcast.emit('mousemove', event);
    });
    socket.on('mouseup', function (event) {
    event.source = socket.id;
    socket.broadcast.emit('mouseup', event);
    });
    });

    View full-size slide

  74. var io = require('socket.io').listen(8080);
    io.sockets.on('connection', function (socket) {
    var color = '#'+Math.floor(Math.random()*16777215).toString(16);
    socket.emit('color', color);
    socket.on('mousedown', function (event) {
    event.source = socket.id;
    event.color = color;
    socket.broadcast.emit('mousedown', event);
    });
    socket.on('mousemove', function (event) {
    event.source = socket.id;
    socket.broadcast.emit('mousemove', event);
    });
    socket.on('mouseup', function (event) {
    event.source = socket.id;
    socket.broadcast.emit('mouseup', event);
    });
    });

    View full-size slide

  75. var io = require('socket.io').listen(8080);
    io.sockets.on('connection', function (socket) {
    var color = '#'+Math.floor(Math.random()*16777215).toString(16);
    socket.emit('color', color);
    socket.on('mousedown', function (event) {
    event.source = socket.id;
    event.color = color;
    socket.broadcast.emit('mousedown', event);
    });
    socket.on('mousemove', function (event) {
    event.source = socket.id;
    socket.broadcast.emit('mousemove', event);
    });
    socket.on('mouseup', function (event) {
    event.source = socket.id;
    socket.broadcast.emit('mouseup', event);
    });
    });
    broadcast
    that shit

    View full-size slide

  76. USE CASES
    •Games
    •Notifications
    •Collaboration
    •Statistics
    •Chat
    stack
    overflow
    travis-ci
    twitter

    View full-size slide

  77. PATTERNS
    • pub/sub
    • ajax-triggered messages
    • server authority
    • server vs client-side templating

    View full-size slide

  78. SECURITY
    • Security model
    • Proxy poisoning
    • Authentication
    • Encrypted transport (TLS/SSL)
    cross-site
    possible
    server
    receives
    origin header
    for auth, use
    cookies or
    pass a
    session id

    View full-size slide

  79. ACHTUNG
    • beware of the state
    • re-starting the server
    • scaling

    View full-size slide

  80. EventSource.

    View full-size slide

  81. WEBSOCKET LIBRARIES
    • socket.io
    • node: Worlize/WebSocket-Node
    • ruby: igrigorik/em-websocket
    • php: sebcode/php-websocketserver
    • js: gimite/web-socket-js
    that last one
    is the
    amazing
    flash client

    View full-size slide

  82. LINKS
    • tools.ietf.org/html/rfc6455
    • dev.w3.org/html5/websockets
    • developer.mozilla.org/en/WebSockets
    • socket.io
    • github.com/igorw/websockets-talk

    View full-size slide

  83. github.com/igorw/websockets-talk
    all the code
    from this
    talk

    View full-size slide

  84. CREDITS
    • thenounproject.com
    • stopwatch
    • lightning
    • sync
    • refresh

    View full-size slide

  85. Questions?
    @igorwesome
    speakerdeck.com
    /u/igorw
    joind.in/4763

    View full-size slide