Will Perone

I have recently noticed what a vast shortage of information there is on the net on how to do proper game networking. This article is to demystify some of the low level stuff in doing a socket based implementation of game networking.

First off you may ask "Should I use a Peer to Peer or Client/Server model?" Well let me tell you, you are gonna have one heck of a time implementing a Peer to Peer model of networking for a game. This is because you need some way to regulate what the absolute state of the game is at any one point so that everyone playing doesn't get desynced. If you are doing P2P, each client is going to have their own idea of what is currently going on in the game based on the packets they have or have not received from the other clients at any one time. Clients will have different pings to each other and thus each will think the others are in different states than they currently are. Also it is very hard in this model to prevent client side network hacks as there is no authority to report to.

A Client/Server model circumvents all of these problems although sacrifices a little in network bandwidth. This is usually not a problem these days with the advent of broadband internet connections and if it does become a problem, you can always put your server on a T1 or higher. There is still a slight problem with the asynchronicity of current connection speeds though (download is usually a lot faster than upload). So you may want to take that into consideration by having clients upload a minimal amount of data to the server during the game. Having said that, let's get to the down and dirty.


When you implement a networking interface, there will always be a port being used on the connecting computer and one on the computer being connected to. Considering that most people have routers these days (so that they can hook up multiple computers to their internet connection), they will all have natural firewalls. This means you want to minimize, statically allocate and group ports to use when they want to act as a server. When they are a client, things are different. Firewalls block ports that you are receiving on not those you are sending from, so when you connect to a server; your from port can be anything you want, but your destination port better be static. One more thing, if you use a port to do too many things at once on a computer, it tends to get overloaded and just lag everything to hell, so you will want to use 1 port for every client as a server.

EXAMPLE

Server has ports 6000-6032 forwarded to his ip on the router
Server will use 6000-6032 to receive connections from clients
Server can send back to clients from ANY port
Client will send to server FROM any port
Client will send to a STATIC server port
Client will receive stuff from a DYNAMIC server port

SERVER SIDE
Listen for connections on TCP on a static port
Open the same port on UDP and wait for packets
When someone connects on TCP, accept on a new socket and use that socket for communicating with them
When a packet is received on the static UDP port, open port+(client number) up and bind a new socket to communicate with them on that port

CLIENT SIDE
Connect with TCP in the obvious way
No need to bind on UDP
Do not send on udp unless there is something to send!
When you do recvfrom on UDP you will have the port it came from (in sock_addr) but you don't need to do anything with it


You will want to minimize the amount of packets you send back and forth, because every packet will have a extra bytes added onto it in it's header (so that routers and such know where it's going and where it came from). If you are sending a bunch of packets of 1 byte each with a header of 128 bytes, it is not worth it; combine them and send as one packet!

A simple way to allow packet combination and packet identification is to have a simple function like the following:

unsigned char sendbuffer[1024];
unsigned int  sendbufferlength;

bool AddPacket(unsigned char packetid, unsigned char *packet, unsigned char packetlength)
{
     if (sendbufferlength+packetlength+1 > 1024) return false;
     
     sendbuffer[sendbufferlength]= packetid;
     memcpy(sendbuffer+sendbufferlength+1, packet, packetlength);
     sendbufferlength+=packetlength+1;
     return true;
}
Then when you get around to doing your network update, just send the 1 sendbuffer rather than multiple smaller packets. Don't forget to reset sendbufferlength= 0; after sending. Decoding on the other side is just as simple although watchout for packets that have been broken up along the way; do not attempt to interpret a partial packet!

On a final note, don't try to zip compress large packets because the overhead of compressing and uncompressing packets isn't worth it!
No Comments yet, be the first!
<- for private contact