Unraveling the Cable: How Action Cable Works

Unraveling the Cable: How Action Cable Works

Rails ships with some handy features, and most of that functionality is there because we have common problems; and those problems need a default solution. You need quick two-way communication between your client and server. Action Cable can solve that quickly, and get you up and productive with out setting up expensive external add-ons and complex integrations.

This magic is wonderful until you need to untangle how websockets, connections, channels, consumers, and clients all fit together. Let's look under the hood and see how everything is woven together.

971cfac6afb23c0f0fd3623284a02a81?s=128

Christopher Sexton

April 30, 2019
Tweet

Transcript

  1. @crsexton

  2. @crsexton Unraveling the Cable How Action Cable Works rails websocket

  3. @crsexton Hi, I'm Christopher! @crsexton Name Twitters

  4. @crsexton About Me

  5. @crsexton About Me

  6. @crsexton About Me

  7. @crsexton About Me

  8. @crsexton Unraveling the Cable How Action Cable Works Enough of

    that Let's get to the technical bits already
  9. @crsexton What should you get out of this?

  10. @crsexton Not magic, not really

  11. @crsexton

  12. @crsexton

  13. @crsexton def nand(a, b) !(a && b) end

  14. @crsexton What is Action Cable?

  15. @crsexton What is Action Cable? Chat Rooms

  16. @crsexton What is Action Cable? Everyone makes chat rooms with

    it
  17. @crsexton What is Action Cable? So many chat rooms

  18. @crsexton What even is Action Cable?

  19. @crsexton Why do we care? What does Action Cable get

    us?
  20. @crsexton Why use Action Cable? Server to client push

  21. @crsexton Why use Action Cable? Real Time*

  22. @crsexton Why use Action Cable? Overhead

  23. @crsexton How does it work? The Action Cable lifecycle

  24. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  25. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE *YAWN* ~~ Anxiety ~~
  26. @crsexton Action Cable Lifecycle 1. Client connects to cable 2.

    Server confirms the connection 3. Client subscribes to channel 4. Client sends messages to server 5. Server sends messages to client
  27. @crsexton Action Cable Lifecycle 1. Client connects to cable 2.

    Server confirms the connection 3. Client subscribes to channel 4. Client sends messages to server 5. Server sends messages to client import { createConsumer } from "@rails/actioncable" export default createConsumer() app/javascript/channels/consumer.js
  28. @crsexton Action Cable Lifecycle 1. Client connects to cable 2.

    Server confirms the connection 3. Client subscribes to channel 4. Client sends messages to server 5. Server sends messages to client module ApplicationCable class Connection < ActionCable::Connection::Base identified_by :current_user def connect self.current_user = find_user end protected def find_user User.find_by(id: cookies.signed[:user_id]) end end end app/javascript/channels/room_channel.js
  29. @crsexton Action Cable Lifecycle 1. Client connects to cable 2.

    Server confirms the connection 3. Client subscribes to channel 4. Client sends messages to server 5. Server sends messages to client consumer.subscriptions.create("RoomChannel", { received(data) { // Handle Data }, speak: function(message) { return this.perform('speak', {message: message}); } }); app/javascript/channels/room_channel.js
  30. @crsexton Action Cable Lifecycle 1. Client connects to cable 2.

    Server confirms the connection 3. Client subscribes to channel 4. Client sends messages to server 5. Server sends messages to client class RoomChannel < ApplicationCable::Channel def speak(data) Message.create! content: data["message"] end end app/channels/room_channel.rb consumer.subscriptions.create("RoomChannel", { speak: function(message) { return this.perform('speak', {message: message}); } }); app/javascript/channels/room_channel.js
  31. @crsexton Action Cable Lifecycle 1. Client connects to cable 2.

    Server confirms the connection 3. Client subscribes to channel 4. Client sends messages to server 5. Server sends messages to client ActionCable.server.broadcast "room_channel", message: "OHAI World" app/models/message.rb
  32. @crsexton Action Cable Lifecycle

  33. @crsexton Action Cable Lifecycle C HANNELS STREAMS CONNECTIONS BROADCAST CONSUMERS

  34. @crsexton All the terminology! C HANNELS STREAMS CONNECTIONS BROADCAST CONSUMERS

  35. @crsexton 1. Client connects to cable Action Cable Lifecycle

  36. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  37. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  38. @crsexton How does it open a connection? 1. Client connects

    to cable
  39. @crsexton Client connects to cable webSocket = new WebSocket(url, protocols);

    action_cable.js
  40. @crsexton Client connects to cable

  41. @crsexton Client connects to cable

  42. @crsexton How does the HTTP request work? 1. Client connects

    to cable
  43. @crsexton Client connects to cable CLIENT SERVER

  44. @crsexton Client connects to cable CLIENT SERVER TCP CONNECTION

  45. @crsexton Client connects to cable CLIENT SERVER

  46. @crsexton Client connects to cable GET / HTTP/1.0 Host: localhost:3000

    CLIENT SERVER
  47. @crsexton Client connects to cable GET / HTTP/1.0 Host: localhost:3000

    CLIENT SERVER
  48. @crsexton Client connects to cable GET / HTTP/1.0 Host: localhost:3000

    HTTP/1.0 200 OK Content-Type: text/html Cache-Control: max-age=0 Set-Cookie: chocolate=chip <!DOCTYPE html> <body> ... CLIENT SERVER
  49. @crsexton Client connects to cable How would we do that

    in Ruby?
  50. @crsexton Client connects to cable require 'socket' body = <<~EOF

    GET / HTTP/1.0\r Host: localhost:3000\r \r EOF Socket.tcp("localhost", 3000) {|sock| sock.print body sock.close_write puts sock.read }
  51. @crsexton Client connects to cable require 'socket' body = <<~EOF

    GET / HTTP/1.0\r Host: localhost:3000\r \r EOF Socket.tcp("localhost", 3000) {|sock| sock.print body sock.close_write puts sock.read } RAILS LOGS Completed 200 OK in 15ms (Views: 13.6ms | ActiveRecord: 0.2ms)
  52. @crsexton require 'socket' body = <<~EOF GET / HTTP/1.0\r Host:

    localhost:3000\r \r EOF Socket.tcp("localhost", 3000) {|sock| sock.print body sock.close_write puts sock.read } Client connects to cable
  53. @crsexton require 'socket' body = <<~EOF GET / HTTP/1.0\r Host:

    localhost:3000\r \r EOF Socket.tcp("localhost", 3000) {|sock| sock.print body sock.close_write puts sock.read } Client connects to cable RESPONSE HTTP/1.0 200 OK X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff X-Download-Options: noopen X-Permitted-Cross-Domain-Policies: none Referrer-Policy: strict-origin-when-cross-origin Content-Type: text/html; charset=utf-8 ETag: W/"9f4da7fe69623bb6fe6c3efe5c907c06" Cache-Control: max-age=0, private, must-revalidate Set-Cookie: _unravel_session=lq1ryiEWvEoFCSLXrzVlLdkAzpAhQPWJMS X-Request-Id: 38dde761-2dda-4f57-93cf-2a4b0f871912 X-Runtime: 0.016541 <!DOCTYPE html> <html> <head> <title>Unravel</title> <meta name="csrf-param" content="authenticity_token" />
  54. @crsexton How do we get from HTTP to a socket?

    1. Client connects to cable
  55. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  56. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  57. @crsexton Web Server Client GET 200 OK

  58. @crsexton Web Server Client GET 200 OK

  59. @crsexton Web Server Client GET 101 Switch Protocol

  60. @crsexton Web Server Client GET 101 Switch Protocol

  61. @crsexton Web Server Client GET 101 Switch Protocol Web Socket

  62. @crsexton Web Server Client GET 101 Switch Protocol Socket (ノ◕ヮ◕)ノ*:・゚✧

    MAGIC
  63. @crsexton Client connects to cable body = <<~EOF.gsub("\n", "\r\n") GET

    ws://localhost:3000/cable HTTP/1.1\r Host: localhost:3000\r Connection: Upgrade\r Upgrade: websocket\r Origin: http://localhost:3000\r \r EOF Socket.tcp("localhost", 3000) {|sock| sock.print body sock.close_write puts sock.read }
  64. @crsexton Client connects to cable body = <<~EOF.gsub("\n", "\r\n") GET

    ws://localhost:3000/cable HTTP/1.1\r Host: localhost:3000\r Connection: Upgrade\r Upgrade: websocket\r Origin: http://localhost:3000\r \r EOF Socket.tcp("localhost", 3000) {|sock| sock.print body sock.close_write puts sock.read } LOGS Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
  65. @crsexton body = <<~EOF.gsub("\n", "\r\n") GET ws://localhost:3000/cable HTTP/1.1\r Host: localhost:3000\r

    Connection: Upgrade\r Upgrade: websocket\r Origin: http://localhost:3000\r \r EOF Socket.tcp("localhost", 3000) {|sock| sock.print body sock.close_write puts sock.read } Client connects to cable RESPONSE HTTP/1.1 101 Web Socket Protocol Handshake Upgrade: WebSocket Connection: Upgrade WebSocket-Origin: http://localhost:3000 WebSocket-Location: ws://localhost:3000 puts sock.read
  66. @crsexton body = <<~EOF.gsub("\n", "\r\n") GET ws://localhost:3000/cable HTTP/1.1\r Host: localhost:3000\r

    Connection: Upgrade\r Upgrade: websocket\r Origin: http://localhost:3000\r \r EOF Socket.tcp("localhost", 3000) {|sock| sock.print body sock.close_write puts sock.read } Client connects to cable RESPONSE HTTP/1.1 101 Web Socket Protocol Handshake Upgrade: WebSocket Connection: Upgrade WebSocket-Origin: http://localhost:3000 WebSocket-Location: ws://localhost:3000 puts sock.read
  67. @crsexton Client connects to cable

  68. @crsexton Client connects to cable

  69. @crsexton 2. Server confirms the connection Action Cable Lifecycle

  70. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  71. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  72. @crsexton Rack 2. Server confirms the connection

  73. @crsexton

  74. @crsexton require 'rack' app = Proc.new do |env| [ '200',

    {'Content-Type' => 'text/html'}, ['Response Body'] ] end Rack::Handler::WEBrick.run app
  75. @crsexton require 'rack' app = Proc.new do |env| [ '200',

    {'Content-Type' => 'text/html'}, ['Response Body'] ] end Rack::Handler::WEBrick.run app
  76. @crsexton require 'rack' app = Proc.new do |env| [ '200',

    {'Content-Type' => 'text/html'}, ['Response Body'] ] end Rack::Handler::WEBrick.run app
  77. @crsexton require 'rack' app = Proc.new do |env| [ '200',

    {'Content-Type' => 'text/html'}, ['Response Body'] ] end Rack::Handler::WEBrick.run app
  78. @crsexton Rack Proc Proc Proc Proc

  79. @crsexton Rack Proc Proc Proc Proc Request

  80. @crsexton Rack Proc Proc Proc Proc Request call(env)

  81. @crsexton Rack Proc Proc Proc Proc Request call(env) call(env)

  82. @crsexton Rack Proc Proc Proc Proc Request call(env) call(env) call(env)

  83. @crsexton Rack Proc Proc Proc Proc Request call(env) call(env) call(env)

    return
  84. @crsexton Rack Proc Proc Proc Proc Request call(env) call(env) call(env)

    return return
  85. @crsexton Rack Proc Proc Proc Proc Request call(env) call(env) call(env)

    return return return
  86. @crsexton Rack Proc Proc Proc Proc Request call(env) call(env) call(env)

    return return return Response
  87. @crsexton Rack Hijack 2. Server confirms the connection

  88. @crsexton Rack Hijack Proc Proc Proc Proc

  89. @crsexton Rack Hijack Proc Proc Proc Proc Request

  90. @crsexton Rack Hijack Proc Proc Proc Proc Request call(env)

  91. @crsexton Rack Hijack env['rack.hijack'].call Proc Proc Proc Proc Request call(env)

  92. @crsexton Rack Hijack Proc Proc Proc Proc Request call(env)

  93. @crsexton Rack Hijack # rails/actioncable/test/connection/client_socket_test.rb io, client_io = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM,

    0) env["rack.hijack"] = -> { env["rack.hijack_io"] = io }
  94. @crsexton Rack Hijack # rails/actioncable/test/connection/client_socket_test.rb io, client_io = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM,

    0) env["rack.hijack"] = -> { env["rack.hijack_io"] = io }
  95. @crsexton Server confirms the connection (‛ƅϪƅ)‛ “(゚ヮ゚“) Socket Connected

  96. @crsexton Cookies 2. Server confirms the connection

  97. @crsexton Server confirms the connection

  98. @crsexton Server confirms the connection module ApplicationCable class Connection <

    ActionCable::Connection::Base identified_by :current_user def connect self.current_user = find_verified_user end private def find_verified_user User.find_by(id: cookies.encrypted[:user_id]) end end end app/channels/application_cable/connection.rb
  99. @crsexton Server confirms the connection module ApplicationCable class Connection <

    ActionCable::Connection::Base identified_by :current_user def connect self.current_user = find_verified_user end private def find_verified_user User.find_by(id: cookies.encrypted[:user_id]) end end end app/channels/application_cable/connection.rb
  100. @crsexton

  101. @crsexton 3. Client subscribes to the channel Action Cable Lifecycle

  102. @crsexton Subscription Handshake 3. Client subscribes to channel

  103. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  104. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  105. @crsexton Client subscribes to the channel CLIENT SERVER {"type":"welcome"}

  106. @crsexton Client subscribes to the channel CLIENT SERVER { "command":"subscribe",

    "identifier":"{'channel':'RoomChannel'}" }
  107. @crsexton Client subscribes to the channel CLIENT SERVER { "identifier":"{'channel':'RoomChannel'}",

    "type":"confirm_subscription" }
  108. @crsexton Client subscribes to the channel

  109. @crsexton Redis Pub/Sub 3. Client subscribes to channel

  110. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  111. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  112. @crsexton Redis CLI

  113. @crsexton Redis Pub/Sub Server Client % %

  114. @crsexton Redis Pub/Sub % redis-cli % Server Client

  115. @crsexton Redis Pub/Sub % Server Client % redis-cli 127.0.0.1:6379> SUBSCRIBE

    chan1
  116. @crsexton Redis Pub/Sub % Server Client % redis-cli 127.0.0.1:6379> SUBSCRIBE

    chan1 Reading messages... 1) "subscribe" 2) "chan1" 3) (integer) 1 ⏎
  117. @crsexton Redis Pub/Sub % redis-cli Server Client % redis-cli 127.0.0.1:6379>

    SUBSCRIBE chan1 Reading messages... 1) "subscribe" 2) "chan1" 3) (integer) 1
  118. @crsexton Redis Pub/Sub % redis-cli 127.0.0.1:6379> Server Client % redis-cli

    127.0.0.1:6379> SUBSCRIBE chan1 Reading messages... 1) "subscribe" 2) "chan1" 3) (integer) 1 ⏎
  119. @crsexton Redis Pub/Sub % redis-cli 127.0.0.1:6379> PUBLISH chan1 hi Server

    Client % redis-cli 127.0.0.1:6379> SUBSCRIBE chan1 Reading messages... 1) "subscribe" 2) "chan1" 3) (integer) 1 ⏎
  120. @crsexton Redis Pub/Sub % redis-cli 127.0.0.1:6379> PUBLISH chan1 hi Server

    Client ⏎ % redis-cli 127.0.0.1:6379> SUBSCRIBE chan1 Reading messages... 1) "subscribe" 2) "chan1" 3) (integer) 1 1) "message" 2) "chan1" 3) "hi"
  121. @crsexton Redis Pub/Sub Server Client % redis-cli 127.0.0.1:6379> SUBSCRIBE chan1

    Reading messages... 1) "subscribe" 2) "chan1" 3) (integer) 1 1) "message" 2) "chan1" 3) "hi" % redis-cli 127.0.0.1:6379> PUBLISH chan1 hi (integer) 1 127.0.0.1:6379> PUBLISH chan1 hi
  122. @crsexton Redis Pub/Sub % redis-cli 127.0.0.1:6379> SUBSCRIBE chan1 Reading messages...

    1) "subscribe" 2) "chan1" 3) (integer) 1 1) "message" 2) "chan1" 3) "hi" 1) "message" 2) "chan1" 3) "hi" % redis-cli 127.0.0.1:6379> PUBLISH chan1 hi (integer) 1 127.0.0.1:6379> PUBLISH chan1 hi (integer) 1 Server Client ⏎
  123. @crsexton Redis Pub/Sub in Ruby?

  124. @crsexton Redis Pub/Sub require 'redis' $redis = Redis.new $redis.subscribe('chan1') do

    |on| on.message do |channel, msg| puts "1) message" puts "2) #{channel}" puts "3) #{msg}" end end require 'redis' $redis = Redis.new $redis.publish "chan1", ARGV[0]
  125. @crsexton Redis Pub/Sub Server % ruby sub.rb Client %

  126. @crsexton Redis Pub/Sub Server % ruby sub.rb Client ⏎ %

  127. @crsexton Redis Pub/Sub Server % ruby sub.rb Client % ruby

    pub.rb hi
  128. @crsexton Redis Pub/Sub Server Client % ruby sub.rb 1) message

    2) chan1 3) hi % ruby pub.rb hi % ⏎
  129. @crsexton Redis Pub/Sub Server Client % ruby sub.rb 1) message

    2) chan1 3) hi % ruby pub.rb hi
 % ruby pub.rb there
  130. @crsexton Redis Pub/Sub Server Client % ruby sub.rb 1) message

    2) chan1 3) hi 1) message 2) chan1 3) there % ruby pub.rb hi
 % ruby pub.rb there ⏎
  131. @crsexton 4. Client sends messages to server Action Cable Lifecycle

  132. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  133. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  134. @crsexton How do WebSockets send the data? 4. Client sends

    messages to server
  135. @crsexton How WebSockets Work 0 1 2 3 0 1

    2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ RFC 6455
  136. @crsexton (Like this helps) 0 1 2 3 0 1

    2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ RFC 6455
  137. @crsexton How WebSockets Work OPCODE LENGTH EXT LENGTH MASK DATA

    2 BYTES Client Only 4 BYTES N BYTES
  138. @crsexton 2-10 BYTES How WebSockets Work 2 BYTES 4 BYTES

    N BYTES OPCODE LENGTH EXT LENGTH MASK DATA Client Only
  139. @crsexton OPCODE LENGTH EXT LENGTH MASK DATA Client Only How

    WebSockets Work 2 BYTES 4 BYTES N BYTES NOT TO SCALE
  140. @crsexton How WebSockets Work

  141. @crsexton 5. Server sends messages to client Action Cable Lifecycle

  142. @crsexton Client Cable Server WebSocket Web Server Redis Workers Other

    Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  143. @crsexton Client WebSocket Redis GET 101 Switch Protocol Web Server

    Workers Other Traffic Cable Server
  144. @crsexton Client WebSocket Redis GET 101 Switch Protocol Web Server

    Workers Other Traffic Cable Server
  145. @crsexton Client WebSocket Redis GET 101 Switch Protocol Web Server

    Workers Other Traffic Cable Server
  146. @crsexton Sending messages to client 1. Broadcast to Redis Pub/Sub

    2. Publish to Cable Server 3. Send message over socket 4. Receive message in consumer
  147. @crsexton Sending messages to client ActionCable .server .broadcast "my_channel", message:

    "hi", one: 1 JSON { "identifier" : "{\"channel\":\"MyChannel\"}", "message" : { "message" : "hi", "one" : 1 } }
  148. @crsexton Sending messages to client import consumer from "./consumer" window.room

    = consumer.subscriptions.create("RoomChannel", { received(data) { var div = document.getElementById('messages'); div.innerHTML += data['message']; }, }); JSON { "message" : { "message" : "hi", "one" : 1 }, "identifier" : "{\"channel\":\"RoomChannel\"}" }
  149. @crsexton What just happened? Let's recap Did anyone get all

    that? Narrator: "No"
  150. @crsexton WHEW

  151. @crsexton Client Cable Server WebSocket Web Server Redis Other Traffic

    Workers Other Traffic GET 101 Switch Protocol PUBLISH/SUBSCRIBE
  152. @crsexton Client Cable Server Web Server Redis Other Traffic

  153. @crsexton Recap 1. Client connects to cable 2. Server confirms

    the connection 3. Client subscribes to channel 4. Client sends messages to server 5. Server sends messages to client Cable Server Web Server Redis Other Traffic Client
  154. @crsexton Recap 1. Client connects to cable 2. Server confirms

    the connection 3. Client subscribes to channel 4. Client sends messages to server 5. Server sends messages to client Cable Server Web Server Redis Other Traffic Client
  155. @crsexton Recap 1. Client connects to cable 2. Server confirms

    the connection 3. Client subscribes to channel 4. Client sends messages to server 5. Server sends messages to client Cable Server Web Server Redis Other Traffic Client
  156. @crsexton Recap 1. Client connects to cable 2. Server confirms

    the connection 3. Client subscribes to channel 4. Client sends messages to server 5. Server sends messages to client Cable Server Web Server Redis Other Traffic Client
  157. @crsexton Recap 1. Client connects to cable 2. Server confirms

    the connection 3. Client subscribes to channel 4. Client sends messages to server 5. Server sends messages to client Cable Server Web Server Redis Other Traffic Client
  158. @crsexton Recap 1. Client connects to cable 2. Server confirms

    the connection 3. Client subscribes to channel 4. Client sends messages to server 5. Server sends messages to client Cable Server Web Server Redis Other Traffic Client
  159. @crsexton Thanks! Questions?

  160. @crsexton I'm Christopher, bye! @crsexton Also answers to "hey you"

    Come say "hi"