The TourBuilder service allows users to build location aware mobile tours. The tours
are constructed using a website where the user creates a tour containing one or
more toursights. Each toursight has a location which is chosen using google maps.
Whenever a user following one of the tours gets within a thresshold of one of the
toursights, the toursight will be shown on the users mobile device.
The TourBuilder private service uses a website to set up the tours. The website
will not be presented in this walk-through. Instead we will be looking at the Tourbuilder
server.
You can download the source for the server
here.
The TourServer console application is the actual server.
The DataLogic class-library is used for database access.
The TourLocationRecieveWS is a web-service used for receiving user location-changes
from the StreamSpin server.
We use to servers in the console application server, one registering users wanting
to join tours, and one listening for user location-changes.
This is why we use the following properties: two sets of ports, TCPListeneres, and
threads.
TourServer.Program.cs
private const int port = 20000;
private Thread serverThread;
private TcpListener serverListener = null;
private const int tourstarterport = 20001;
private Thread tourserverthread;
private TcpListener tourserverListener = null;
IDictionary<int, Tour> runningTours = new Dictionary<int, Tour>();
UserLocationWS.UserLocation userLocationRequestWS = null;
We now move on to creating the two servers.
The server listening for users requesting tours is:
TourServer.Program.cs
...
private void StartTourStarterServer()
{
bool running = true;
serverListener = null;
while (running)
{
try
{
IPHostEntry
host = Dns.GetHostEntry(Dns.GetHostName());
IPAddress
localAddr = host.AddressList[0];
tourserverListener
= new TcpListener(localAddr, tourstarterport);
tourserverListener.Start();
while
(running)
{
try
{
TcpClient
client = tourserverListener.AcceptTcpClient();
Stream
s = client.GetStream();
BinaryReader
binReader = new BinaryReader(s);
int
userID = binReader.ReadInt32();
int
serviceID = binReader.ReadInt32();
if
(RegisterUserLocationRequest(userID, serviceID))
{
runningTours.Add(userID,
new Tour(serviceID));
}
client.Close();
}
catch
(Exception ex)
{
running = false;
}
}
}
catch (Exception ex)
{
running = false;
}
}
}
...
The server listens for incomming connections. When connected it tries to retrieve
ther userid and serviceid
The server now tries to register a user location request with the StreamSpin online
server via a web-service.
TourServer.Program.cs
...
public bool RegisterUserLocationRequest(int userId, int serviceId)
{
try
{
string url = "http://tourlocation.streamspin.com/Service.asmx/RecieveUserLocation";
double movementThresshold
= 5;
string timestamp = CryptoLib.Signatures.CreateTimeStamp(DateTime.Now);
string toSign = movementThresshold.ToString()
+ serviceId.ToString() +
timestamp + userId.ToString() + url;
string signature = CryptoLib.Signatures.Sign(toSign,
"[SECRET KEY]");
bool isRegistrered =
userLocationRequestWS.RegisterUserLocationRequest(
movementThresshold,
serviceId, timestamp, userId, url, signature);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message.ToString());
return false;
}
return true;
}
...
What the RegisterUserLocationRequest method does is to ask the StreamSpin server
if the user is online
The serviceId and password of the requesting services is also provided to autenticate
the request.
If the user is online the boolean value true is returned, and the new location is
sent to the web-service whenever the user moves more than 5 meters.
If the user location request succeeds an entry is added to the list of running tours,
with the userid as key.
runningTours.Add(userID, new Tour(serviceID));
The new tour is created with a list of sights and a LocationChanged method which
will be used when we recieve location changes from the StreamSpin server.
Now we need a server listening for location changes in already registered users.
The actual reporting to the server from the StreamSpin servers are done either through
a web-service or a web-page as described in the web-service API in the
The RegisterUserLocationRequest method
TourServer.Program.cs
...
private void StartRequestLocationTestServer()
{
bool running = true;
serverListener = null;
while (running)
{
try
{
IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
IPAddress localAddr = host.AddressList[0];
serverListener = new TcpListener(localAddr, port);
serverListener.Start();
while (running)
{
try
{
TcpClient client = serverListener.AcceptTcpClient();
Stream s = client.GetStream();
BinaryReader binReader = new BinaryReader(s);
int userID = binReader.ReadInt32();
double lat = binReader.ReadDouble();
double lng = binReader.ReadDouble();
if (lat != -999)
runningTours[userID].LocationChange(new Location(lat, lng));
client.Close();
}
catch (Exception ex)
{
running = false;
}
}
}
catch (Exception ex)
{
running = false;
}
}
}
...
The user location changes is recieved through the TourBuilder TourLocationRecieve
web-service.
Since the callback is a simple http request you must remember to add the following
to your web service web.config file.
TourLocationRecieveWS.Web.config
...
<configuration>
<appSettings/>
<connectionStrings/>
<system.web>
<webServices>
</webServices>
...
The web-service uses a socket to report user location changes to the server above.
Three values are reported by the StreamSpin location server. The userid and the
latitude and longitude of the user.
If the user is ofline the Streamspin location server returns the latitude and longitude
as -999.
If the user is online, then the server above uses the list of running tours.
runningTours[userID].LocationChange(new Location(lat, lng));
The LocationChange method of the tour checks whether the user is within a desired
threshold of a tour sight.
TourServer.Tour.cs
...
IDictionary<int, Location> seights = new Dictionary<int, Location>();
IDictionary<int, string> seightNames = new Dictionary<int, string>();
contentpublisher.ContentPublishing cp = null;
public void LocationChange(Location userLocation)
{
foreach (KeyValuePair<int, Location> seightPair
in seights)
{
Location seightLocation
= seightPair.Value;
double dist = seightLocation.Distance(userLocation);
if (!seightLocation.IsViewed
&& dist < tourThressHold)
{
seightLocation.IsViewed = true;
contentpublisher.Content content = new TourServer.contentpublisher.Content();
content.Description = string.Format("Tour : {0}", seightNames[seightPair.Key]);
content.Url = string.Format(
"http://tourbuilder.streamspin.com/ShowSeight.aspx?seightid={0}",
seightPair.Key);
string timestamp = CryptoLib.Signatures.CreateTimeStamp(DateTime.Now);
string text = content.Description + content.Url + serviceId.ToString() + timestamp;
string
signature = CryptoLib.Signatures.Sign(text, "[SECRET KEY]");
int
returnVal = cp.PublishPrivateContent(content, serviceId, signature,
timestamp);
if
(returnVal < -900)
Console.WriteLine(returnVal.ToString());
}
}
}
...
Each tour has two dictionaries. One mapping sight ids to sight names, and one mapping
sight ids to locations.
IDictionary<int, Location> seights = new Dictionary<int, Location>();
IDictionary<int, string> seightNames = new Dictionary<int, string>();
The LocationChange method checks if the new user location is within one of the not
viewed sights.
A distance between the user location and the sight is calculated, and a check is
performed to see whether the distance is less than the desired threshold. If so,
then we send the sight to the user.
Location seightLocation = seightPair.Value;
double dist = seightLocation.Distance(userLocation);
if (!seightLocation.IsViewed && dist < tourThressHold)
{
...
We use the StreamSpin content publishing webservice to send the sigth to the user.
contentpublisher.Content content = new TourServer.contentpublisher.Content();
The content description is set to the name of the sight.
content.Description = string.Format("Tour : {0}", seightNames[seightPair.Key]);
The content url is set to link to a tourbuilder web-page which will show the details
of the sight.
content.Url = string.Format(
"http://tourbuilder.streamspin.com/ShowSeight.aspx?seightid={0}",
seightPair.Key);
Finally the content is published to the user, along with the serviceid and password
of the tourbuilder service for authentication.
int returnVal = cp.PublishPrivateContent(content, serviceId, signature, timestamp);
This is the end of the walk-through.
The remaining files in the tour builder project is merely used for accessing the
database (DataLogic.Tours.cs) and representing locations and sights aswell as distance
calculations (TourServer.Location.cs).
The server is started in the TourServer.Program.cs constructor.
TourServer.Program.cs
...
public Program()
{
tourserverthread = new Thread(new ThreadStart(StartTourStarterServer));
tourserverthread.Start();
serverThread = new Thread(new ThreadStart(StartRequestLocationTestServer));
serverThread.Start();
}
...
Jump up to the start of the example