|
Torque Network Library Architectural Overview
Torque Network Library Architectural OverviewThe Torque Network Library is built in layers, each adding more functionality to the layer below it.The Platform LayerAt the lowest level, TNL provides a platform independent interface to operating system functions. The platform layer includes functions for querying system time, sleeping the current process and displaying alerts. The platform layer includes wrappers for all of the C standard library functions used in the TNL.The platform layer also contains the Socket and Address classes, which provide a cross-platform, simplified interface to datagram sockets and network addresses. The NetBase LayerThe NetBase layer is the foundation upon which most of the classes in TNL are based. At the root of the class hierarchy is the Object base class. Every subclass of Object is associated with an instance of NetClassRep through a set of macros, allowing for instances of that class to be constructed by a class name string or by an automatically assigned class id.This id-based instantiation allows objects subclassed from NetEvent and NetObject to be serialized into data packets, transmitted, constructed on a remote host and deserialized. Object also has two helper template classes, SafePtr, which provides safe object pointers that become NULL when the referenced object is deleted, and a reference pointer class, RefPtr, that automatically deletes a referenced object when all the references to it are destructed. The BitStream and PacketStream classesThe BitStream class presents a stream interface on top of a buffer of bytes. BitStream provides standard read and write functions for the various TNL basic data types, including integers, characters and floating point values. BitStream also allows fields to be written as bit-fields of specific size. An integer that is always between 3 and 9 inclusive can be written using only 3 bits using the writeRangedU32 method, for example.BitStream huffman encodes string data for additional space savings, and provides methods for compressing 3D points and normals, as routines for writing arbitrary buffers of bits. The PacketStream subclass of BitStream is simply a thin interface that statically allocates space up to the maximum size of a UDP packet. A routine can declare a stack allocated PacketStream instance, write data into it and send it to a remote address with just a few lines of code. The NetInterface and NetConnection LayerThe NetInterface class wraps a platform Socket instance and manages the set of NetConnection instances that are communicating through that socket. NetInterface is manages the two-phase connection initiation process, dispatch of protocol packets to NetConnection objects, and provides a generic interface for subclasses to define and process their own unconnected datagram packets.The NetConnection class implements the connected Notify Protocol layered on UDP. NetConnection manages packet send rates, writes and decodes packet headers, and notifies subclasses when previously sent packets are known to be either received or dropped. NetInterface instances can be set to use a public/private key pair for creating secure connections. In order to prevent attackers from depleting server CPU resources, the NetItnerface issues a cryptographically difficult "client puzzle" to each host attempting to connect to the server. Client puzzles have the property that they can be made arbitrarily difficult for the client to solve, but whose solutions can be checked by the server in a trivial amount of time. This way, when a server is under attack from many connection attempts, it can increase the difficulty of the puzzle it issues to connecting clients. The Event Layer - EventConnection, NetEvent and Remote Procedure CallsThe EventConnection class subclasses NetConnection to provide several different types of data transmission policies. EventConnection uses the NetEvent class to encapsulate event data to be sent to remote hosts. Subclasses of NetEvent are responsible for serializing and deserializing themselves into BitStreams, as well as processing event data in the proper sequence.NetEvent subclasses can use one of three data guarantee policies. They can be declared as GuaranteedOrdered, for ordered, reliable message delivery; Guaranteed, for reliable but possibly out of order delivery, or Unguaranteed, for ordered but not guaranteed messaging. The EventConnection class uses the notify protocol to requeue dropped events for retransmission, and orders the invocations of the events' process methods if ordered processing is requested. Because declaring an individual NetEvent subclass for each type of message and payload to be sent over the network, with its corresponding pack, unpack and process routines, can be somewhat tedious, TNL provides a Remote Procedure Call (RPC) framework. Using some macro magic, argument list parsing and a little assembly language, methods in EventConnection subclasses can be declared as RPC methods. When called from C++, the methods construct an event containing the argument data passed to the function and send it to the remote host. When the event is unpacked and processed, the body of the RPC method implementation is executed. See Also RPC in the Torque Network Library for a more complete description of RPC in the Torque Network Library. Ghosting - GhostConnection and NetObjectThe GhostConnection class subclasses EventConnection in order to provide the most-recent and partial object state data update policies. Instances of the NetObject class and its subclasses can be replicated over a connection from one host to another. The GhostConnection class manages the relationship between the original object, and its "ghost" on the client side of the connection.In order to best utilize the available bandwidth, the GhostConnection attempts to determine which objects are "interesting" to each client - and among those objects, which ones are most important. If an object is interesting to a client it is said to be "in scope" - for example, a visible enemy to a player in a first person shooter would be in scope. Each GhostConnection object maintains a NetObject instance called the scope object - responsible for determining which objects are in scope for that client. Before the GhostConnection writes ghost update information into each packet, it calls the scope object's performScopeQuery method which must determine which objects are "in scope" for that client. Each scoped object that needs to be updated is then prioritized based on the return value from the NetObject::getUpdatePriority() function, which by default returns a constant value. This function can be overridden by NetObject subclasses to take into account such factors as the object's distance from the camera, its velocity perpendicular to the view vector, its orientation relative to the view direction and more. Rather than always sending the full state of the object each time it is updated across the network, the TNL supports only sending portions of the object's state that have changed. To facilitate this, each NetObject can specify up to 32 independent states that can be modified individually. For example, a player object might have a movement state, detailing its position and velocity, a damage state, detailing its damage level and hit locations, and an animation state, signifying what animation, if any, the player is performing. A NetObject can notify the network system when one of its states has changed, allowing the GhostConnections that are ghosting that object to replicate the changed state to the clients for which that object is in scope. Encryption and TNLThe TNL has hooks in various places to use encryption when requested. To enable encrypted connections, the NetInterface must be assigned an instance of AsymmetricKey as a public/private key pair using NetInterface::setPrivateKey. The NetInterface::setRequiresKeyExchange method may then be called to instruct the NetInterface that all incoming connection requests must include a key exchange.Asymmetric keys can be constructed with varying levels of security. The TNL uses Elliptic Curve public key crytpography, with key sizes ranging from 20 to 32 bytes. AsymmetricKey instances can be constructed either with a new, random key of a specified size, or can be created from an existing ByteBuffer. Once a secure connection handshake is completed, TNL uses the AES symmetric cipher to encode connected packets. The BitStream::hashAndEncrypt and BitStream::decryptAndCheckHash methods use an instance of SymmetricCipher to encrypt and decrypt packets using a shared session key. In order to have properly secure communications, the cryptographically strong pseudo-random number generator (PRNG) in TNL must be initialized with good (high entropy) random data. Useful ClassesTNL uses a number of utility classes throughout. Some of these include:Vector - The Vector template class is a lightweight version of the STL Vector container. DataChunker - The DataChunker class performs small, very low overhead memory allocation out of a pool. Individual allocations cannot be freed, however the entire pool can be freed at once. ClassChunker - The ClassChunker template class uses the DataChunker to manage allocation and deallocation of instances of the specific class. ClassChunker maintains a free list for quick allocation and deallocation. StringTableEntry - StringTableEntry wraps a ref-counted element in the StringTable, with simple conversion operations to C strings. StringTableEntry instances can be sent in the parameter lists of RPC methods or using the EventConnection::packStringTableEntry method. ByteBuffer - The ByteBuffer class wraps a buffer of arbitray binary data. ByteBuffer serves as a base class for BitStream, as well as the cryptographic primitives. Debugging and Error HandlingCorrecting bugs in a networked simulation can sometimes be an incredibly difficult task. TNL provides some handy features to make debugging somewhat less challenging:Asserts - The TNLAssert and TNLAssertV macros specify a condition that, if false, will cause the application to display an error dialog and halt in the debugger where the assert was hit. Asserts are very useful for sanity-checking arguments to functions, and making sure network data is being properly read from and written to packets. Logging - TNL has a set of simple but effective facilities for logging status information. The TNLLogMessage and TNLLogMessageV macros allow the user to specify logging tokens with particular log messages. Logging of messages associated with a given token can be enabled or disabled using the TNLLogEnable macro. Also, the TNLLogBlock macro can be used to log multiple logprintf statements in a single test. Rather than log to a file or other destination, TNL applications must declare at least one instance of a sublclass of TNLLogConsumer. Every TNLLogConsumer instance receives all the logging messages that are currently enabled. Debugging object writes into packets - One of the most common errors programmers experience when using TNL is failing to properly match the NetEvent::pack and NetEvent::unpack or NetObject::packUpdate and NetObject::unpackUpdate routines. By default, if TNL_DEBUG is defined, the EventConnection and GhostConnection classes will write extra size information into the packet before each object's data are written. The ConnectionParameters::mDebugObjectSizes flag can be modified directly to further control this behavior. |