Picture Hunt Socket Sample

advertisement
Picture Hunt Silverlight Socket Demo
Storyboard
controlled
animation
Network
Status
Indicator
Automatic
connection
retry
Data Binding
to update
scores
About the game
LINQ to simplify coding
Async socket code
Multiple players in
one game
Running the Picture Hunt game demo
• Simplest way: start the “RunDemo.bat” file
• It starts the three components of the demo:
– the Silverlight Policy Server
• Go to directory (cd) “SilverlightPolicyServer”
• Start “Run.bat”
– Start the Picture Hunt Game Server
• Go to directory (cd) “PictureHunt\PictureHuntServer”
• Start PictureHuntServer.exe
– Start the Silverlight game
• Go to directory (cd) PictureHunt\PictureHunt\Bin\Debug
• Start PictureHuntTestPage.html
Overall Architecture
On the client computers:
A running Web browser
with the Silverlight game
The server computer has more pieces:
1. The game HTML file served up by a
running web server
2. The running game server program
3. A bunch of marked-up images in the right
directory
4. The running socket policy server program
Programming hint:
Common files for clients and servers
The Silverlight IDE (Microsoft Visual Web
Developers 2010 Express) will copy files into
the project directory when you “add existing
item” to your Silverlight project
That’s not what I wanted! I wanted a single file
in both the Server and Game projects.
In the end, I edited the .csproj file by hand to
reference files in the server project.
Policy Server details
Like many web-facing technologies, Silverlight
imposed some security restrictions on Sockets.
You need to run a Silverlight Policy Server on
port 943 (or other ports). The System.Net team
has a simple policy server as a code snippet at
http://msdn.microsoft.com/library/cc645032.aspx
Multiple Players
Multiplayer games are ideal for sockets:
• Anyone can click at any time
• The server can update anyone at any time
Http style “request/response” doesn’t work well
for these types of problems.
It’s the server that has most multiplayer code;
each client of course only handles itself
Async Socket Code
Connect
Read and
Write
Closing
Async Socket Code
Connecting
var s = new Socket
(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
var e = new SocketAsyncEventArgs();
e.RemoteEndPoint = new
DnsEndPoint(serverName, 4529);
e.Completed += OnConnected;
s.ConnectAsync(e);
Async Socket Code
Writing
Picture Hunt makes a list of ArraySegments, and then writes
them out with “SendAsync”. The WriteCompleted doesn’t
actually do anything for this application
var senddata = new List<ArraySegment<byte>>();
senddata.Add(new ArraySegment<byte>(header));
if (data != null)
senddata.Add(new ArraySegment<byte>(data));
var writeEventArgs = new SocketAsyncEventArgs();
writeEventArgs.Completed += WriteCompleted;
writeEventArgs.BufferList = senddata;
socket.SendAsync(writeEventArgs);
Handling message
Async Socket Code
boundaries
TCP/IP is a stream protocol: it doesn’t respect your
game message boundaries. For example: the
game server sends three messages:
But the game might get these messages:
Why? TCP/IP data doesn’t preserve message boundaries: it’s
a stream of data (like a file: you can write 10 bytes and 10
bytes and 10 bytes, and then read 8 bytes and 15 bytes and
7 bytes)
Solution: Your program has to track the message boundaries
Async Socket Code
Serializing Data
over a Network
Different people have different styles for serializing data.
Picture Hunt has hand-made code for serialization.
Each Command that has extra header fields has to
implement “InitHeader()” to write them out into a
BinaryWriter.
Data is read in with the CommandReader class (which
also handles message boundaries). CommandReader
has to know about every class and every opcode; this is
written by hand
‘htonl’ converts host to native format (this lets my
program work on any system)
Async Socket Code
Closing
Close your socket when you’re done:
closingSocket.Shutdown(SocketShutdown.Both);
closingSocket.Close();
closingSocket = null;
Network Status Style:
Discrete Network Status
Network
Status
Indicator
Picture Hunt uses a Discrete
Network Status: a little icon of
the network conditions with
no text and absolutely no
dialog boxes
The Discrete style is often a good choice. Other choices are
• Per-task dialog: show multiple activities related to a task (e.g., downloading)
• User-selected dialog: similar, but is shown only when user asks. Is appropriate
when networking isn’t a primary part of the user’s work
• UI replacement: when offline, switch interface. Is appropriate when the app has
no off-line capabilities (which is rare)
Network Status States
Unstarted
• Goes to “Started” when the user clicks “Connected”; program is told to
StartNetworkConnection()
Started
• Goes to “OK” if a connection can be made
• Goes to “FailNl” if a connection cannot be made
Ok
• Goes to “Fail” if a socket read fails to read
Fail
• Sets a timer to switch to “Retry”
Retry
• The program is told to StartNeworkConnection()
• Goes to “Ok” if the connection works and “Fail” if it does not
FailNl
• Sets a timer to switch into “RetryNl”
RetryNl
• The program is told to StartNetworkConnection()
• Goes to “Ok” if the connection works and “FailNl” if it does not
Automatic Connection Retry
People move their laptop computers a lot more than they used to. Your
program has to handle Network Transitions smoothly:
• Always retry on failures
• Use the network status (and not an emergency dialog)
Transitions are common: the user doesn’t need a big reminder
Adjust the retry times when
there’s no connection
• At start: retry every 2 seconds
• Then every 15 seconds
• Later, retry every hour
Why? The game doesn’t want to overuse the
network. There’s a validation routine to make
sure the table of adjustments is reasonable.
All this is in NetworkStatus.cs – the GetRetryTime() and the associated
RetryTime list and DelayTime class. There is also a validation routine.
And one more thing:
Network Transitions
You should also detect NetworkAddressChanged and proactively set up a new socket.
• New socket usable? Use it and drop to the old one
• New socket not usable? Drop it and keep the old one.
Just be careful when you’ve got two sockets open at once.
The NetworkAddressChanged event is pretty chatty;
you’ll need to “de-bounce” it with a small timer.
With a discrete network status style, you don’t tell the
user that you’re even trying it.
Hints: debugging new connections
• Quick test (laptop): start with Wi-Fi and no Ethernet
and run the game. Then plug in Ethernet, wait five
seconds, and turn off your WiFi. The game should
already be connected to Ethernet.
• I made life easier for myself by adding in a “New
Network” button for debugging. Every time I clicked it,
it would try a new network.
• I also added a server command to reject all new players
– this helps validate that the code is robust. The game
should accept the new connection failure and keep on
using the old one. Not all new connections work!
LINQ to simplify coding
The game demo uses LINQ in several places to make the code
easier to write and understand.
For example, when the user can asks for a hint for an area to
click; the following game server code finds an area to show:
Area a = (from area in areas where area.HitBy
== null && area.Name == CurrLookFor select
area).DefaultIfEmpty(null).FirstOrDefault();
The resulting single Area is now either a good candidate to
return, or is null.
Reading a LINQ book really helped me get started.
Data Binding to Update Score
Data Binding is a way to
automatically link your data
to a UI element
When you update your data,
the UI is updated, too!
Can work the other way as
well (UIļƒ Data)
Data Binding Code
public class PlayerScore
{
public int index { get; set; }
public string name { get; set; }
public int score { get; set; }
}
public ObservableCollection<PlayerScore>
playerScores { get; set; }
Data Binding Code (part 2)
About that code…
1. Everything needs to be public
2. Everything needs to be a property
3. PlayerScore does not implement
INotifyPropertyChange, so only clear+replace will
show up (e.g., can’t take an existing score and change
it; have to remove all and add back in)
I couldn’t have done it without Petzold’s Windows Phone
book (free download; got to
http://www.charlespetzold.com/phone/ )
XAML (UI) part of Data Binding
<ListBox Name="playerScoreList" ItemsSource="{Binding Path=playerScores}"
Width="207" Height="190" Opacity="0.8" IsHitTestVisible="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding name}" FontSize="20" />
<TextBlock Grid.Column="1" Text=" " FontSize="20" />
<TextBlock Grid.Column="2" Text="{Binding score}" FontSize="20" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
Storyboard Controlled Animation
Adding animation was easy and made a big
improvement in the game
Add <Storyboard> items in the XAML; set the
TargetName to the thing to animate and the
TargetProperty to the property to animate
(usually opacity or position)
In the game code, call ___Storyboard.Begin() to
trigger the animation
Storyboard Controlled Animation
Animations include:
• The title
• The instructions
• The player list
• The “look for” (both new words and when
there’s a winner)
• The winner text
MSDN was enough to make it work
Download