Start Page » Game Development with the Drag[en]gine » Drag[en]gine Network Protocol Specification
The Drag[en]gine Network Protocol (DNP) provides communication support for games using an unreliable communication channel, in general UDP. This includes synchronizing game states (Network States containing Network State Values) and sending messages. Synchronizing network state values is handled internally by the communication modules involved. Sending messages the application can choose if this is done reliable or unreliable.
Network protocols typically use Network Byte Order as defined by the TCP standard which is Big Endian. The message writing and reading of DNP is implemented to match Drag[en]gine File Reader/Writer system. This allows all data written by game scripts to be stored to file, written to memory and transmitted across the network without changing the data. Since this system uses Little Endian DNP uses Little Endian too. Hence throughout this specification Little Endian byte ordering is used unless marked otherwise.
System intending to communicate with Drag[en]gine games can use Drag[en]gine Networking Library to use DNP without implementing the specification outlined here. You need this specification only if you want to implement DNP into your application without this library.
For message writing and reading the same file writer system is used as for file and memory data writing and reading. This ensures any data written in the game engine (no matter if saved to files, persisted to memory or transmitted across the network) is using the same binary format and is interchangeable. You can save data to files and transmit the file data as-is across the network and read it successfully on the other side.
The following data types are supported (names match decBaseFileReader:
Data type | Length | Description |
---|---|---|
Char | 1 byte | Signed 8-bit integer in the range from -128 to 127 inclusive. |
Byte | 1 byte | Unsigned 8-bit integer in the range from 0 to 255 inclusive. |
Short | 2 bytes | Signed 16-bit integer in the range from -32'768 to 32'767 inclusive. |
UShort | 2 bytes | Unsigned 16-bit integer in the range from 0 to 65'535 inclusive. |
Int | 4 bytes | Signed 32-bit integer in the range from -2'147'483'648 to 2'147'483'647 inclusive. |
UInt | 4 bytes | Unsigned 32-bit integer in the range from 0 to 4'294'967'295 inclusive. |
Long | 8 bytes | Signed 64-bit integer in the range from -pow(2,63) to pow(2,63)-1 inclusive. |
ULong | 8 bytes | Unsigned 64-bit integer in the range from 0 to 18'446'744'073'709'551'615 inclusive. |
Float | 4 bytes | 32-bit floating point value. |
Double | 8 bytes | 64-bit floating point value. |
String8 | 1 + length bytes | Unsigned 8-bit value storing the length of UTF-8 encoded string followed by “length” unsigned 8-bit integer characters. String can not be longer than 255 characters (UTF8 encoded). |
String16 | 2 + length bytes | Unsigned 16-bit value storing the length of UTF-8 encoded string followed by “length” unsigned 8-bit integer characters. String can not be longer than 65'535 characters (UTF8 encoded). |
Vector | 12 bytes | 3-Component vector with 32-bit floating point values for each component stored in order X, Y then Z. |
Vector2 | 8 bytes | 2-Component vector with 32-bit floating point values for each component stored in order X then Y. |
Quaternion | 16 bytes | 4-Component quaternion with 32-bit floating point values for each component stored in order X, Y, Z then W. |
Point | 8 bytes | 2-Component point with 32-bit signed integer values for each component stored in order X then Y. |
Point3 | 12 bytes | 3-Component point with 32-bit signed integer values for each component stored in order X, Y then Z. |
DVector | 24 bytes | 3-Component vector with 64-bit floating point values for each component stored in order X, Y then Z. |
Color | 16 bytes | 4-Component color with 32-bit floating point values for each component stored in order R, G, B then A. |
Color3 | 12 bytes | 3-Component color with 32-bit floating point values for each component stored in order R, G then B. |
State values can use more diverse data types to allow network modules to better optimize network usage. These data types are supported for state values:
Data type | Code | Length | Description |
---|---|---|---|
SInt8 | 0 | 1 byte | Signed 8-bit integer in the range from -128 to 127 inclusive. |
UInt8 | 1 | 1 byte | Unsigned 8-bit integer in the range from 0 to 255 inclusive. |
SInt16 | 2 | 2 bytes | Signed 16-bit integer in the range from -32'768 to 32'767 inclusive. |
UInt16 | 3 | 2 bytes | Unsigned 16-bit integer in the range from 0 to 65'535 inclusive. |
SInt32 | 4 | 4 bytes | Signed 32-bit integer in the range from -2'147'483'648 to 2'147'483'647 inclusive. |
UInt32 | 5 | 4 bytes | Unsigned 32-bit integer in the range from 0 to 4'294'967'295 inclusive. |
SInt64 | 6 | 8 bytes | Signed 64-bit integer in the range from -pow(2,63) to pow(2,63)-1 inclusive. |
UInt64 | 7 | 8 bytes | Unsigned 64-bit integer in the range from 0 to 18'446'744'073'709'551'615 inclusive. |
Float16 | 8 | 2 bytes | 16-bit floating point value. |
Float32 | 9 | 4 bytes | 32-bit floating point value. |
Float64 | 10 | 8 bytes | 64-bit floating point value. |
String | 11 | N bytes | UTF8 encoded String of variable length. |
Data | 12 | N bytes | Arbitrary data stored as UInt8 values. |
Point2S8 | 13 | 2 bytes | 2-Component point with signed 8-bit signed integer values for each component stored in order X then Y. |
Point2U8 | 14 | 2 bytes | 2-Component point with unsigned 8-bit signed integer values for each component stored in order X then Y. |
Point2S16 | 15 | 4 bytes | 2-Component point with signed 16-bit signed integer values for each component stored in order X then Y. |
Point2U16 | 16 | 4 bytes | 2-Component point with unsigned 16-bit signed integer values for each component stored in order X then Y. |
Point2S32 | 17 | 8 bytes | 2-Component point with signed 32-bit signed integer values for each component stored in order X then Y. |
Point2U32 | 18 | 8 bytes | 2-Component point with unsigned 32-bit signed integer values for each component stored in order X then Y. |
Point2S64 | 19 | 16 bytes | 2-Component point with signed 64-bit signed integer values for each component stored in order X then Y. |
Point2U64 | 20 | 16 bytes | 2-Component point with unsigned 64-bit signed integer values for each component stored in order X then Y. |
Point3S8 | 21 | 3 bytes | 3-Component point with signed 8-bit signed integer values for each component stored in order X, Y then Z. |
Point3U8 | 22 | 3 bytes | 3-Component point with unsigned 8-bit signed integer values for each component stored in order X, Y then Z. |
Point3S16 | 23 | 6 bytes | 3-Component point with signed 16-bit signed integer values for each component stored in order X, Y then Z. |
Point3U16 | 24 | 6 bytes | 3-Component point with unsigned 16-bit signed integer values for each component stored in order X, Y then Z. |
Point3S32 | 25 | 3 bytes | 3-Component point with signed 32-bit signed integer values for each component stored in order X, Y then Z. |
Point3U32 | 26 | 3 bytes | 3-Component point with unsigned 32-bit signed integer values for each component stored in order X, Y then Z. |
Point3S64 | 27 | 3 bytes | 3-Component point with signed 64-bit signed integer values for each component stored in order X, Y then Z. |
Point3U64 | 28 | 3 bytes | 3-Component point with unsigned 64-bit signed integer values for each component stored in order X, Y then Z. |
Vector2F16 | 29 | 4 bytes | 2-Component vector with 16-bit floating point values for each component stored in order X then Y. |
Vector2F32 | 30 | 8 bytes | 2-Component vector with 32-bit floating point values for each component stored in order X then Y. |
Vector2F64 | 31 | 16 bytes | 2-Component vector with 64-bit floating point values for each component stored in order X then Y. |
Vector3F16 | 32 | 6 bytes | 3-Component vector with 16-bit floating point values for each component stored in order X, Y then Z. |
Vector3F32 | 33 | 12 bytes | 3-Component vector with 32-bit floating point values for each component stored in order X, Y then Z. |
Vector3F64 | 34 | 24 bytes | 3-Component vector with 64-bit floating point values for each component stored in order X, Y then Z. |
QuaternionF16 | 35 | 8 bytes | 4-Component quaternion with 16-bit floating point values for each component stored in order X, Y, Z then W. |
QuaternionF32 | 36 | 16 bytes | 4-Component quaternion with 32-bit floating point values for each component stored in order X, Y, Z then W. |
QuaternionF64 | 37 | 32 bytes | 4-Component quaternion with 64-bit floating point values for each component stored in order X, Y, Z then W. |
Certain commands are reliable. They are guaranteed to be delivered or else the connection is interrupted. In contrary to unreliable commands reliable commands can be delayed for up to ReliableTimeout (recommended 3s). If a reliable command is not acknowledged before ReliableResendInterval (recommended 0.5s) the sender has to resend the same command. This is repeated every ReliableResendInterval until the command is acknowledged or ReliableTimeout is reached.
The receiving side has to ignore duplicate or not expected commands. To achieve this each communication side maintains a NextSendCommand and NextReceiveCommand value for each connection. This value has to be set to 0
upon every accepted connection request. The sender assigns the next NextSendCommand to the command to be reliably send. It then increments NextSendCommand by one. If NextSendCommand reaches 65'536
the value has to wrapped around to 0
.
Upon receiving a reliable command the receiver validates the command number against NextReceiveCommand using these rules:
ReliableWindowSize is 10
and is used to prevent outdated or stray commands being processed.
If the command is not accepted it has to be discarded and processing this command stops.
If CommandNumber equals NextReceiveCommand the command is processed and send to the application since it is the next command expected to arrive. In this case NextReceiveCommand is incremented by1. If NextReceiveCommand reaches 65'536
the value has to be wrapped around to 0
.
If CommandNumber does not equal NextReceiveCommand the command has over-taken the next expected command and has to be held back until all commands in between have been received, processed and send to the application. Whenever a held back command is processed NextReceiveCommand has to be incremented as outlined above.
Every valid received command has to be acknowledged. This tells the sending side the command has been received and no resending is required.
Clients and server can link local network states to the other side. Both client and server can send link requests to the other side. It is up to the application to decide if a link request is accepted or declined. Link requests are handled like Reliable Messages in that they are send reliable and contain a message the application can use to know what kind of state to set up. The receiving application has to set up the state to link exactly the same way as the sending application did. This means the same count of values with the same data types. If this is not the case the receiving client has to decline the state linking. This is in particular the case if the receiving application returns a null state. This is done if the receiving application declines linking a state for whatever reason.
The sender assigns a LinkId to the state to link. This identifier is then used in the future to reference a linked state. Once assigned the LinkId can not be reused until it has been taken down. Multiple clients maintain own links for a local network state. Each connection has potentially a different LinkId. Hence LInkId are not shared across connections although the state they link are.
In addition to the message delivered to the application the command contains the initial value for all values in the network state.
State links can be read write or read only. The sender decided if the receiver is allowed to modify the state or not. Servers usually send read-only states to clients for all entities controlled by the game or other players. The client usually gets a read-write state for his own state he has to modify. The sender has to ignore attempts of clients trying to update states that are read-only to them.
The receiver has to send back a Link Up command if the link is accepted or a Link Down command to reject it. As soon as a Link Down command is send the LinkId in question becomes free again to be used for a new link.
Commands begin with an unsigned 8-bit integer CommandCode indicating the command type. Commands have no explicit length. The command content is the remaining data in the UDP package after the CommandCode. Network clients are required to ignore all commands with unknown CommandCode.
The following commands are supported:
Command Code | Name | Protocol Version |
---|---|---|
0 | Connection Request | DNP1 |
1 | Connection Acknowledge | DNP1 |
2 | Connection Close | DNP1 |
3 | Unreliable Message | DNP1 |
4 | Reliable Message | DNP1 |
5 | Link State | DNP1 |
6 | Link Acknowledge | DNP1 |
7 | Link Up | DNP1 |
8 | Link Down | DNP1 |
9 | Link Update | DNP1 |
Send by the connecting client to connect to a server. This is the only command accepted by the server from an unconnected client. The server accepts the connection if no other connection exists with the same hostname and port combination and no other server specific rules forbid the client from connecting. If the client does not receive a Connect Acknowledge command until Connect Resend Interval (recommended 1s) after sending the last request the command has to be resend. If Connect Timeout (recommended 5s) elapsed since the first request send the client has to stop resending the connect request and report connect timeout error to the user. The server has to ignore connect requests for an already connected client.
The command has this format:
Type | Name | Description |
---|---|---|
Byte | CommandCode | Command code. Is value 0 |
UShort | ProtocolCount | Count of protocols supported by the client. |
UShort[ProtocolCount] | Supported Protocols | List of protocols supported by the client. Each list entry has to be one of these values:
New protocols can be added in the future. Servers have to ignore unknown protocol values. |
Send by the server as answer to the Connect Request send by a connecting client. The server accepts the connection if no other connection exists with the same hostname and port combination and no other server specific rules forbid the client from connecting. The server checks if the client supports at least one protocol the server supports too. If multiple protocols are supported the server selects the best suited protocol, typically the one with the most features. If the server rejects the client it has to forget about the connect request after sending the answer.
The command has this format:
Type | Name | Description |
---|---|---|
Byte | Command Code | Command code. Is value 1 |
Byte | Result Code | Decision of the server. Has to be one of these values:
More result codes can be added in the future. All unknown result codes have to be treated is if ResultCode had been |
UInt16 | Protocol | Protocol selected by the server. This value is only present if Result Code has the value 0 . |
Send by the client before closing connection to server. Send by server before disconnecting client unless this is the client having send a close connection. No acknowledge is send. This command is best effort. If the command is lost client and server have to figure out connection loss using other means.
The command has this format:
Type | Name | Description |
---|---|---|
Byte | CommandCode | Command code. Is value 2 |
Send by client or server or server to client. used to send an unreliable message to the other communication partner. Unreliable messages are used for frequently send messages. They are not acknowledged and thus can be possibly lost. Furthermore they can be delivered to the application in any order. Applications have to use unreliable messages only if the loss of information can be compensated, for example by receiving another update in regular intervals.
Messages should be of short length. No explicit fragmenting of message content is done. This is left for the underlying communication channel. If a message is not fully received it has to be considered lost. The exact message size fitting into one communication channel package depends on the communication channel and is often not detectable. As a rule of thumb on IPv4 a package size of 1200 (conservative 540) can be expected to be transmitted in one package.
The command has this format:
Send by client or server or from server to client. Used to send a reliable message to the other communication partner. Reliable messages are used for infrequent send messages of high importance. They are acknowledged and guaranteed to be received. Furthermore they are guaranteed to be delivered to the application in the order they have been send. Applications have to use reliable messages only if loss of information can not be compensated.
Messages should be of short length. No explicit fragmenting of message content is done. This is left for the underlying communication channel. If a message is not fully received it has to be considered lost. The exact message size fitting into one communication channel package depends on the communication channel and is often not detectable. As a rule of thumb on IPv4 a package size of 1200 (conservative 540) can be expected to be transmitted in one package.
The command has this format:
Type | Name | Description |
---|---|---|
Byte | CommandCode | Command code. Is value 4 |
UInt16 | Number | Command sequence number. See Reliable Communication. |
Byte[*] | Data | Message data. The length of the data is the remaining count of bytes in the UDP package. |
Send by client to server or server to client. Used to link a local network state to the other side. Both client and server can send link requests to the other side. It is up to the application to decide if a link request is accepted or declined.
Messages should be of short length. No explicit fragmenting of message content is done. This is left for the underlying communication channel. If a message is not fully received it has to be considered lost. The exact message size fitting into one communication channel package depends on the communication channel and is often not detectable. As a rule of thumb on IPv4 a package size of 1200 (conservative 540) can be expected to be transmitted in one package. This size is reduced by the count of value data send along the message.
The command has this format:
Type | Name | Description |
---|---|---|
Byte | CommandCode | Command code. Is value 5 |
UInt16 | Number | Command sequence number. See Reliable Communication. |
UInt16 | LinkId | Identifier of link. |
Byte | Flags | Flags type value. Flags have to be values from this list:
|
UInt16 | MessageLength | Length of message in bytes. |
Byte[MessageLength] | Message | Message data. |
UInt16 | ValueCount | Count of values. |
ValueData[ValueCount] | Values | Initial value of all values in the state. |
ValueData has this format:
Type | Name | Description |
---|---|---|
Byte | Type | Data Type of value. |
* | Data | Initial value. The count of bytes required by Data is defined by the Length attribute in State Value Data Type. |
Send by client to server or from server to client. Used to acknowledge receiving a reliable message to the other communication partner.
The command has this format:
Type | Name | Description |
---|---|---|
Byte | CommandCode | Command code. Is value 6 |
UInt16 | Number | Command sequence number. See Reliable Communication. |
Byte | ResultCode | Result of operation. Has to be one of these values:
|
Send by client to server or server to client. Used to accept a Link State command. Only after receiving a Link Up command the client is allowed to send state updates if the state in question is read-write to the client. The sender can not assign LinkId to any other link until sending a Link Down command to free a LinkId.
The command has this format:
Send by client to server or server to client. Used to tear down a Link State. Once torn down the sender can reuse the LinkId for another link. It is recommended to keep assigning new LinkId until the value range is exhausted before reusing LinkId.
The command has this format:
Send by client to server or server to client. Used to update a remote state. Client or server is only allowed to send a state update if it has a read-write state. This is the case if the client or server linked the state to other sides or it received a state with read-write capability.
The command contains one or more values to update which do not have to be of the same state. The sender is not required to update all values with one command send. The sender decides how many values it includes in the command and when it updates the values. In general the sender has to update state changes as fast as possible. The data type range and precision information assigned to values has to be used to send updates only if necessary. For example a floating point value with 0.1 precision should only send an update if the value set by the application deviates more than 0.1 from the last known value. The application sets the precision according to needs. Optionally the sender can update state values if they have recently changed but not more than their precision requires. This can be used as a kind of “keep-alive”. In this case LinkCount is 0
.
If the receives values change enough (according to the same rules as sending them) the application has to be send notification for each changed value.
The command has this format:
Type | Name | Description |
---|---|---|
Byte | CommandCode | Command code. Is value 9 |
UInt16 | LinkCount | Count of links to update. |
LinkData[LinkCount] | Links | Link data |
LinkData has this format:
Type | Name | Description |
---|---|---|
UInt16 | LinkId | Identifier of link. |
UInt16 | ValueCount | Count of values. |
ValueData[ValueCount] | Values | Updated values. |
ValueData has this format:
Type | Name | Description |
---|---|---|
Byte | Type | Data Type of value. |
* | Data | Updated value. The count of bytes required by Data is defined by the Length attribute in State Value Data Type. |