What we'll cover
1. Why Real-Time?2. Common Real-Time Use Cases3. What are your options?
How do you choose?.NET examplesPros & Cons
4. The Future of Real-Time
4 / 121
@leggetter
Realtime is required when there's a Need or
Demand for:
Up to date informationInteraction to maintain engagement (UX)
8 / 121
@leggetter
Internet
“ a global computer network providing a varietyof information and communication facilities,consisting of interconnected networks usingstandardized communication protocols.
10 / 121
@leggetter
HTTP + Browsers were restrictive
HTTP - request/response paradigmKeeping persistent HTTP connections aliveNo cross-browser XMLHttpRequest2 connection limitNo browser cross origin supportGeneral cross browser incompatibilities
18 / 121
@leggetter
HTTP + Browsers were restrictive
HTTP - request/response paradigmKeeping persistent HTTP connections aliveNo cross-browser XMLHttpRequest2 connection limitNo browser cross origin supportGeneral cross browser incompatibilitiesSo we HACKED! Java Applets, Flash, HTTP Hacks
18 / 121
@leggetter
Technology Advancements
Memory & CPU speed and costThe CloudBrowser standardisation & enhancementsAny client can use the standards
21 / 121
@leggetter
Internet Usage (per day)
200 billion emails
24 / 121
@leggetter
Internet Usage (per day)
200 billion emails
7 million blog posts written†
500 million tweets30 billion WhatsApp messages
24 / 121
@leggetter
Internet Usage (per day)
200 billion emails
7 million blog posts written†
500 million tweets30 billion WhatsApp messages
55 million Facebook status updates5 billion Google+ +1's60 million Instagram photos posted2 billion minutes spent on Skype33 million hours of Netflix watched750 million hours of YouTube watched
24 / 121
@leggetter
Polling Calculations
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, with a 10 second polling interval
38 / 121
@leggetter
Polling Calculations
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, with a 10 second polling interval3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,000
38 / 121
@leggetter
Polling Calculations
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, with a 10 second polling interval3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,0004. Poll requests per user/minute = (60 / 10) = 6
38 / 121
@leggetter
Polling Calculations
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, with a 10 second polling interval3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,0004. Poll requests per user/minute = (60 / 10) = 65. Poll requests per user/hour = (6 * 60) = 360
38 / 121
@leggetter
Polling Calculations
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, with a 10 second polling interval3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,0004. Poll requests per user/minute = (60 / 10) = 65. Poll requests per user/hour = (6 * 60) = 3606. Poll requests site wide per hour = (360 * 10,000) = 3,600,000
38 / 121
@leggetter
Polling Calculations
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, with a 10 second polling interval3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,0004. Poll requests per user/minute = (60 / 10) = 65. Poll requests per user/hour = (6 * 60) = 3606. Poll requests site wide per hour = (360 * 10,000) = 3,600,000
With polling the site would need to handle 3.65 Million requests per hour
Or 50k HTTP requests + maintain 10k persistent connections?
38 / 121
@leggetter
Cache - clients keep pollingPush Proxy solutions
fanout.iostreamdata.io
Quick Win solutions
39 / 121
@leggetter
2. Use an existing solution
Don't reinvent the wheel
Unless you've a unique use case
40 / 121
@leggetter
Why use an existing solution?
Connection fallback/upgrade hacks still requiredWebSocket: 91% of connectionsHTTP fallback: 9% of connections
41 / 121
@leggetter
Why use an existing solution?
Connection fallback/upgrade hacks still requiredWebSocket: 91% of connectionsHTTP fallback: 9% of connections
Support/Community
41 / 121
@leggetter
Why use an existing solution?
Connection fallback/upgrade hacks still requiredWebSocket: 91% of connectionsHTTP fallback: 9% of connections
Support/CommunityMaintenance
41 / 121
@leggetter
Why use an existing solution?
Connection fallback/upgrade hacks still requiredWebSocket: 91% of connectionsHTTP fallback: 9% of connections
Support/CommunityMaintenanceFuture features
41 / 121
@leggetter
Why use an existing solution?
Connection fallback/upgrade hacks still requiredWebSocket: 91% of connectionsHTTP fallback: 9% of connections
Support/CommunityMaintenanceFuture featuresScaling
41 / 121
@leggetter
Solutions by language
PHP: Ratchet, dNode-phpJava: Netty, JettyJavaScript (Node.JS): Faye, Socket.IO (Engine.IO), Primus.io.NET (C#): SignalR, XSocketsPython: Lots of options built on TornadoRuby: em-websocket, FayeLanguage agnostic: most hosted services
44 / 121
@leggetter
Mobile Friendly
Only some have mobile librariesHow much data are you sending?SSL required on 3/4G networks
46 / 121
@leggetter
5. Application/Solution Communication Patterns
How does the client/server & client/client communicate
47 / 121
@leggetter
Simple Messaging
// client
var ws = new WebSocket('wss://localhost/');
ws.onmessage = function(evt) { var data = JSON.parse(evt.data);
50 / 121
@leggetter
Simple Messaging
// client
var ws = new WebSocket('wss://localhost/');
ws.onmessage = function(evt) { var data = JSON.parse(evt.data);
// ^5 performHighFive();};
50 / 121
@leggetter
Simple Messaging
// client
var ws = new WebSocket('wss://localhost/');
ws.onmessage = function(evt) { var data = JSON.parse(evt.data);
// ^5 performHighFive();};
// server
server.on('connection', function(socket){
50 / 121
@leggetter
Simple Messaging
// client
var ws = new WebSocket('wss://localhost/');
ws.onmessage = function(evt) { var data = JSON.parse(evt.data);
// ^5 performHighFive();};
// server
server.on('connection', function(socket){
socket.send(JSON.stringify({action: 'high-5'}));});
50 / 121
@leggetter
Simple Messaging
using Nexmo.Api;
// SMSvar results = SMS.Send(new SMS.SMSRequest { from = "15555551212", to = "17775551212", text = "this is a test" });
// Voicevar result = Voice.TextToSpeech(new Voice.TextToSpeechCallCommand { to = "17775551212", from = "15555551212", text = "Hello from Nexmo" });
51 / 121
@leggetter
PubSub
// client
var client = new Faye.Client('http://localhost:8000/faye');
client.subscribe('/leggetter-updates', function(data) {
54 / 121
@leggetter
PubSub
// client
var client = new Faye.Client('http://localhost:8000/faye');
client.subscribe('/leggetter-updates', function(data) {
console.log(data.text);});
54 / 121
@leggetter
PubSub
// client
var client = new Faye.Client('http://localhost:8000/faye');
client.subscribe('/leggetter-updates', function(data) {
console.log(data.text);});
client.subscribe('/leggetter-dm-notifications', function(data) { console.log(data.count);});
54 / 121
@leggetter
PubSub
// client
var client = new Faye.Client('http://localhost:8000/faye');
client.subscribe('/leggetter-updates', function(data) {
console.log(data.text);});
client.subscribe('/leggetter-dm-notifications', function(data) { console.log(data.count);});
// server
server.publish('/leggetter-updates', {text: 'Hello DevWeek!'});
54 / 121
@leggetter
PubSub
// client
var client = new Faye.Client('http://localhost:8000/faye');
client.subscribe('/leggetter-updates', function(data) {
console.log(data.text);});
client.subscribe('/leggetter-dm-notifications', function(data) { console.log(data.count);});
// server
server.publish('/leggetter-updates', {text: 'Hello DevWeek!'});
server.publish('/leggetter-dm-notifications', {count: 2});
54 / 121
@leggetter
Evented PubSub
// client
var updates = io('/leggetter-updates');updates.on('created', function (data) { // Add activity to UI});
56 / 121
@leggetter
Evented PubSub
// client
var updates = io('/leggetter-updates');updates.on('created', function (data) { // Add activity to UI});updates.on('updated', function(data) { // Update activity});updates.on('deleted', function(data) { // Remove activity});
56 / 121
@leggetter
Evented PubSub
// client
var updates = io('/leggetter-updates');updates.on('created', function (data) { // Add activity to UI});updates.on('updated', function(data) { // Update activity});updates.on('deleted', function(data) { // Remove activity});
// server
var io = require('socket.io')();var updates = io.of('/leggetter-updates');
56 / 121
@leggetter
Evented PubSub
// client
var updates = io('/leggetter-updates');updates.on('created', function (data) { // Add activity to UI});updates.on('updated', function(data) { // Update activity});updates.on('deleted', function(data) { // Remove activity});
// server
var io = require('socket.io')();var updates = io.of('/leggetter-updates');updates.emit('created', {text: 'PubSub Rocks!', id: 1});
56 / 121
@leggetter
Evented PubSub
// client
var updates = io('/leggetter-updates');updates.on('created', function (data) { // Add activity to UI});updates.on('updated', function(data) { // Update activity});updates.on('deleted', function(data) { // Remove activity});
// server
var io = require('socket.io')();var updates = io.of('/leggetter-updates');updates.emit('created', {text: 'PubSub Rocks!', id: 1});updates.emit('updated', {text: 'Evented PubSub Rocks!', id: 1});updates.emit('deleted', {id: 1});
56 / 121
@leggetter
PubSub
client.subscribe('devexp-channel', function(data) { if(data.eventType === 'chat-message') { addMessage(data.message); }
60 / 121
@leggetter
PubSub
client.subscribe('devexp-channel', function(data) { if(data.eventType === 'chat-message') { addMessage(data.message); } else if(data.eventType === 'channel-purposed-changed') { updateRoomTitle(data.purpose); } else if(/* and so on */) { }})
60 / 121
@leggetter
PubSub
client.subscribe('devexp-channel', function(data) { if(data.eventType === 'chat-message') { addMessage(data.message); } else if(data.eventType === 'channel-purposed-changed') { updateRoomTitle(data.purpose); } else if(/* and so on */) { }})
Evented PubSub
var devexp = io('/devexp-channel');devexp.on('chat-message', addMessage);devexp.on('channel-purposed-changed', updateChannelPurpose);
60 / 121
@leggetter
PubSub
client.subscribe('devexp-channel', function(data) { if(data.eventType === 'chat-message') { addMessage(data.message); } else if(data.eventType === 'channel-purposed-changed') { updateRoomTitle(data.purpose); } else if(/* and so on */) { }})
Evented PubSub
var devexp = io('/devexp-channel');devexp.on('chat-message', addMessage);devexp.on('channel-purposed-changed', updateChannelPurpose);devexp.on('current-topic-changed', updateChannelTopic);devexp.on('user-online', userOnline);devexp.on('user-offline', userOffline);
60 / 121
@leggetter
Data Sync
// client
var ref = new Firebase("https://app.firebaseio.com/doc1/lines");
63 / 121
@leggetter
Data Sync
// client
var ref = new Firebase("https://app.firebaseio.com/doc1/lines");
ref.on('child_added', function(childSnapshot, prevChildKey) { // code to handle new child.});
63 / 121
@leggetter
Data Sync
// client
var ref = new Firebase("https://app.firebaseio.com/doc1/lines");
ref.on('child_added', function(childSnapshot, prevChildKey) { // code to handle new child.});
ref.on('child_changed', function(childSnapshot, prevChildKey) { // code to handle child data changes.});
63 / 121
@leggetter
Data Sync
// client
var ref = new Firebase("https://app.firebaseio.com/doc1/lines");
ref.on('child_added', function(childSnapshot, prevChildKey) { // code to handle new child.});
ref.on('child_changed', function(childSnapshot, prevChildKey) { // code to handle child data changes.});
ref.on('child_removed', function(oldChildSnapshot) { // code to handle child removal.});
63 / 121
@leggetter
Data Sync
// client
var ref = new Firebase("https://app.firebaseio.com/doc1/lines");
ref.on('child_added', function(childSnapshot, prevChildKey) { // code to handle new child.});
ref.on('child_changed', function(childSnapshot, prevChildKey) { // code to handle child data changes.});
ref.on('child_removed', function(oldChildSnapshot) { // code to handle child removal.});
ref.push({ 'editor_id': 'leggetter', 'text': 'Nexmo Rocks!' });
63 / 121
@leggetter
Data Sync
// client
var ref = new Firebase("https://app.firebaseio.com/doc1/lines");
ref.on('child_added', function(childSnapshot, prevChildKey) { // code to handle new child.});
ref.on('child_changed', function(childSnapshot, prevChildKey) { // code to handle child data changes.});
ref.on('child_removed', function(oldChildSnapshot) { // code to handle child removal.});
ref.push({ 'editor_id': 'leggetter', 'text': 'Nexmo Rocks!' });
Framework handles updates to other clients
63 / 121
@leggetter
RMI
// clientvar chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) { // handle message};
66 / 121
@leggetter
RMI
// clientvar chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) { // handle message};
chat.server.send( 'me', 'hello world' );
66 / 121
@leggetter
RMI
// clientvar chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) { // handle message};
chat.server.send( 'me', 'hello world' );
$.connection.hub.start(); // async
66 / 121
@leggetter
RMI
// clientvar chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) { // handle message};
chat.server.send( 'me', 'hello world' );
$.connection.hub.start(); // async
// serverpublic class ChatHub : Hub{
66 / 121
@leggetter
RMI
// clientvar chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) { // handle message};
chat.server.send( 'me', 'hello world' );
$.connection.hub.start(); // async
// serverpublic class ChatHub : Hub{ public void Send(string name, string message) {
66 / 121
@leggetter
RMI
// clientvar chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) { // handle message};
chat.server.send( 'me', 'hello world' );
$.connection.hub.start(); // async
// serverpublic class ChatHub : Hub{ public void Send(string name, string message) { // Call the broadcastMessage method to update clients. Clients.All.broadcastMessage(name, message); }}
66 / 121
@leggetter
Code
https://github.com/leggetter/realtime-dotnet-examplesShort link: http://j.mp/rt-dotnet-ex
73 / 121
@leggetter
.NET Self-Hosted Real-Time options
SignalRXSockets
75 / 121
@leggetter
What we'll look at:
References\Microsoft.AspNet.SignalR.*
Scripts\jquery.signalR*.js
App_Start\SignalRStartup.cs
Controllers\HomeController.cs
Hubs\ChatHub.cs
Views\Home\SignalR.cshtml
Script\chat\SignalRChat.js
77 / 121
@leggetter
Pros
.NETSimple integrationMS SupportedjQuery Dependency
Cons
Tightly coupledRMI onlySelf-ScalingScaling (realtime + HTTP)
Self-Hosted Demo 1: Pro & Cons
78 / 121
@leggetter
What we'll look at:
References\XSockets.*
App_Start\XSocketsStartup.cs
Controllers\HomeController.cs
XSockets\ChatController.cs
Views\Home\XSockets.cshtml
Scripts\XSockets.latest.js
Script\chat\XSocketsChat.cs
80 / 121
@leggetter
Pros
.NETSimple integrationCommunication patterns
PubSub/EventedRMI
Licensed
Cons
Tightly coupledSelf-ScalingScaling (realtime + HTTP)Licensed
Self-Hosted Demo 2: Pro & Cons
81 / 121
@leggetter
Pros
.NETMaps well to PubSubLoosely coupledCould use another runtime
Cons
How does it fit with RMI/SignalR?Multiple componentsSelf-scalingQueue routing questionsIn: HTTP. Out: WebSocket
Self-Hosted: .NET + Message Queue - Pro &
Cons
84 / 121
@leggetter
Pros
PubSubConnection fallbackRedis Queue supportSimple integration
Cons
Not .NET(?)You need to scale
Self-Hosted + Faye: Pros & Cons
86 / 121
@leggetter
.NET Hosted Real-Time options
AblyFirebaseFanoutPubNubPusherRealtime.coSyncano
87 / 121
@leggetter
What we'll look at:
References\PusherServer
Controllers\HomeController.cs
Views\Home\Pusher.cshtml
Script\chat\PusherChat.js
Pusher Debug Console
89 / 121
@leggetter
Pros
Simple & powerfulInstantly scalableManaged & dedicatedDirect integration. No overhead.
Cons
3rd party relianceDifficult to influence functionality
Hosted - Pros & Cons
90 / 121
@leggetter
Why use a hosted service?
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, no polling
91 / 121
@leggetter
Why use a hosted service?
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, no polling3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,000
91 / 121
@leggetter
Why use a hosted service?
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, no polling3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,0004. That's it! Total: 50,000
91 / 121
@leggetter
Why use a hosted service?
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, no polling3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,0004. That's it! Total: 50,000
Your servers handle 50k requests per hour instead of 3.6M
You offload the polling or persistent connections to the service
91 / 121
@leggetter
Build vs. Buy - Costs
baremetrics.com/calculator
93 / 121
@leggetter
How do you choose?
7 Realtime Framework Considerations
1. Should you keep on polling?2. Use an Existing Solution3. Use a language you're comfortable with4. Do you need native mobile support?5. Simple Messaging, PubSub/Evented, RMI or DataSync6. Architectural considerations7. Hosted v Self-Hosted (Build vs. Buy)
94 / 121
@leggetter
BayeuxDDPdNodeEPCPGRIPgRPC
MQTTPusher ProtocolSTOMPSignalR ProtocolWAMP (Web App Messaging Protocol)
XMPP (various)
Communication Pattern Protocol
Standardisation
97 / 121
@leggetter
A thing can be anything
SensorsAppliancesVehiclesSmart PhonesDevices (Arduino, Electric Imp, Raspberry Pi etc.)
104 / 121
@leggetter
A thing can be anything
SensorsAppliancesVehiclesSmart PhonesDevices (Arduino, Electric Imp, Raspberry Pi etc.)ServersBrowsersApps: Native, Web, running anywhere
104 / 121
@leggetter
The Majority of code we'll write will still be
for "Apps"
ConfiguringMonitoringInteractingApp Logic
105 / 121
@leggetter
Real-Time Use Case Evolution
Notifications & SignallingActivity StreamsData Viz & PollsChatCollaborationMultiplayer Games
106 / 121
@leggetter
Notifications/Activity Streams -> Actions
107 / 121
@leggetterThe end of apps as we know it - Intercom
600M MAUs10M integrationsapp-within-an-app modeltaxi, order food, tickets, games etc.
112 / 121
@leggetter
Ben Foxall - A conceptual future for the multi-device web (FutureJS 2014)
118 / 121
@leggetter
You need Real-Time!
There are lots of options.
Make the choice that's right for you.
I hope this helps!
119 / 121
@leggetter
Resources
Real-time Tech Guidegithub.com/leggetter/realtime-dotnet-examplesTools, Tips and Techniques for Developing Real-time AppsNexmo
120 / 121
@leggetter