PrismServer is a complete solution for adding chat and other general purpose multi-user messaging to your .NET applications. The concepts of a chat-enabled application, like creating and entering chat rooms, sending and receiving chat messages, and management of user profiles, are abstracted by
PrismServer and made available through simple properties, methods, and events. This code submission consists of the following projects:
SCG.Prism - A .NET class library assembly that contains components and classes that encapsulate
PrismServer. Important components and classes are:
PrismConnection component - Allows a client application to connect to and communicate with a
PrismServer. Provides properties to specify the host address and port number of the server, methods to initiate communications, and events to respond to various actions such as incoming chat, the arrival of a new user into a chat room, or a message from the administrator.
PrismServer component - Encapsulates the actual server side of
PrismServer. Allows multiple incoming clients to connect and communicate through sockets. This component is the basis of any
PrismServer server application, and provides properties and events so the server's user interface (if any) can remain up to date.
PrismUser class - Represents a single individual who is logged into a
PrismServer. This class is used to represent users in the client and the server application.
PrismRoom class - Represents a "chat room" that can contain one or more
PrismUsers who are communicating among themselves.
PrismServerAdmin application - A Windows Forms application that is a fully functional
PrismServer server. Provides a user interface so the server operator can see who is connected, and monitor activity and performance history.
ChatNDraw application - A sample Windows Forms client application. Users can connect to a
PrismServer, create and enter chat rooms, chat with other users, and use a shared drawing blackboard.
ChatNDraw, a sample PrismServer client application
A screen shot of the PrismServer Administrator's console application
PrismServer is the multi-user engine for the Windows based strategy games produced by Silicon Commander Games ("SCG"). SCG has been producing Windows games since the mid 1990s, and several titles allow multiple players to coordinate, create games, and play over the Internet in real-time. SCG needed a general purpose set of components to provide the multi-player feature, and
PrismServer was the result.
Originally written as a set of Delphi components, this new version embraces the .NET platform, and the C# programming language. New versions of SCG's popular strategy games are under development, and will use the .NET based
PrismServer to provide multi-player capability.
Writing a Client Application
To enable a client application to connect to and communicate via a
PrismServer, use the
PrismConnection component. In a WinForms application, you can simply drop this component onto the application's form and set the properties in design-time.
PrismConnection publishes three properties which must be set prior to connecting:
Host - Specify the domain name or the IP address of the server machine where
PrismServer is running.
Port - Specify the port number that the
PrismServer is configured to use on the server machine.
SubjectName - The concept of "Subject Name" provides the ability for multiple client applications to utilize a single
PrismServer, and only receive the messages that were originated from the same client.
PrismServer will route messages only to connected clients that share the same Subject Name. You should provide a
string value that corresponds to the name of your application.
Once the properties above are set, set the
Active property to
true to attempt to establish a connection. This action will block the application until a connection is made, or an exception is thrown.
Once a connection is established, the next step is to "log in" to the
PrismServer using a user name/password. The
PrismConnection component offers two methods that can be used to log in to the server:
LoginNew. These allow users to log in to the server using a pre-registered user name and password, or as a completely new user, respectively. Your client's user interface should provide paths so that the user can initiate each type of log in.
Login to log into the server using a previously registered user name and password. This call is non-blocking.
PrismConnection will respond via triggering either the
LoginOK event, or the
LoginError event. Possible errors are invalid password, and user name not found in the server's registry.
LoginNew to log into the server with a new user name. This method takes as a parameter an instance of a
PrismUser object, which contains the user information for the new user. The component library contains a
PrismUserInfoDialog dialog box component that can be used to quickly create a
PrismUser instance. See the
ChatNDraw demo client application to see this component in action. The call is non-blocking, and
PrismConnection will respond by triggering either the
LoginOK event or the
LoginError event. Possible errors include the specified user name already exists, or an invalid user name specified.
In your client application, you can always retrieve the instance of the local
PrismUser that is connected by referencing the
connection.Host = _frmLogin.txtHost.Text;
connection.Port = (int)_frmLogin.numPort.Value;
connection.Active = true;
catch (Exception error)
MessageBox.Show(error.Message, "PrismServer Connection Error");
Each client connected to a
PrismServer occupies a single
PrismRoom, and can chat and interact only with other clients within the same room. Your client application can see all of the
PrismRooms that have been created that share its Subject Name.
PrismConnection component notifies the client of newly created
PrismRooms via the
RoomAdded event. You should expect to receive a series of these events immediately after a successful log-in, one for each
PrismRoom that currently exists on the server. The client should represent the rooms in the user interface, using a
ListView, or similar control. When a
PrismRoom is removed,
PrismConnection notifies the client via a
PrismConnection also notifies the client when users enter and leave
PrismRooms. This is accomplished via the
After successful log in, the client is placed into a default room, or lobby.
PrismConnection notifies that client that it has entered a new room via the
JoinedRoom event. Note that
JoinedRoom is triggered, in addition to an accompanying
UserAddedToRoom event; the
UserAddedToRoom containing the
PrismUser object that represents the client that is actually connected in the local application (the same instance as the
To join a different
PrismRoom, call the
EnterRoom method. To create a new
PrismRoom, call the
CreateRoom method. These calls are non-blocking, but do not place you into the new room immediately. After calling one of the methods, you should expect to receive several events,
UserLeftRoom (indicating that you have left your current room),
User<code>AddedToRoom (indicating that you have entered the new room), and finally
JoinedRoom. Also, if you are the last user to leave a room,
PrismServer will destroy the room (unless it is the default lobby) and
PrismConnection will trigger the
RoomRemoved event. If you create a new room, you will receive the
RoomAdded event prior to
PrismRooms for Multi-Player Games
CreateRoom method has two parameters, the desired room name and the maximum number of participants that the room can hold (specify
0 to indicate no maximum number). If you pass a number greater than zero,
PrismConnection will trigger a
StartSignal event when the indicated number of participants have entered the room, and the server will lock the room so no other clients can enter it. This feature is designed to allow multi-player games to kick off once the specified number of players have entered the game room.
Chat and Data Messages
When you are in a
PrismRoom, you can exchange chat and data messages with the other clients in the room. To send a
string of chat text, call the
SendChat method. When a
string of chat text is received from other clients in the room (note that this excludes the local client),
PrismConnection triggers the
"Data Messages" are also
strings that can be sent to clients in the room, and are handled in a similar way to chat
strings. The idea behind Data Messages is to provide a facility for client applications to pass application-specific, non-chat, data to clients in the room. Call
SendData to send a Data Message, and respond to incoming Data Messages by handling the
Included is the complete source code for a simple client application,
ChatNDraw. This sample client allows users to connect to a
PrismServer, create and join chat rooms, chat with other users, and draw on a shared blackboard. Some points of interest:
ChatNDraw uses Data Messages to communicate strokes that have been drawn on the board, as well as the action of clearing the board and changing the color or the pen.
ChatNDraw allows the user to view and modify user information when a user is double clicked in the
PrismUser can be displayed using the
PrismUserInfoDialog dialog box component that is included in the source code. The double click event handler code compares the
PrismUser that was double clicked to
ThisUser, and if equal, assigns the dialog to an editable state. If user info in the dialog has been modified, it is saved back to the server by calling the
SaveUserInfo method. The server ultimately responds and
PrismConnection fires a
UserInfoChanged event letting you (and any other clients in the room) know that the user information has changed.
ChatNDraw allows users to see current
PrismServer statistics, by calling the
ServerStats method; these are passed back from the server via the
ServerStatsReceived event in the form of a
Writing a Server Application
PrismServer component is the basis of a
PrismServer server application. It encapsulates the multi-threaded server socket, management of Subject Names,
PrismUsers, and handles routing of messages to connected clients. The
PrismServer components offers a number of properties to control aspects of the server's behavior:
Port - Indicates the port number that the server will listen for connections on
LobbyName - The name of the default
PrismRoom that new clients are added to
ProhibitSameIP - If
true, prohibits multiple connections from the same IP address
ProhibitSameUserName - If
true, prohibits multiple log ins using the same user name
PingInterval - Controls how often the server pings clients (in seconds) - clients that do not respond to pings are considered timed out and disconnected
Implementation - Must be set to an instance of a component that derives from the abstract
PrismServerImplementation (see below)
PrismServer component also offers some methods that allow the operator to interact with the server and connected clients, as well as a series of events. Server applications should respond to these events to update their user interfaces to reflect the modified information (new clients connecting, rooms added or removed, etc.) See the included
PrismServerAdmin application for a sample server application that performs these actions.
User Management in PrismServer
PrismServer component exposes an
Implementation property that must be assigned to a component that derives from the virtual
PrismServerImplementation provides an interface for user management. You can derive a new component from
PrismServerImplementation to allow the server to use a local file, a database, or any other storage mechanism desired to manage user information. This storage mechanism which is defined by the derived class will be referred to subsequently as the "user registry", this does not correspond to the Windows registry. Included in the package is a simple concrete descendant that you can use out of the box:
PrismServerFileImplementation. This component stores user information in simple binary files on the local file system.
PrismServerImplementation component contains the following methods that should be overridden to produce a working user management implementation:
bool UserExists(string userName) - Return whether the specified user name exists in the user registry.
bool IsPasswordValid(string userName, string password) - Is the specified password correct for the user?
void PopulateUserInfo(PrismUser user) - The passed
PrismUser object contains a valid
UserName property only, you need to look up and populate the remaining properties from your registry.
bool CheckUserName(string userName, ref string msg) - Return whether or not the specified user name is valid for your registry, and if not describe why in the
bool CheckPassword(string password, ref string msg) - Return whether the specified
password is valid in your security scheme, and if not, describe why in the
void StoreUserInfo(PrismUser user) - Store the property values for the specified
PrismUser in your registry.
bool CheckRoom(string roomName, int maxUsers, ref string msg) - Return whether the parameters of a
PrismRoom are valid, and if not explain why in the
void SaveSettings() - Persist the current server settings (if any) somewhere.
void LoadSettings() - Load the persisted server settings (if any).
void Initialize() - Perform one time initialization.
void ProcessCustomCommand(string commandName, string commandParams) - Provides a mechanism to process custom server commands. A client application can call
CustomCommand method to send a custom command to the server. A custom command consists of a command name (
string), and parameters (
PrismServer component can likewise pass custom commands to clients using its
CustomCommand method. Both
PrismConnection components provide
CustomCommandRecieved events to handle receipt of custom commands. This architecture allows a greater degree of flexibility and customization to clients and servers.
Under the Hood
Both the client (
PrismConnection component) and server (
PrismServer component) sides of
PrismServer use a utility class called
PrismNetworkStream to manage reading and writing data from the connected socket.
PrismNetworkStream is responsible for reading and writing the raw bytes from and to the underlying socket. Internally,
PrismServer uses a text-based transport protocol and passes information among clients as
string message is composed of a length header, followed by a pipe (
|) delimiter, then the remainder of the message. For example, a message containing the text "
My Message" would flow through the socket as the following
Built upon this simple transport protocol,
PrismServer uses its own internal protocol for messaging. Each message begins with a "type" qualifier, followed by zero to many parameters. For example, when the client sends a line of chat text to the server (the
SendChat method), the following command is sent to the transport protocol (which appends the message length, then sends it to the server socket),
CHAT|This is my chat message!
29|CHAT|This is my chat message!
When the server receives the message, it propagates it to the remainder of the clients that are in the same
PrismRoom. However, it needs to append the user name of the client that typed in the chat message so this can be communicated to the other clients. The server responds by sending the following back to other clients in the room:
CHAT|UserName|This is my chat message!
38|CHAT|This is my chat message!
PrismConnection component receives this message, it ultimately fires the
ChatMessageReceived event, with two parameters indicating the
PrismUser (based on the
UserName in the
string) and the message text itself.
PrismServer protocol is briefly outlined in the PrismProtocol.txt file included in the download.
Thread Safety Issues
Both primary components,
PrismConnection (for client side applications) and
PrismServer (for server applications) make heavy use of .NET events to communicate actions to their host applications. For example,
PrismConnection fires a
RoomAdded event when a new chat room was created by someone who is logged in under the same Subject Name. It is tempting to simply provide an event handler for this event and add the room name to a
ListBox on the main form's user interface. However, the events that are fired occur in the context of a different thread. Trying to update a GDI+ user interface element in a different thread will result in an
You should ensure that any code that updates user interface elements occurs in the main UI thread. The
PrismServerAdmin application tackles this by maintaining several Queues that the event handlers add items to (in a thread-safe manner, via a
lock statement). On the main form, a
Timer component continuously runs (on the main UI thread), and pulls the items out of the Queues and into the UI elements.
ChatNDraw takes a different approach. It uses a short-cut by setting
false, bypassing this restriction. This action is typically not recommended, but since
ChatNDraw's UI is only ever modified by the single external thread in the
PrismConnection component, it was deemed a safe approach in this case. If there is ever any chance of UI elements being changed by different threads, using this flag is not recommended, and the calls should be routed through delegates using the
In the 5.1 release, I have beefed up the thread safety of the
PrismServer admin console, after I discovered some flaws in the initial release. As a general rule of thumb, I should also point users to the
Invoke method of the
Control class in the .NET Framework. If you need to call a method that interacts with UI controls from a different thread, you should use the
Invoke method - check the .NET Framework docs for details.
18th July, 2007
6th May, 2008
- Article updated
- Noteworthy enhancements to 5.1:
- Improved overall thread safety in the server.
- Added a new component,
GameCoordinator. This component can be used in the client to synchronize orders and state changes in a real-time multiplayer game. I am using this for my current game project, Solar Vengeance 5.0.
- Improved many server side functions, allowing for more expansion capabilities. For example, you can now more easily derive a new
Form class from
ServerForm, and access many of the underlying UI elements and methods.
- Added a
CustomCommand method to the
PrismGuest class, and a
CustomCommandReceived event in
PrismServer. These correspond to the same methods in the client, in
PrismConnection. They allow you to define special custom commands that are handled in your own custom server, and custom commands that can be sent to connected clients.
I will maintain the official source code release for
PrismServer on the Silicon Commander Games Web site. Noteworthy enhancements and fixes that are contributed will be rolled into the source code as version releases.
I am interested in the following types of contributions:
- .NET Framework Upgrade - I soon plan to upgrade
PrismServer to version 3.5 of the .NET Framework.
- Functional enhancements - New areas of functionality can be added to the core PrismServer and PrismConnection classes, and by logical extension the
PrismServer protocol itself. Some possible extensions might include peer to peer binary file sharing, the ability to block or restrict certain users of IP address ranges, and more robust administration features.
- New user registry Implementations - New components derived from
PrismServerImplementation could allow for user information to be stored in databases, XML files, or other mechanisms.