Tutorial Simple Chat - Server Code

<Previous Overview><Next Client Code>
PixiServer setup
The idea is to create an instance of the PixiServer class, add an ApplicationDomain and then add a single Zone to the Application Domain.
  1. Start by creating a new Console Application and name it PixChatServer.
  2. Add a reference to the Pixi.Server.dll by right-clicking the References folder in the Solution Explorer and browsing to where the Pixi.server.dll is located.
  3. Add the following two using statements to the top of the class:
using Pixi.Server; 
using Pixi.Server.DataStructures;
  1. Add the following code to the Main method to create our instance of the PixiServer, start the server running and then stop the server when we are through:
        static void Main(string[] args)
        {
            //Create a new instance of the PixiServer class
            PixiServer pixiServer = new PixiServer();

            //Start the server
            pixiServer.StartServer("192.168.1.114", 11000, 11001);

            //Wait till the user hits a key and then exit the program
            Console.ReadKey();

            //Remember to stop the server
            pixiServer.StopServer();
        }
Common Code
The Server and Client will require some common code in order to communicate properly. We need a message class called Chat with two message types of ChatSend and ChatReply. We will also need two constants to identify our ApplicationDomaintID and ZoneID.
  1. Add a Class Library project to our solution named PixiChat.Common.
  2. Add a class to the project named ChatConstants.
  3. Add the following code so that the class looks like this:
    public class ChatConstants
    {
        public const short ApplicationDomainID = 0;
        public const short ZoneID = 1;
    }
  1. Add a second class and name it GameMessageClass. Change the 'class' identifier to enum and make the enum type a byte. Add a single member named ChatMessage so that it looks like the following:
    public enum GameMessageClass : byte
    {
        ChatMessage
    }
  1. Finally, add a third class and name it GameMessageType. Change the 'class' identifier to enum and make the enum type a short. Add two members named ChatSend and ChatReply so that it looks like the following:
    public enum GameMessageType : short
    {
        ChatSend,
        ChatReply
    }
Application Domain and Zone
Our chat server needs an ApplicationDomain to function properly. The ApplicationDomain needs at least one Zone.
  1. Add a reference to the PixiChat.Common project we created previously and add the following using statement:
using PixiChat.Common;
  1. Add the following lines of code to the Main method right after the the creation of the pixiServer object:
            //Create a new instance of the PixiServer class
            PixiServer pixiServer = new PixiServer();

            //Add and application domain and a zone
            IApplicationDomain appDomain = pixiServer.AddApplicationDomain(ChatConstants.ApplicationDomainID,
                "PixiChatDomain", SimulationStorageType.Quadtree);

            appDomain.AddZone(ChatConstants.ZoneID, "Zone1", 0, 0, 5000, 5000);
StateDynamicEntity
In order for clients to interact with one another, they will need to be added to the zone. We will accomplish this by creating a Player class that is derived from the StateDynamicEntity class.
  1. Add a new project to our solution that is a Class Library and name it PixiChatServer.Game.
  2. Add a reference to System.Drawing.
  3. Add references to the Pixi.Server.dll, Pixi.Math.dll, Pixi.Network.dll, Pixi.Logging.dll and Lidgren.Network.dll by right-clicking the References folder in the Solution Explorer and browsing to where the those libraries are located.
  4. Create a new class and name it Player.cs.
  5. Add the following using statements to the top of the player class:
using System.Drawing;

using Pixi.Server;
using Pixi.Server.DataStructures;
using Pixi.Server.Network;
using Pixi.Logging;
using Pixi.Math;
  1. We need to derive our player class from the StateDynamicEntity class so add the following to the class definition and override the Update method:
    public class Player : StateDynamicEntity
    {
        public override List<ServerMessage> Update(IApplicationDomain applicationDomain, ILogger logger)
        {
            throw new NotImplementedException();
        }
    }
  1. We will need some data to create the StateDynamicEntity so add the following constructor to our player class:
        public Player(int stateObjectId, string name, byte stateObjectType, 
            Vector3F originalPosition, Vector3F heading, short zoneId, 
            RectangleF size)
            : base(stateObjectId, name, stateObjectType, originalPosition, heading, 
                zoneId, size)
        {
        }
  1. Now we need to add a minimum amount of code to the update method:
        public override List<ServerMessage> Update(IApplicationDomain applicationDomain, ILogger logger)
        {
            //Create a list of ServerMessages to return
            List<ServerMessage> returnMessages = new List<ServerMessage>();

            return returnMessages;
        }
Event Handlers
We will create event handlers to handle the Login event and the Chat event. Event handlers are classes that we create and implement the IServerEventHandler interface.
  1. Add a new class to the PixiChatServer.Game project and name it LoginHandler. The LoginHandler class will handle the system login event so that we can add players to our zone. Add the following using statements to the top of the class:
