OFGame Network Design
From OpenFrag
Contents |
Features
- Version checking
- Delta compression
- Supports several players and servers on same IP
- 32 Maximum
- Latency prediction
- Asynchronous methods
- Each object will keep status information for a single synchronous method call at a time.
Security Model
Server side security model with client side additional security as a complement.
Key Exchange
Interface
This will reside in the openfrag::network namespace.
All methods return a bool, they will return true if the method succeeds, otherwise if not.
Data Structures
enum Chat
AllChat = OF_MAX_PLAYERS, TeamChat
enum Protocol
P00 = OF_BASE_PROTOCOL, P01, P02, P03, P04, P05, P06, P07, P08, P09, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22, P23, P24, P25, P26, P27, P28, P29, P30, P31
enum Type
LocalHub, RemoteHub, LocalServer, RemoteServer, LocalClient, RemoteClient
enum Team
Observer, Team_A, Team_B
struct Moves
bool Forward, Left, Right, Backwards;
struct PlayerInformation
std::string name;
unsigned char id;
float pitch, yaw, speed, x, y, z;
Team team;
struct WorldInformation
PlayerInformation players[OF_MAX_PLAYERS];
unsigned int nplayers;
float localX, localY, localZ;
std::list<std::string> msg;
struct FullServerInformation
unsigned short servernamelen, mapnamelen;
unsigned char servername[OF_SERVER_NAME_SIZE], mapname[OF_MAX_MAPNAME_SIZE];
rsa_key pk;
struct Connection
unsigned int ip;
unsigned char protocol;
struct ClientChoices
Team team;
Hub
bool start(Common::Type const type);
Starts the Hub synchronously. When start() returns the Hub is ready to receive queries.
type: Specifies the type of Hub to create, local or remote. This just changes the name of a file containing it's IP. This file in case of a remote Hub should be distributed with the Client and Server, so they know where to connect to. In case of a local Hub, this file would have to be copied to each local Client and Server, this isn't very intuitive, so in the future the Hub should just be discovered through IP scanning or broadcast.
bool stop(); [Async] [WIP]
Stops the Hub asynchronously. When stop() returns the Hub will probably still be running. Check the Hub status with isRunning().
bool isRunning();
Returns true if the Hub is running, otherwise if not.
Server
bool start(char const* const server-name, unsigned int const server-name-length, Common::Type const type, Common::Protocol const protocol);
Starts the Server synchronously. When start() returns the Server is ready to receive queries, though it might still not be registered. Check of the Server is already registered with isRegistered().
server-name: The server name.
server-name-length: The length of the server name, if it's longer then OF_SERVER_NAME_SIZE, it will be cut short.
type: Specifies the type of Server to create. This just specifies the name of the file from which to get the Hub's IP.
protocol: Specifies the protocol the Server should use. This is important when there are several Servers, Clients or even an Hub running on the same machine, it differentiates them.
bool stop(); [Async] [WIP]
Stops the Server asynchronously, if the Server is registered, it will unregistered it. When stop() returns the Server will probably still be running. Check the Server status with isRunning().
bool isRunning();
Returns true if the Server is running, otherwise if not.
bool isRegistered();
Returns true if the Server is registered on the Hub, otherwise if not.
bool isDone();
This is a general purpose method that will return true when the last asynchronous method called is completed. For instance, it returns true after a call to stop() is finished.
Client
bool start(Common::Type const type, Common::Protocol const protocol);
Starts the Client synchronously.
When start() returns the Client is ready to send queries.
type: Specifies the type of Client to create.
This just specifies the name of the file from which to get the Hub's IP.
protocol: Specifies the protocol the Server should use. This is important when there are several Servers, Clients or even an Hub running on the same machine, it differentiates them.
bool stop(); [Async]
Stops the Client asynchronously, if the Client is connected, it will disconnect it.
When stop() returns the Client will probably still be running.
Check the Client status with isDone() and isSuccess().
bool abort(); [Async]
This is a general purpose method that will abort the last asynchronous method called. For instance, it can abort a updateServerList().
bool updateServerList(); [Async] [Abort]
Updates the list of Servers currently registered from the Hub asynchronously.
This method can be aborted using the abort() method.
bool getServerList(std::list<Client::ServerInfo>* const serverList);
Outputs the list of Servers previously retrived from the Hub into an hash_map.
serverList: The variable where the list of servers will be output to.
bool connect(Common::Connection const con);
Connects the Client to a Server.
con: The ip and protocol the Client should connect to.
bool disconnect(); [Async]
Disconnects the Client from the Server if it's connected. If the Client isn't connected to a Server the isSuccess() will return false.
bool sendClientChoices(Common::ClientChoices const* const cc); [Async]
After the Client is connected it can send specific choices to the Server. For instance, which team to select.
cc: The choices to send to the Server.
bool move(Common::Moves const moves);
After the Client is connected, the Client can spawn a player and start sending the Server it's movements.
moves: A series of boolean variables that represent the key state of each key, true if they are pressed false otherwise.
bool look(float const pitch, float const yaw);
After the Client is connected, the Client can spawn a player and start sending the Server it's orientation.
pitch: The angle the player is looking at, up-down.
yaw: The angle the player is looking at, left-right.
bool say(unsigned char const id, std::string const msg);
bool say(Common::Chat const id, std::string const msg);
This send text messages to the other players.
The Client needs to be running and connected, those are the only restrictions right now, so players in observer mode will be able to talk to everyone else, I'll change that in the future.
id: The target the message is aimed at, this can be a player id or a Common::Chat id.
msg: The message to be send.
bool getWorldInfo(Common::WorldInformation* const wi);
As soon as the client connects with a Server, even before sending it's choices, it will start receiving world updates. The player can be in observer mode and still see what's going on.
wi: World Information.
IMPORTANT: The model for the local player should only be drawn on the local coordinates in the World Information structure, the input engine should not handle the positioning of the local players' model directly.
bool isConnected();
Returns true if the Client is connected to a Server, false otherwise.
bool isRunning();
Returns true if the Client is running, false otherwise.
bool isDone();
This is a general purpose method that will return true when the last asynchronous method called is completed. For instance, it returns true after a call to getServerList() is finished.
bool isSuccess();
This is a general purpose method that will return true if the last asynchronous method called returned successfully. Just because isDone() returns true doesn't mean the call was done successfully.
Examples
Hub Example
In this example we create a Hub. It's ready to receive server registrations and client queries with just these lines.
#include "hub.h"
...
net::Hub testHub;
if(!testHub.start(net::Common::LocalHub))
{
std::cerr << "Hub::start() failed." << std::endl;
return false;
}
Server Example
In this example we create a Server. The Server will try to register itself on a local Hub, this will be changed later since local Servers will be discovered through broadcasting instead. Registration isn't mandatory, if it fails the server will still run. This example assumes the presence of the files "localHubIPFile.dat" and "localHubPublicKeyFile.dat", the first has the Hub's IP and second has the Hub's public key, those files are created by a Hub object.
#include "server.h"
...
net::Server testServer;
unsigned int i;
if(!testServer.start("name", 4, net::Common::LocalServer, net::Common::P00))
{
std::cerr << "Server::start() failed." << std::endl;
return false;
}
std::cerr << "Server Started." << std::endl;
for(i = 0; i < OF_CLIENT_RETRY; ++i)
{
if(!testServer.isRegistered())
{
Sleep(OF_CLIENT_SLEEP);
}
else
{
std::cerr << "Server registered." << std::endl;
break;
}
}
Client Example
In this example we create a Client that tries to get a server list from a Hub, if successful tries to connect to the first Server on that list, and if successful sends to the server the team which it wishes to join. This example also assumes the presence of the files "localHubIPFile.dat" and "localHubPublicKeyFile.dat", the first has the Hub's IP and second has the Hub's public key, those files are created by a Hub object.
#include "client.h"
...
net::Client Client;
if(!Client.start(net::Common::LocalClient, net::Common::P01))
{
std::cerr << "Client" << "::start() failed." << std::endl;
return false;
}
std::cerr << "Client" << " Started." << std::endl;
if(Client.updateServerList())
{
while(!Client.isDone())
{
std::cerr << "Client" << " Sleep..." << std::endl;
Sleep(OF_CLIENT_SLEEP);
}
if(!Client.isSuccess())
{
std::cerr << "Client" << "::updateServerList() failed." << std::endl;
return false;
}
}
else
{
std::cerr << "Client" << "::getServerList() failed." << std::endl;
}
std::cerr << "Client" << "::updateServerList() done." << std::endl;
std::list<net::Client::ServerList> fsi;
Client.getServerList(&fsi);
if(Client.connect(fsi.connection))
{
while(!Client.isDone())
{
std::cerr << "Client" << " Sleep..." << std::endl;
Sleep(OF_CLIENT_SLEEP);
}
if(!Client.isSuccess())
{
std::cerr << "Client" << "::connect() failed." << std::endl;
return false;
}
}
else
{
std::cerr << "Client" << "::connect() failed." << std::endl;
}
std::cerr << "Client" << "::connect() done." << std::endl;
net::Common::ClientChoices cc;
cc.team = netCommon::Team_A;
if(Client.sendClientChoices(&cc))
{
while(!Client.isDone())
{
std::cerr << "Client" << " Sleep..." << std::endl;
Sleep(OF_CLIENT_SLEEP);
}
if(!Client.isSuccess())
{
std::cerr << "Client" << "::sendClientChoices() failed." << std::endl;
return false;
}
}
else
{
std::cerr << "Client" << "::sendClientChoices() failed." << std::endl;
}
std::cerr << "Client" << "::sendClientChoices() done." << std::endl;
...
/*<Game Loop>*/
Client.move(move, pitch, yaw);
Client.getWorldInfo(&wi);
/*Draw local player on wi.localX/Y/Z*/
/*The local player doesn't need the network engine to tell him where to look*/
for(i = 0; i < wi.nplayers; ++i)
{
/*Draw the players on wi.players[i].x/y/z, looking at wi.players[i].pitch/yaw*/
}
/*</Game Loop>*/
TODO
Common
- Make support for modems configurable
- IP Filter
- Administrator authentication
- Remote administrator authentication
- Transfer maps, sounds, models and MOTD
- Implement bandwidth throttling
- Implement serial numbers
- Low priority
- Fix error messages
- Change version with restart
- Recompilation is needed as it stands
- Configurable input rate
- More enums, less #defines
- Have to remove Iterator from ClientConnectionInformation struct
- Implement Nodes
- Implement stop() methods
- Use portable code, specifically threading
Hub
- Send more then one datagram at a time per client, receive more then one ACK per datagram.
- Save server list to a file
- Remove offline servers from server list
- Receive Server remove request
- Remember to check if removing that server will invalidade any iterators
- Remove stagnated connections
- Throtle the amount of client requests
Server
- Send Server remove to Hub
- Low priority
- Send anti-cheat program
- Throttle the amount of client requests
- Use map list to cycle maps
- Limit amount of players
- Preallocate resources for player connections
Client
- Request Server information about it(server ping, player number/frags/ping, mod, map name)
- Cancel that request if necessary
- Store favorites
- Low priority
- Receive anti-cheat program
Strange Behavior
- The client starts receiving updates as soon as it establishes a connection with the server. At the time the methods commandChoices() and recvMapInfo() run, the server will still be sending updates, and when those methods call the function recvfrom(), instead of the packets pertinent to those methods, they might instead receive updates. Those packets are ignored by the client, and it will later request that they be resent, but the client will still output an error message to std::cerr.
Error messages:
"Bad packet: Wrong type"
"Bad packet: Bad length"
- There is a chance a server might send a packet authenticated with MAC key A, when the client already switched to MAC key B. Those packets are ignored by the client, and it will later request that they be resent, but the client will still output an error message to std::cerr.
Error message:
"Common::checkMAC() failed:"
- The server doesn't allow the client to send packets timed into the future, since the server keeps all packets on a circular queue, new packets inappropriately timed would overwrite previous ones. That by itself is not a problem, they'll get overwritten eventually, but if they are prematurely overwritten, another client that needs a past packet will get incoherent data. Every once in a while the server might slow down it's packet sending routine, because of high traffic, excessive CPU usage, etc. When this happens client packets that are properly timed, might appear to the server like they are timed into the future. Those packets are ignored by the server, and the client will later resend them, but the server will still output an error message to std::cerr.
Error message:
"Bad packet: Wrong update"
Dependencies
- Windows 2000+
- WinSock
- ws2_32.lib
- LibTomCrypt by Tom St Denis
- libgcc.a
- libtfm.a
- libtomcrypt.a
- WinSock
- Linux/BSD/Mac
- Untested


