|See what's going on with flipcode!|
Network Game Programming - Issue 05 - Watch your Language
by Dan Royer (28 July 1999)
|Return to The Archives|
Now that we've got a socket open and we're all set, we're ready to transmit data. While I will do my best to explain the essentials, I would like to point out that everyting from here on is my solution. You may come up with something completely different. If you do, I'd like to hear about it.|
(note the start of new paragraph as I take a deep mental breath)
Sending and receiving data is easily the hardest part of communicating on the internet. One of the advantages of modern networking is that you are guaranteed to receive all the data that was sent in the order it was sent (FIFO: first in, first out) if you use TCP/IP. That's all well and good but there's one little problem: the machine sending the data knows what it's sending but the machine that's receiving the data needs some kind of hint or it will be as usefull as a random string of numbers.
Most of the time the solution is fairly straightforward. For example, an FTP program would send strings such as PWD, CWD, USER, PASS and so on. A typical chat application might send [user id][message length+1][message+terminating null] to and from the server once the users are logged in to a chat room, some special user ids being reserved for non-chat messages such as leave chat room, kick user, etc...
The first thing I did when designing my communication language was step back and try to find something in common with every single message. I found only one thing. Every message is unique in one way or another. Even if the same TYPE of data is sent its USE will be different for each type of message. This also means that some message may be of the same size, only their type will be unique.
What it really breaks down into is this: we need to send messages in some kind of pattern that the server and client have agreed upon. The messages should probably begin with an id code so that the receiving computer knows what the message is and how big it is. So I created
When the machine receiving the data first start reading it in, it reads in a single int and based on the unique id code it knows what kind of data block it is receiving. This enables it to allocate ram to store the block in with a minimum of fuss and no wasted space. Then it fills out the rest of the allocated space with the data left to be read (if any) and we're all done.
There are three major problems left with this system. Firstly, it can happen that lag gets in the way and only half a message is sent or received (the rest arriving with the next FD_READ). Second, we need a place to store the data blocks that have been received but not processed and we need a place to store the messages waiting to be sent. The solution is a new message queue class.
I keep a "to send" linked list and a "being received" linked list for each client (or each socket, depending on how you want to look at it). With each transmission d_transmitted goes up. When d_transmitted equals d_size, the entire message has been transmitted. If cCMQueue is in the list of messages to send and the entire block has been sent then the queue can be destroyed. If the cCMQueue is in the list of received messages and it's message has been downloaded and processed then it too can be destroyed.
The nice thing about cCMQueue is that it can be used without modification in both client and server, so all the cCMQueue management stuff as well as all the read/send stuff can go in cComm. It has one complication, however (remember how I said there were three problems?): the cComm class only knows about one message type, cCMData. If our client and server want to send anything more than a single int they'll need a way for cComm to recognize the type and be able to figure out what size it is...What if we override a cComm base class method? Joy!
So how does all this work together? Well, if we're reading in data it would be a little like this:
And sending data is a very similar process. Lately I've been trying to come up with a method that would avoid allocating/destroying data blocks over and over but I'm not saying any more until I get it working.
So now we've covered everything you'll need to know in order to listen, connect, transmit and disconnect. But what about games?! How does it all fit in with games? WASN'T THAT THE WHOLE POINT!?! @#$%(&!! Calm down, you're gonna bust a vein if you keep that up. Next week we'll cover everything you'd need to know about WHAT to send in order to run a game. Until then take something to lower that blood pressure and get plenty of rest, OK?