|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThe Genesis UDP project is a class library that implements a lightweight UDP server and client using the .NET sockets functionality. It uses UDP to keep the amount of data being sent across the network low, and has many features such as basic encryption, sequenced packets, and a reliable channel. Genesis communicates via "command packets" - a command packet is one or more UDP packets that have a 2 byte opcode, and a variable number of string fields. Unreliable packets can be up to 512 bytes, reliable packets can be longer but are split up by Genesis and sent in sequence. There are a few internal opcodes used by Genesis, but apart form that, how packets, opcodes and fields are handled is totally the responsibility of the host application developer. There is also an optional encryption system, it is not too advanced but if enabled, will generate a random 320 bit key for each connecting client and use that key in an XOR encryption algorithm. This is quite secure as no two clients share the same key, however it relies on the initial connection packets not being sniffed. Adding public/private key encryption etc. is a possible enhancement to the library. It is possible to be selective over which connections are encrypted and which are not. Servers vs. ClientsGenesis works on the basis that every instance of Genesis can be both a client and a server. The line between the client and the server is blurred, as any application that uses Genesis can both connect to servers and accept connections from clients. Of course, clients can't just connect by default - the appropriate events must be hooked in the host application to enable the functionality. A server is defined as the remote host that accepted the connection, whilst a client is the remote host that initiated the connection. As an instance of Genesis can do both, it can be both a server and client to other Genesis instances. Servers and clients are known collectively simply as hosts.
The diagram above shows how the idea of clients and servers works in Genesis. Each blue box represents an instance of the Genesis library - none are specifically designated servers or clients as both can potentially accept and initiate connections. The definition of server and client is only valid in the context of a single Genesis instance. Let us consider "Genesis 1", it is connected to servers 2 and 3, and 4 is connected as a client, this is determined by the directions of the arrows (the arrows represent which box initiated the connection). If we look at the perspective of "Genesis 2", we see there are just two clients, 1 and 3. However, if we look at the perspective of "Genesis 4", there is one server, "Genesis 1". Notice how "Genesis 1" can be both a client or server depending upon the context. BackgroundThe idea for Genesis was based on looking at the network protocols of the various online FPS games such as Quake and Half-Life, which use UDP to allow fast communication between the game server and its connected clients. These implementations have the option to send reliable and sequenced packets - abilities which are also incorporated into Genesis. The original intention for Genesis was to be the start of an underlying network API for a game engine written in .NET, however its potential is so great it has been given to the community as a standalone project. Using the codeThere are three interfaces that must be used to implement the Genesis functionality. These are:
The Included with the source are two projects "GenesisChatServer" and "GenesisChatClient". These are a pair of projects that implement a chat system similar to IRC. Using these projects as a reference point should help with understanding the Genesis classes. Creating A ServerLet's look at the GenesisChatServer - this project shows how Genesis can act as a server to serve other instances of Genesis (the clients). First, we need to declare and instantiate the Genesis object in the host application. private GenesisCore.IGenesisUDP m_UDP;
...
m_UDP = GenesisCore.InterfaceFactory.CreateGenesisUDP("ChatServer");
Notice how the class The string[] addresses = m_UDP.GetLocalAddresses( );
...
In order to handle the Genesis communication events, they must be hooked as per the code below: //Hook genesis core events
m_UDP.OnListenStateChanged += new ListenHandler(m_UDP_OnListenStateChanged);
m_UDP.OnConnectionAuth += new ConnectionAuthHandler(m_UDP_OnConnectionAuth);
m_UDP.OnCommandReceived += new IncomingCommandHandler(m_UDP_OnCommandReceived);
m_UDP.OnConnectionStateChanged += new
ConnectionStateChangeHandler(m_UDP_OnConnectionStateChanged);
private void OnConnectionAuth(object o, ConnectionAuthEventArgs e)
{
...
if(e.AuthCommand.Fields[1].Length < 3)
{
e.AllowConnection = false;
e.DisallowReason = "Nickname too short.";
return;
}
else if(e.AuthCommand.Fields[1].Length > 15)
{
e.AllowConnection = false;
e.DisallowReason = "Nickname too long.";
return;
}
...
}
Creating A ClientLet's look now at the client side of the chat project, "GenesisChatClient". This is similar to the server application in that it instantiates an instance of Genesis and hooks up various events, however some new events are hooked: m_UDP.OnListenStateChanged += new ListenHandler(m_UDP_OnListenStateChanged);
m_UDP.OnLoginRequested += new SendLoginHandler(m_UDP_OnLoginRequested);
m_UDP.OnAuthFeedback += new AuthenticatedHandler(m_UDP_OnAuthFeedback);
m_UDP.OnConnectionStateChanged += new
ConnectionStateChangeHandler(m_UDP_OnConnectionStateChanged);
m_UDP.OnCommandReceived += new IncomingCommandHandler(m_UDP_OnCommandReceived);
m_UDP.OnConnectionAuth += new ConnectionAuthHandler(m_UDP_OnConnectionAuth);
m_UDP.OnSocketError += new SocketErrorHandler(m_UDP_OnSocketError);
m_UDP.OnConnectionRequestTimedOut += new
RequestTimedOutHandler(m_UDP_OnConnectionRequestTimedOut);
private void OnLoginRequested(object o, LoginSendEventArgs e)
{
if(e.Connected)
{
e.ServerConnection.SendUnreliableCommand(0,
GenesisConsts.OPCODE_LOGINDETAILS,
new string[] {txtServerPW.Text, txtNickName.Text} );
spState.Text = "Sending login details...";
}
else
{
spState.Text = "Connection rejected - " + e.Reason;
}
}
The
private void m_UDP_OnConnectionAuth(object o, ConnectionAuthEventArgs e)
{
//Clients don't accept connections.
e.AllowConnection = false;
e.DisallowReason = "Can't connect directly to a chat client";
}
One thing of importance is how connections are established from the client. It starts in the chat client with the following code: /// <summary>
/// Connect to server button was clicked
/// </summary>
private void btnConnect_Click(object sender, System.EventArgs e)
{
server_ip = txtServerIP.Text;
m_UDP.RequestConnect(ref server_ip,
Convert.ToInt32(txtServerPort.Text),
out server_req_id);
spState.Text = "Connecting...";
btnConnect.Enabled = true;
}
The Note that establishing a connection is an asynchronous operation, and to catch whether a connection has succeeded requires the use of two events, Sending Data to Remote HostsGenesis wraps this functionality in very easy to use method. Two of the methods reside in the int SendUnreliableCommand(byte flags, string opcode, string[] fields);
int SendReliableCommand(byte flags, string opcode, string[] fields);
These two methods allow sending a single command packet to the remote host associated with the //Command packet flags
public static byte FLAGS_NONE = 0;
public static byte FLAGS_CONNECTIONLESS = 1;
public static byte FLAGS_ENCRYPTED = 2;
public static byte FLAGS_COMPOUNDPIECE = 4;
public static byte FLAGS_COMPOUNDEND = 8;
public static byte FLAGS_RELIABLE = 16;
public static byte FLAGS_SEQUENCED = 32;
Of the flags available in the Constants.cs file, only one should ever be used manually in the method calls, and that is It is possible to broadcast a command packet to multiple remote hosts using the following methods in the int SendUnreliableCommandToAll(BroadcastFilter filter,
byte flags, string opcode, string[] fields);
int SendReliableCommandToAll(BroadcastFilter filter,
byte flags, string opcode, string[] fields);
These methods work exactly the same as the first two, except they take broadcast filter flags, which are defined in Constants.cs as: [Flags]
public enum BroadcastFilter : int
{
None = 0, //Filter out everything
Servers = 1, //Send to servers we are connected to.
Clients = 2, //Send to clients connected to us.
All = Servers | Clients,
//Send to both servers
//and clients (every connection).
AuthedOnly = 4,//Only send to authed clients or servers we are authed with.
}
This allows the broadcast to be limited to servers, clients, and optionally only those connections that have successfully been authenticated. Points of InterestIt is important to note that the events in Genesis are thrown on a separate thread - not the UI thread. This means that the When the The only method within Genesis that can take a hostname instead of an IP is History
|
||||||||||||||||||||||