using Pixi.Server;
using Pixi.Server.DataStructures;
using Pixi.Server.Network;
using Pixi.Logging;
using Pixi.Network;
using Pixi.Math;
using PixiChat.Common;
using Lidgren.Network;
  1. Next implement the IServerEventHandler interface. We tell PixiServer that we want to handle the system login message by implementing the MessageClassHandled and MessageTypeIDHandled properties as follows:
        public byte MessageClassHandled
        {
            get { return (byte)SystemMessageClass.System; }
        }

        public short MessageTypeIDHandled
        {
            get { return (short)SystemMessageType.LoginRequest; }
        }
  1. Next we add code to the HandleMessage method. In this handler we need to get the UserName from the payload of the message. Next we set the ClientInfo username field to this value. We can now create an instance of the Player class that we created previously setting the ClientID property to the message.ClientInfo.SessionId. This allows the system to identify the client later. Now add the player to the zone and send a login success message back to the client:
        public void HandleMessage(ServerMessage message, ILogger logger, IApplicationDomain applicationDomain)
        {
            //get the username from the message and update the client information
            string userName = message.Payload.ToString();
            
            //Was this message sent via UDP
            bool isUdp = message.IsSourceChannelUDP;
            message.ClientInfo.SetUserName(userName, isUdp);
            
            //Create a player object
            Player player = new Player(message.ClientInfo.SessionId, userName, 
                ChatConstants.PlayerObjectType, Vector3F.Zero, Vector3F.Zero, 
                ChatConstants.ZoneID, new System.Drawing.RectangleF(0, 0, 10, 10));

            player.ClientId = message.ClientInfo.SessionId;

            //Add the player object to the zone
            applicationDomain.AddStateObject(player);

            //Send a login success message back to the client
            //Get the current network timestamp for the return message
            float now = (float)NetTime.Now;

            ServerMessage successMessage = new ServerMessage(now, 
                (byte)SystemMessageClass.System, (short)SystemMessageType.LoginSuccess);

            successMessage.SourceId = 0;
            
            //SourceID of the message sent in becomes the TargetID on the way back
            successMessage.TargetId = message.SourceId;
            
            //Send using tcp
            successMessage.UseUdp = false;

            //Add the message to the Application Domain to be sent back to the client
            applicationDomain.AddOutgoingMessage(successMessage);
        } }
  1. The next event handler we need is the ChatHandler to handle chat messages. Add a new class to the PixiChatServer.Game project and name it ChatHandler. The ChatHandler class will handle the custom chat event so that we can distribute chat messages to all players logged in. Add the following using statements to the top of the class:
using Pixi.Server;
using Pixi.Server.DataStructures;
using Pixi.Server.Network;
using Pixi.Logging;
using PixiChat.Common;
using Lidgren.Network;
  1. Next implement the IServerEventHandler interface. We tell PixiServer that we want to handle the custom chat message by implementing the MessageClassHandled and MessageIDHandled properties as follows:
         public byte MessageClassHandled
        {
            get { return (byte)GameMessageClass.ChatMessage; }
        }

        public short MessageTypeIDHandled
        {
            get { return (short)GameMessageType.ChatSend; }
        }
  1. As with the LoginHandler we now add code to the HandleMessage method. First we get the chat message from the message.Payload. Next we get a list of players from the applicationDomain and send the chat message to each one:
        public void HandleMessage(ServerMessage message, ILogger logger, IApplicationDomain applicationDomain)
        {
            logger.Debug("Received Chat Message from " + message.ClientInfo.UserName + " message = " + 
                message.Payload.ToString());
            //Get the current timestamp
            float now = (float)NetTime.Now;

            string chatMessage = message.Payload.ToString();

            //Get all players in the game
            List<StateDynamicEntity> entities = applicationDomain.GetStateDynamicEntitiesByType(
                ChatConstants.ZoneID, ChatConstants.PlayerObjectType);

            //Go through each one and send the incoming message.
            foreach (StateDynamicEntity client in entities)
            {
                //if this client was the one who send the message
                if (client.StateObjectId == message.ClientInfo.SessionId)
                    chatMessage = "You said: " + chatMessage;
                else
                    chatMessage = message.ClientInfo.UserName + " said: " + chatMessage;

                //create the servermessage to send to this client
                ServerMessage chatReplyMessage = new ServerMessage(now, 
                    (byte)GameMessageClass.ChatMessage, (short)GameMessageType.ChatReply);
                chatReplyMessage.SourceId = 0;

                //SourceID of the message sent in becomes the TargetID on the way back
                chatReplyMessage.TargetId = client.ClientId;

                //Send back using udp
                chatReplyMessage.UseUdp = true;
                chatReplyMessage.TransportType = NetDeliveryMethod.ReliableOrdered;
                
                //Set the payload
                chatReplyMessage.Payload = chatMessage;

                //Add the message to the Application Domain to be sent back to the client
                applicationDomain.AddOutgoingMessage(chatReplyMessage);

            }
        }


Final Step
Finally, in the PixiChatServer project, make a reference to the PixiChatServer.Game project so that it will compile and get copied to the bin directory. Run the program and you should have a working chat server.

Last edited Feb 3, 2012 at 4:32 AM by pixiserver, version 11

Comments

No comments yet.