|
You may already know that, when I manage to find a few seconds of spare time every once in a while, I am working on a game called Ulysses Agenda. This game is basically a combination of the Windows Communication Foundation, Windows Presentation Foundation, and even some Windows Workflow Foundation (hopefully). In order to build the network engine, I decided to get the smallest unit of functionality running that I could so I decided to build just the chat channel.
WCF is all about the interfaces - everything is driven by the interfaces, especially peer-to-peer WCF applications. Here's a look at my IChat interface:
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;namespace UlyssesAgenda.NetworkLibrary
{
[ServiceContract(CallbackContract=typeof(IChat))]
public interface IChat
{
[OperationContract(IsOneWay = true)]
void SendChatMessage(ChatMessage chatMessage);[OperationContract(IsOneWay = true)]
void Join(NetworkPlayer p);[OperationContract(IsOneWay = true)]
void Leave(NetworkPlayer p);
}
public interface IChatChannel : IChat, IClientChannel
{
}[DataContract]
public class ChatMessage
{
[DataMember]
public string messageBody;[DataMember]
public NetworkPlayer originator;[DataMember]
public TargetScope destination;
}
}
This interface is in a library all on its own called UlyssesAgenda.NetworkLibrary. This library is going to contain all of the shared interfaces and data contract structs required for network communications. I've got a WPF application called UlyssesAgenda.GameServer and a WPF application called UlyssesAgenda.GameClient - pretty straightforward. One technique that I'm using to test these applications is that I have switched them from WPF applications to console applications. The WPF main window still shows up - but I also get an attached console window - giving me a lot of troubleshooting flexibility - specifically - I get to have a visible trace of network messages on non-debuggable machines (like my wife's PC... *ahem* not that I would ever *cough* use her machine to test *cough* networking code... )
GameClient's App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint
name="ChatEndpoint"
address="net.p2p://ulyssesagenda/chat"
binding="netPeerTcpBinding"
bindingConfiguration="PeerBinding"
contract="UlyssesAgenda.NetworkLibrary.IChat"/>
</client>
<bindings>
<netPeerTcpBinding>
<binding
name="PeerBinding"
port="8092"
maxReceivedMessageSize="2147483647" >
<security mode="None">
</security>
</binding>
</netPeerTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
What we're looking at here is that the Game Client is going to listen for IChat messages on net.p2p://ulyssesagenda/chat on port 8092. If you're running on Vista, you will be prompted to unlock this port. And here's the App.config file for the UniverseServer:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint
name="ChatEndpoint"
address="net.p2p://ulyssesagenda/chat"
binding="netPeerTcpBinding"
bindingConfiguration="PeerBinding"
contract="UlyssesAgenda.NetworkLibrary.IChat"/>
</client>
<bindings>
<netPeerTcpBinding>
<binding
name="PeerBinding"
port="8091"
maxReceivedMessageSize="2147483647" >
<security mode="None">
</security>
</binding>
</netPeerTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
The only noticeable difference ( so far ), is that the UniverseServer is running on a different port. This allows you to run a game client and a Universe Server on the same box, either for testing purposes or because you want to be able to play the game you're hosting. Finally, let's take a look at the code in the App.xaml.cs file that connects to the peer mesh:
using System;
using System.Windows;
using System.Data;
using System.Xml;
using System.Configuration;
using System.ServiceModel;
using UlyssesAgenda.NetworkLibrary;
namespace UlyssesAgenda.UniverseServer
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : System.Windows.Application
{
private DuplexChannelFactory<IChatChannel> _chatChannelFactory;
private InstanceContext _site;
private NetLibImplementations.Chat _chat;
private IChatChannel _chatProxy;
private NetworkPlayer _me;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Console.WriteLine("Universe Server Starting Up...");_me = new NetworkPlayer();
_me.PlayerName = "UniverseServer";
_chat = new UlyssesAgenda.UniverseServer.NetLibImplementations.Chat();
_site = new InstanceContext(_chat);
_chatChannelFactory = new DuplexChannelFactory<IChatChannel>(_site, "ChatEndpoint");
_chatProxy = (IChatChannel)_chatChannelFactory.CreateChannel();
Console.WriteLine("Created Chat Proxy Channel.");
_chatProxy.Open();
Console.WriteLine("Chat Proxy Started...");
}}
}
The code is nearly identical for the game client, except the game client harness actually sends some chat messages. In theory, the server will only send broadcast type messages that might indicate that the game is shutting down, whereas clients will be sending messages to players, guilds, galaxies, starports, and more:
_chatProxy.Join(_me);
ChatMessage message = new ChatMessage();
message.originator = _me;
message.destination = new TargetScope();
message.messageBody = "Hello from the game client.";
_chatProxy.SendChatMessage(message);
Now take a look at a screenshot of the game client and game server running simultaneously on my Vista laptop:
