3
\$\begingroup\$

When working on network packet structure for games, what is more efficient (in terms of code structure) for packet reading for client/server?

Currently, Our packet structure is to send the packet ID as an Int32, which is read when it is coming into the server. But to minimize the amount of packet ID's we use, instead of having a (for example) LoginRequest AND a LoginResponse, we use just a Login packet. The data is structured differently whether it is the server, or client sending the packet.

Should we use an individual Packet for each Request/Response? Or is there a better way?

EDIT:

And by data being different for whether it is the client or server sending, it is different in terms of complete packet structure. So while it still has the same ID, it uses entirely different data types.

Example (Using a Lobby Request/Lobby Response as one packet)
Client Request

  • Packet Id (Int32) (Lets say ID = 5)
  • LobbyName (String)
  • LobbyPassword (String)
  • GameLobby (Boolean)

Server Response

  • Packet Id (Int32) (still ID = 5)
  • LobbyName (String)
  • GameLobby (Boolean)
  • MemberCount (Int32)
\$\endgroup\$
1
  • \$\begingroup\$ Ultima Online used this and byte for a packet Id (and even though they run out, they used two packets with packet subids). And based on game you are writing a lot of stuff wont even have a response or request. \$\endgroup\$
    – Kikaimaru
    Commented Jan 11, 2013 at 11:28

6 Answers 6

3
\$\begingroup\$

When your client->server packet #5 has a different structure than your server->client packet #5, you are already separating them. A 5S packet (the i-want-to-join-a-lobby-request) has an entirely different meaning than a 5C packet (this-is-the-lobby-you-are-in-message).

When you keep using this structure, you will soon encounter the problem that not every server message has an equivalent client message and vice versa. There will be messages which will only be sent by one party, because there is no reason for a reaction from the other side. And there will be messages to which the other party can respond to with many different messages which would all warrant an own message type.

For that reason I would recommend you to decouple client-sided message type IDs and server-sided message type IDs.

\$\endgroup\$
1
  • \$\begingroup\$ This pretty much confirms what I was thinking. Of course "not every server message has an equivalent client message and vice versa", but for the ones that do, is coupling packets ok? \$\endgroup\$
    – ZeunO8
    Commented Jan 11, 2013 at 9:55
3
\$\begingroup\$

First note: If you are using an Int32 for packet ids you are not going to run out. Even using a ushort is still a lot of packet ids. How many ids are you going to need?

Usually with packets you want to send the least amount of data possible. So sharing a Login packet would probably send unneeded data. You could argue that the Login packet is just used once at logon...so you are not going to have any performance problems.

\$\endgroup\$
1
  • \$\begingroup\$ +1 for stating we won't need Int32 for packet id's. I guess it was just something we went with. Probably should change it down the road. But an extra 3 bytes won't really change much for now. \$\endgroup\$
    – ZeunO8
    Commented Jan 11, 2013 at 10:55
1
\$\begingroup\$

As Luis mentioned, you're unlikely to run out of ID's for your packets, it might be useful to have the highest bit of your packet ID represent if it is a request or response.

If you can ensure requests are responded to in-order, you could then also have an expected response buffer at each site, and more easily match incoming responses with their requests. In this way, you wouldnt even need an ID for responses.

\$\endgroup\$
1
\$\begingroup\$

Use 1 ID per message type. Trying to save IDs by using 1 ID for 2 different messages based on the direction of travel is just extra confusion for no gain. Even if you only used an 8-bit int for message IDs, you'd still have 256 available.

\$\endgroup\$
1
\$\begingroup\$

There are times where it makes sense to use some level of coupling between the server and client side exchange of data, particularly in a conversation. But generally I've usually followed the notion that the underlining numeric representation has no relation between one another.

As an example:

  • 0x01C7 = CMSG_WHOIS_REQUEST
  • 0X09FE = SMSG_WHOIS_RESPONSE

While reading over the code source, the use of the names makes it easy to understand the context of its use without having to follow the exchange from start to finish. It's very descriptive and helps when debugging something that isn't working as you expected.

We generally keep the numeric representation entirely agnostic of the exchange of data they represent. We did this on purpose to allow us to randomize the packet ids each build as a means to deter outsiders from being able to reverse engineer the protocol as easily. It naturally doesn't prevent reverse engineering, but does slow their process tremendously :P.

As others have eluded, I really don't see any purpose in having any coupling between their numeric representations. You typically wouldn't be using the numeric value anyway in the code and would be using some defined enumeration key or defined constant variable in its place for long-term maintence ease.

\$\endgroup\$
-1
\$\begingroup\$

I would split up the Packets over multiple libraries (dll's, jar's or whatever you are using)

Since you use the same packet for both directions, when you want to change a packet to contain specific data client -> server communication, you also have to test the server -> client again.

You'd have to (unit)test your entire communication every time you update a package and you have the chance to introduce new bugs on every update you do.

When you split the packages whenever you update the client -> server message, you are sure the other messages are not affected.

Also, I would not use ID's to identify different Packets, I would do this based on classname.

For example, the packet that is send when a client connects to the game I would call ClientWantsToConnectPacket. On the server side, I would handle each packet based on the classname.

This way, your client server connection starts to look a bit more like a ServiceBus solution then raw socket connections.

\$\endgroup\$
10
  • \$\begingroup\$ When you say I would not use ID's to identify different Packets, I would do this based on classname, I assume you mean use a String to identify packets rather some some form of number id. Why do this? Would it not be easier to user some Client/Server side enum which does that for you? \$\endgroup\$
    – ZeunO8
    Commented Jan 11, 2013 at 10:51
  • 4
    \$\begingroup\$ -1 for the classname comment. Strings are much larger to send than IDs, and tend to encourage unsafe deserialisation practices as well. \$\endgroup\$
    – Kylotan
    Commented Jan 11, 2013 at 11:01
  • \$\begingroup\$ In .net and java i've created a system that can automaticly serialize and deserialize classes to JSON and send and recieve them through sockets. This is done through reflection based on the classname send in the message. This way, all I need to do to be able to send new data is define a class for the message (i.e. HelloMessage), and a handler(HelloMessageHandler) that implements a generic interface (IHandleMessages<HelloMessage>). The rest is automated and all that I have to do next is writing the logic in the handler (i.e. adding a new player to the game etc). \$\endgroup\$
    – Thomas
    Commented Jan 11, 2013 at 11:10
  • \$\begingroup\$ Note this works well cross platform: In the .net server I create a message, on the java client I create a copy of the message (needed for deserialization) and a handler and all works on its own. \$\endgroup\$
    – Thomas
    Commented Jan 11, 2013 at 11:11
  • 2
    \$\begingroup\$ @Thomas:... or just give them proper names and assign them ID numbers of 1, 2, etc. The ID is sent in the message header and that is precisely how you identify the message based on a few bytes. \$\endgroup\$
    – Kylotan
    Commented Jan 11, 2013 at 11:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .