High-Frequency Realtime with SignalR 1.x
This tutorial shows how to create a web application that uses ASP.NET SignalR to provide high-frequency messaging functionality. High-frequency messaging in this case means updates that are sent at a fixed rate; in the case of this application, up to 10 messages a second.
The application you’ll create in this tutorial displays a shape that users can drag. The position of the shape in all other connected browsers will then be updated to match the position of the dragged shape using timed updates.
Concepts introduced in this tutorial have applications in real-time gaming and other simulation applications.
Comments on the tutorial are welcome. If you have questions that are not directly related to the tutorial, you can post them to the ASP.NET SignalR forum or StackOverflow.com.
Overview
This tutorial demonstrates how to create an application that shares the state of an object with other browsers in real time. The application we’ll create is called MoveShape. The MoveShape page will display an HTML Div element that the user can drag; when the user drags the Div, its new position will be sent to the server, which will then tell all other connected clients to update the shape’s position to match.
The application created in this tutorial is based on a demo by Damian Edwards. A video containing this demo can be seen here.
The tutorial will start by demonstrating how to send SignalR messages from each event that fires as the shape is dragged. Each connected client will then update the position of the local version of the shape each time a message is received.
While the application will function using this method, this is not a recommended programming model, since there would be no upper limit to the number of messages getting sent, so the clients and server could get overwhelmed with messages and performance would degrade. The displayed animation on the client would also be disjointed, as the shape would be moved instantly by each method, rather than moving smoothly to each new location. Later sections of the tutorial will demonstrate how to create a timer function that restricts the maximum rate at which messages are sent by either the client or server, and how to move the shape smoothly between locations. The final version of the application created in this tutorial can be downloaded from Code Gallery.
This tutorial contains the following sections:
- Prerequisites
- Create the project
- Add the ASP.NET SignalR and JQuery.UI NuGet packages
- Create the base application
- Add the client loop
- Add the server loop
- Add smooth animation on the client
- Further Steps
Prerequisites
This tutorial requires Visual Studio 2012 or Visual Studio 2010. If Visual Studio 2010 is used, the project will use .NET Framework 4 rather than .NET Framework 4.5.
If you are using Visual Studio 2012, it’s recommended that you install the ASP.NET and Web Tools 2012.2 update. This update contains new features such as enhancements to publishing, new functionality, and new templates.
If you have Visual Studio 2010, make sure that NuGet is installed.
Create the project
In this section, we’ll create the project in Visual Studio.
- From the File menu click New Project.
- In the New Project dialog box, expand C# under Templates and select Web.
Select the ASP.NET Empty Web Application template, name the project MoveShapeDemo, and click OK.
Add the SignalR and JQuery.UI NuGet Packages
You can add SignalR functionality to a project by installing a NuGet package. This tutorial will also use the JQuery.UI package for allowing the shape to be dragged and animated.
- Click Tools | Library Package Manager | Package Manager Console.
Enter the following command in the package manager.
[!code-powershellMain]
1: Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
Enter the following command into the package manager console to install the JQuery and JQuery.UI packages.
[!code-powershellMain]
1: Install-Package jQuery.ui.combined
Create the base application
In this section, we’ll create a browser application that sends the location of the shape to the server during each mouse move event. The server then broadcasts this information to all other connected clients as it is received. We’ll expand on this application in later sections.
- In Solution Explorer, right-click on the project and select Add, Class…. Name the class MoveShapeHub and click Add.
Replace the code in the new MoveShapeHub class with the following code.
[!code-csharpMain]
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using Microsoft.AspNet.SignalR;
5: using Microsoft.AspNet.SignalR.Hubs;
6: using Newtonsoft.Json;
7:
8: namespace MoveShapeDemo
9: {
10: public class MoveShapeHub : Hub
11: {
12: public void UpdateModel(ShapeModel clientModel)
13: {
14: clientModel.LastUpdatedBy = Context.ConnectionId;
15: // Update the shape model within our broadcaster
16: Clients.AllExcept(clientModel.LastUpdatedBy).updateShape(clientModel);
17: }
18: }
19: public class ShapeModel
20: {
21: // We declare Left and Top as lowercase with
22: // JsonProperty to sync the client and server models
23: [JsonProperty("left")]
24: public double Left { get; set; }
25:
26: [JsonProperty("top")]
27: public double Top { get; set; }
28:
29: // We don't want the client to get the "LastUpdatedBy" property
30: [JsonIgnore]
31: public string LastUpdatedBy { get; set; }
32: }
33: }
The
The object that will be sent to the client (MoveShapeHub
class above is an implementation of a SignalR hub. As in the Getting Started with SignalR tutorial, the hub has a method that the clients will call directly. In this case, the client will send an object containing the new X and Y coordinates of the shape to the server, which then gets broadcasted to all other connected clients. SignalR will automatically serialize this object using JSON.ShapeModel
) contains members to store the position of the shape. The version of the object on the server also contains a member to track which client’s data is being stored, so that a given client won’t be sent their own data. This member uses theJsonIgnore
attribute to keep it from being serialized and sent to the client.Next, we’ll set up the hub when the application starts. In Solution Explorer, right-click the project, then click Add | Global Application Class. Accept the default name of Global and click OK.
Add the following
[!code-csharpMain]using
statement after the provided using statements in the Global.asax.cs class.1: using System.Web.Routing;
Add the following line of code in the
Application_Start
method of the Global class to register the default route for SignalR.[!code-csharpMain]
1: RouteTable.Routes.MapHubs();
Your global.asax file should look like the following:
[!code-csharpMain]1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5: using System.Web.Security;
6: using System.Web.SessionState;
7:
8: using System.Web.Routing;
9:
10: namespace MoveShapeDemo
11: {
12: public class Global : System.Web.HttpApplication
13: {
14: protected void Application_Start(object sender, EventArgs e)
15: {
16: RouteTable.Routes.MapHubs();
17: }
18: }
19: }
- Next, we’ll add the client. In Solution Explorer, right-click the project, then click Add | New Item. In the Add New Item dialog, select Html Page. Give the page an appropriate name (like Default.html) and click Add.
- In Solution Explorer, right-click the page you just created and click Set as Start Page.
Replace the default code in the HTML page with the following code snippet.
[!NOTE] Verify that the script references below match the packages added to your project in the Scripts folder. In Visual Studio 2010, the version of JQuery and SignalR added to the project may not match the version numbers below.
[!code-htmlMain]
1: <!DOCTYPE html>
2: <html>
3: <head>
4: <title>SignalR MoveShape Demo</title>
5: <style>
6: #shape {
7: width: 100px;
8: height: 100px;
9: background-color: #FF0000;
10: }
11: </style>
12: </head>
13: <body>
14: <script src="Scripts/jquery-1.6.4.js"></script>
15: <script src="Scripts/jquery-ui-1.10.2.js"></script>
16: <script src="Scripts/jquery.signalR-1.0.1.js"></script>
17: <script src="/signalr/hubs"></script>
18: <script>
19: $(function () {
20: var moveShapeHub = $.connection.moveShapeHub,
21: $shape = $("#shape"),
22:
23: shapeModel = {
24: left: 0,
25: top: 0
26: };
27:
28: moveShapeHub.client.updateShape = function (model) {
29: shapeModel = model;
30: $shape.css({ left: model.left, top: model.top });
31: };
32:
33: $.connection.hub.start().done(function () {
34: $shape.draggable({
35: drag: function () {
36: shapeModel = $shape.offset();
37: moveShapeHub.server.updateModel(shapeModel);
38: }
39: });
40: });
41: });
42: </script>
43:
44: <div id="shape" />
45: </body>
46: </html>
drag
event to send the shape’s position to the server.Start the application by pressing F5. Copy the page’s URL, and paste it into a second browser window. Drag the shape in one of the browser windows; the shape in the other browser window should move.
Add the client loop
Since sending the location of the shape on every mouse move event will create an unneccesary amount of network traffic, the messages from the client need to be throttled. We’ll use the javascript setInterval
function to set up a loop that sends new position information to the server at a fixed rate. This loop is a very basic representation of a “game loop”, a repeatedly called function that drives all of the functionality of a game or other simulation.
Update the client code in the HTML page to match the following code snippet.
[!code-htmlMain]
1: <!DOCTYPE html>
2: <html>
3: <head>
4: <title>SignalR MoveShape Demo</title>
5: <style>
6: #shape {
7: width: 100px;
8: height: 100px;
9: background-color: #FF0000;
10: }
11: </style>
12:
13: </head>
14: <body>
15: <script src="Scripts/jquery-1.6.4.js"></script>
16: <script src="Scripts/jquery-ui-1.10.2.js"></script>
17: <script src="Scripts/jquery.signalR-1.0.1.js"></script>
18: <script src="/signalr/hubs"></script>
19: <script>
20: $(function () {
21: var moveShapeHub = $.connection.moveShapeHub,
22: $shape = $("#shape"),
23: // Send a maximum of 10 messages per second
24: // (mouse movements trigger a lot of messages)
25: messageFrequency = 10,
26: // Determine how often to send messages in
27: // time to abide by the messageFrequency
28: updateRate = 1000 / messageFrequency,
29: shapeModel = {
30: left: 0,
31: top: 0
32: },
33: moved = false;
34:
35: moveShapeHub.client.updateShape = function (model) {
36: shapeModel = model;
37: $shape.css({ left: model.left, top: model.top });
38: };
39:
40: $.connection.hub.start().done(function () {
41: $shape.draggable({
42: drag: function () {
43: shapeModel = $shape.offset();
44: moved = true;
45: }
46: });
47:
48: // Start the client side server update interval
49: setInterval(updateServerModel, updateRate);
50: });
51:
52: function updateServerModel() {
53: // Only update server if we have a new movement
54: if (moved) {
55: moveShapeHub.server.updateModel(shapeModel);
56: moved = false;
57: }
58: }
59: });
60: </script>
61:
62: <div id="shape" />
63: </body>
64: </html>
updateServerModel
function, which gets called on a fixed frequency. This function sends the position data to the server whenever themoved
flag indicates that there is new position data to send.Start the application by pressing F5. Copy the page’s URL, and paste it into a second browser window. Drag the shape in one of the browser windows; the shape in the other browser window should move. Since the number of messages that get sent to the server will be throttled, the animation will not appear as smooth as in the previous section.
Add the server loop
In the current application, messages sent from the server to the client go out as often as they are received. This presents a similar problem as was seen on the client; messages can be sent more often than they are needed, and the connection could become flooded as a result. This section describes how to update the server to implement a timer that throttles the rate of the outgoing messages.
Replace the contents of
MoveShapeHub.cs
with the following code snippet.[!code-csharpMain]
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5:
6: using System.Threading;
7: using Microsoft.AspNet.SignalR;
8:
9: using Newtonsoft.Json;
10:
11: namespace MoveShapeDemo
12: {
13: public class Broadcaster
14: {
15: private readonly static Lazy<Broadcaster> _instance =
16: new Lazy<Broadcaster>(() => new Broadcaster());
17: // We're going to broadcast to all clients a maximum of 25 times per second
18: private readonly TimeSpan BroadcastInterval =
19: TimeSpan.FromMilliseconds(40);
20: private readonly IHubContext _hubContext;
21: private Timer _broadcastLoop;
22: private ShapeModel _model;
23: private bool _modelUpdated;
24:
25: public Broadcaster()
26: {
27: // Save our hub context so we can easily use it
28: // to send to its connected clients
29: _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
30:
31: _model = new ShapeModel();
32: _modelUpdated = false;
33:
34: // Start the broadcast loop
35: _broadcastLoop = new Timer(
36: BroadcastShape,
37: null,
38: BroadcastInterval,
39: BroadcastInterval);
40: }
41:
42: public void BroadcastShape(object state)
43: {
44: // No need to send anything if our model hasn't changed
45: if (_modelUpdated)
46: {
47: // This is how we can access the Clients property
48: // in a static hub method or outside of the hub entirely
49: _hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
50: _modelUpdated = false;
51: }
52: }
53:
54: public void UpdateShape(ShapeModel clientModel)
55: {
56: _model = clientModel;
57: _modelUpdated = true;
58: }
59:
60: public static Broadcaster Instance
61: {
62: get
63: {
64: return _instance.Value;
65: }
66: }
67: }
68:
69: public class MoveShapeHub : Hub
70: {
71: // Is set via the constructor on each creation
72: private Broadcaster _broadcaster;
73:
74: public MoveShapeHub()
75: : this(Broadcaster.Instance)
76: {
77: }
78:
79: public MoveShapeHub(Broadcaster broadcaster)
80: {
81: _broadcaster = broadcaster;
82: }
83:
84: public void UpdateModel(ShapeModel clientModel)
85: {
86: clientModel.LastUpdatedBy = Context.ConnectionId;
87: // Update the shape model within our broadcaster
88: _broadcaster.UpdateShape(clientModel);
89: }
90: }
91: public class ShapeModel
92: {
93: // We declare Left and Top as lowercase with
94: // JsonProperty to sync the client and server models
95: [JsonProperty("left")]
96: public double Left { get; set; }
97:
98: [JsonProperty("top")]
99: public double Top { get; set; }
100:
101: // We don't want the client to get the "LastUpdatedBy" property
102: [JsonIgnore]
103: public string LastUpdatedBy { get; set; }
104: }
105:
106: }
The above code expands the client to add the
Broadcaster
class, which throttles the outgoing messages using theTimer
class from the .NET framework.Since the hub itself is transitory (it is created every time it is needed), the
Broadcaster
will be created as a singleton. Lazy initialization (introduced in .NET 4) is used to defer its creation until it is needed, ensuring that the first hub instance is completely created before the timer is started.The call to the clients’
Lastly, instead of calling the client method from the hub directly, theUpdateShape
function is then moved out of the hub’sUpdateModel
method, so that it is no longer called immediately whenever incoming messages are received. Instead, the messages to the clients will be sent at a rate of 25 calls per second, managed by the_broadcastLoop
timer from within theBroadcaster
class.Broadcaster
class needs to obtain a reference to the currently operating hub (_hubContext
) using theGlobalHost
.Start the application by pressing F5. Copy the page’s URL, and paste it into a second browser window. Drag the shape in one of the browser windows; the shape in the other browser window should move. There will not be a visible difference in the browser from the previous section, but the number of messages that get sent to the client will be throttled.
Add smooth animation on the client
The application is almost complete, but we could make one more improvement, in the motion of the shape on the client as it is moved in response to server messages. Rather than setting the position of the shape to the new location given by the server, we’ll use the JQuery UI library’s animate
function to move the shape smoothly between its current and new position.
Update the client’s
updateShape
method to look like the highlighted code below:[!code-htmlMain]
1: <!DOCTYPE html>
2: <html>
3: <head>
4: <title>SignalR MoveShape Demo</title>
5: <style>
6: #shape {
7: width: 100px;
8: height: 100px;
9: background-color: #FF0000;
10: }
11: </style>
12:
13: </head>
14: <body>
15: <script src="Scripts/jquery-1.6.4.js"></script>
16: <script src="Scripts/jquery-ui-1.10.2.js"></script>
17: <script src="Scripts/jquery.signalR-1.0.1.js"></script>
18: <script src="/signalr/hubs"></script>
19: <script>
20: $(function () {
21: var moveShapeHub = $.connection.moveShapeHub,
22: $shape = $("#shape"),
23: // Send a maximum of 10 messages per second
24: // (mouse movements trigger a lot of messages)
25: messageFrequency = 10,
26: // Determine how often to send messages in
27: // time to abide by the messageFrequency
28: updateRate = 1000 / messageFrequency,
29: shapeModel = {
30: left: 0,
31: top: 0
32: },
33: moved = false;
34:
35: moveShapeHub.client.updateShape = function (model) {
36: shapeModel = model;
37: // Gradually move the shape towards the new location (interpolate)
38: // The updateRate is used as the duration because by the time
39: // we get to the next location we want to be at the "last" location
40: // We also clear the animation queue so that we start a new
41: // animation and don't lag behind.
42: $shape.animate(shapeModel, { duration: updateRate, queue: false });
43: };
44:
45: $.connection.hub.start().done(function () {
46: $shape.draggable({
47: drag: function () {
48: shapeModel = $shape.offset();
49: moved = true;
50: }
51: });
52:
53: // Start the client side server update interval
54: setInterval(updateServerModel, updateRate);
55: });
56:
57: function updateServerModel() {
58: // Only update server if we have a new movement
59: if (moved) {
60: moveShapeHub.server.updateModel(shapeModel);
61: moved = false;
62: }
63: }
64: });
65: </script>
66:
67: <div id="shape" />
68: </body>
69: </html>
Start the application by pressing F5. Copy the page’s URL, and paste it into a second browser window. Drag the shape in one of the browser windows; the shape in the other browser window should move. The movement of the shape in the other window should appear less jerky as its movement is interpolated over time rather than being set once per incoming message.
Further Steps
In this tutorial, you’ve learned how to program a SignalR application that sends high-frequency messages between clients and servers. This communication paradigm is useful for developing online games and other simulations, such as the ShootR game created with SignalR.
The complete application created in this tutorial can be downloaded from Code Gallery.
To learn more about SignalR development concepts, visit the following sites for SignalR source code and resources:
|