Posted 18 November 2012 - 01:28 AM
Packets are just byte arrays. Serializable is generally terrible, because it adds all kind of reflection and type information that bloats your packets for no good reason. You already know what the bytes mean.
A typical game packet will look something like this, at the byte level (assuming UDP):
[a] [b] -- protocol version bytes / magic header
[c] -- cyclic sequence number of this packet
[d] [e] -- lowest two bytes of timestamp when this packet was sent
[f] -- last sequence number received by the other end
[g] [h] -- lowest two bytes of timestamp provided by the other end for the packet with id [g]
[i] [j] -- duration of processing between receiving packet [f] and sending out this packet [c]
[k] -- source of the packet (player id, or server)
[l] -- destination for the packet (player id, or server)
[m] -- number of messages in this packet
followed by, for each message:
[a] -- size of message (after size, channel, and type)
[b] -- logical channel for message
[c] -- type of message
[[a] bytes] -- payload according to [a]
finally followed by:
[x bytes] -- checksum, CRC, or signature of packet
Each message type [c] maps to a different layout of the packets, and should be interpreted differently.
There is also lots of variation.
Sometimes, the destination isn't included, because the destination is implicit in where you send the packet.
Sometimes, the timing information is not included, if calculating accurate round-trip-time or server-time-offset isn't needed.
Sometimes, there's not multiple logical channels in the stream, so the channel id isn't needed.
Sometimes, payloads bigger than 255 bytes per message is needed, and larger size fields are used.
Sometimes, integers are encoded using variable length (for example, if high bit is set, use the lowest 7 bits, shift left 7 bits, and then attach whatever comes after it, repeat until get a byte with high bit clear.)
Sometimes, more acknowledgement information is included, to support advanced forms of selective re-transmit, packet scheduling, etc.
Sometimes, the destination values are bigger, because you can target any particular object or entity within the system, and there are many of those.
There's a lot more variation, even within commonly accepted best practices. Compression, bit stuffing, implicit length by type, the list goes on. There are also games that don't use best practices, and send data in text form instead of binary, or send one packet per message, or do all kinds of other unnecessary things. However, if you start with this packet structure (again, assuming UDP,) then you're doing fine.
If you're using TCP, you have to additionally prefix each packet with a length field; typically 2 bytes (as you don't want to send more than 64kB of data at a time anyway.) You do not need a "magic" header/signature for TCP, as the protocol version stays the same for the duration of the connection. You may not be as concerned about timing with TCP, because it will deliver everything in order with larger amounts of jitter than you'll see with UDP. You do not need acknowledgement information for TCP, because it takes care of re-transmitting. The list goes on :-)
enum Bool { True, False, FileNotFound };