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

WebSockets

 WebSockets

Talk on WebSockets for the PHPBenelux Conference 2012.

A4b95be2145cc46f891707b6db9dd82d?s=128

Igor Wiedler

January 28, 2012
Tweet

Transcript

  1. WEBSOCKETS

  2. • phpBB • Symfony2 • Silex igorw

  3. None
  4. WEBSOCKETS

  5. WEB

  6. Application HTTP Presentation TCP Session TCP Transport TCP Network IP

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

    Data Link Physical the web stack
  8. HTTP

  9. client

  10. request client

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

  12. THIS IS A GOOD THING

  13. • Headers • Content-Types • Caching • Authentication • Transactions

    • Stateless this provides you with lots of useful tools
  14. BUT

  15. NOT FOR EVERYTHING event-driven apps games streaming

  16. UNIDIRECTIONAL

  17. are we there yet? polling

  18. LATENCY lots of overhead and processing

  19. STATELESS sessions are re- implemented with cookies

  20. Application HTTP Presentation TCP Session TCP Transport TCP Network IP

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

    Data Link Physical tcp has sessions http throws them away
  22. WEBSOCKETS

  23. SOCKETS persistent connection between two machines

  24. WEBSOCKETS server-push

  25. PROTOCOL API

  26. THE WEBSOCKET PROTOCOL

  27. browser browser- server

  28. None
  29. RFC 6455

  30. bi-directional

  31. low-latency

  32. stateful

  33. • Handshake / Upgrade • TCP • Proxies • Supports

    TLS (SSL)
  34. COMET legacy push hacks websockets before they were cool

  35. THE WEBSOCKET API

  36. browser app-browser

  37. None
  38. var ws = new WebSocket('ws://example.org:8080/updates'); ws.onopen = function () {

    ws.send('hello'); }; ws.onmessage = function (message) { console.log(message.data); };
  39. JS INTERFACE • WebSocket • onopen • onmessage • onerror

    • onclose • binaryType • MessageEvent • data
  40. browser API PROTOCOL

  41. None
  42. • built on node.js • abstracts socket communication • supports

    fallback transports
  43. • session storage • namespacing • acknowledgement • broadcasting •

    reconnection
  44. • json serialization • heartbeats • connection ids • io.sockets

    collection
  45. • browser support flash fallback supports everything!

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

    connections
  47. zero-broker messaging

  48. None
  49. $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
  50. $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);
  51. $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);
  52. $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);
  53. None
  54. 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); });
  55. 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
  56. 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); });
  57. 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
  58. 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); });
  59. 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
  60. <script src="http://localhost:8080/socket.io/socket.io.js"> </script> <script> var socket = io.connect('http://localhost:8080'); socket.on('debug', function

    (data) { console.log(data); }); </script>
  61. LE CODE •blog comments •chat •canvas

  62. LE CODE •blog comments •chat •canvas

  63. None
  64. 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')); } }
  65. 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
  66. 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); }); });
  67. 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); }); });
  68. 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); }); });
  69. 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); }); });
  70. Secret Sauce

  71. 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); }
  72. 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); }
  73. 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
  74. $this->publishCommentCreate($comment);

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

  76. LE CODE •blog comments •chat •canvas

  77. None
  78. <div class="messages"></div> <form> <input type="text" name="message"> <button type="submit">Send</button> </form> <script

    src="http://localhost:8080/socket.io/socket.io.js"></script> <script src="jquery-1.7.min.js"></script> <script src="chat.js"></script>
  79. var socket = io.connect('http://localhost:8080'), messages = $('.messages'); socket.on('message', function (message)

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

    { var messageHtml = $('<div></div>').text(message.body); messages.append(messageHtml); messages.prop('scrollTop', 100000); });
  81. 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(''); });
  82. 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(''); });
  83. var io = require('socket.io').listen(8080); io.sockets.on('connection', function (socket) { socket.on('message', function

    (message) { io.sockets.emit('message', message); }); });
  84. 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); }); });
  85. 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
  86. 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); } }); });
  87. 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
  88. LE CODE •blog comments •chat •canvas

  89. None
  90. Paintbrush Canvas mousedown mousemove mouseup WS mousedown mousemove mouseup Paintbrush

    mousedown mousemove mouseup
  91. 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); }); });
  92. 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); }); });
  93. 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); }); });
  94. 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); }); });
  95. 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
  96. USE CASES •Games •Notifications •Collaboration •Statistics •Chat stack overflow travis-ci

    twitter
  97. PATTERNS • pub/sub • ajax-triggered messages • server authority •

    server vs client-side templating
  98. 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
  99. ACHTUNG • beware of the state • re-starting the server

    • scaling
  100. Push only?

  101. EventSource.

  102. 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
  103. LINKS • tools.ietf.org/html/rfc6455 • dev.w3.org/html5/websockets • developer.mozilla.org/en/WebSockets • socket.io •

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

  105. CREDITS • thenounproject.com • stopwatch • lightning • sync •

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