I'm working on a multiplayer Flash game (ActionScript 3) with an accompanying server written in Python and I'd like some tips regarding networking, and specifically, the handling of packets and implementation of such a system on both the client and server.
At this moment, I am using bytes to identify packets (e.g, 0x00 is login request, 0x02 is login response), sending and reading data in the order specified by my protocol. This is fine, and I am okay with this, but I'd like to clean up the code that handles this system because it seems redundant and unintuitive to use. I'm asking for experienced network programmers, specifically game developers, to answer this question as I lack experience in this field.
Right now, the packet system is made of two main components: The Networker
and the PacketParser
. The Networker
's job is to determine when a packet begins and then passes the packet to the PacketParser
which will read the packet contents determined by the identification byte, and return an associative array of the data, which is then handled by the Networker
, for example, an Entity's position is updated.
When sending a packet, the user would send an associative array of data to the Networker
, which is then sent to the socket through the PacketParser
, and this code is rather ugly and ridiculous. Here is a sample in Python, packetparser.py:
def sendPacket(data, socket):
byters = "" # Buffer - will be sent at once
if data["Type"] == "LoginReply":
byters = unpacker.sendUnsignedByte(byters, 1)
byters = unpacker.sendString(byters, data["Message"])
if data["Type"] == "Message":
byters = unpacker.sendUnsignedByte(byters, 2)
byters = unpacker.sendString(byters, data["Message"])
if data["Type"] == "Tick":
byters = unpacker.sendUnsignedByte(byters, 3)
if data["Type"] == "TankInfo":
byters = unpacker.sendUnsignedByte(byters, 4)
name = data["Tank"].getName()
velocity = data["Tank"].velocity
baserot = data["Tank"].baserot
x = data["Tank"].x
y = data["Tank"].y
byters = unpacker.sendString(byters, name)
byters = unpacker.sendInt(byters, velocity)
byters = unpacker.sendInt(byters, baserot)
byters = unpacker.sendInt(byters, x)
byters = unpacker.sendInt(byters, y)
As you can see, this is rather ridiculous, and repetitive.
So, I now would like to smoothen this system out. My initial idea is to create a Packet
interface and for every packet I'd like to handle, I would implement the interface with the hardcoded data, so I can do something like this:
networker.sendPacket(new LoginRequestPacket("username", "password"));
This is rather nice to the eye and nice to use, but is it the best?
Also, this works fine for sending packets, but what about receiving packets? Perhaps the Networker
will have to do this?
ident = socket.readByte();
if (ident == 0x01)
{
pack = new LoginRequestPacket();
pack.read(socket);
}
Also, once packets have been received, where in the codebase should game logic be handled (relating to the packet received)? Right now, it's handled directly in Networker
but this seems self-defeating - the Networker
for network-related stuff, not game logic! That's handled in the main Game
class! So, should I set up/use some sort of event system, for example, onLoginResponse()
or onPositionUpdate
? Or maybe, to make the Packet
classes more useful, logic should be handled directly in there?
Or maybe even queuing the packets somewhere, for both input and output? Would that work?
These are just things I've thought of on the top of my head, I have no idea on what the pros and cons of these suggestions are and I'd like someone to outline them.
I've had such little experience in this that I really want to hear how you have implemented such a system, to give me a baseline to develop off. Please tell me what I'm doing wrong, what I'm doing right, and what should be done better.
Thanks!
UPDATE
I didn't say so before, but right now, the client is using non-blocking sockets and the server is using blocking sockets, spawning a new thread per client. After some research it looks like the common consensus is to avoid blocking sockets for a game that expects >10 clients (source), so I will be redesigning the serverside codebase to use non-blocking sockets, but this still leaves the issue of where to handle the logic and how Packet
s are architecturally designed. Please advise me!