Network Game Programming - Issue 06 - Talk about things you like to do...
by Dan Royer (05 August 1999)



Return to The Archives
Last time on "Network a la carte with Dan"...


[ Dan is wearing a white cylindrical hat on his head and a pale blue apron that reads "kiss the coder" ]
Hello, laddles and jellyspoons!
[ Applause and laughter ]
We laughed, we cried, we set up our communication system between client and server. Now let's get COOKING!

Sorry, I couldn't resist. In this article we're going to cover some of the basics messages that would be sent back and forth, including logging in, finding out who else is in the game and more. I'd like to remind you all again that this is MY solution, you may come up with something better/different/obscure/strange/wierd/blood/sugar/sex/majic/whatever and if so I'd like to hear about it.


Logging In


In order for clients to know who a particular message is about every client must be assigned a unique ID code and the clients need to know what those codes are. But they don't need to know until they've logged in successfully, do they? When a client connects to a server...

...In FD_ACCEPT the server (having already accepted the socket) should assign a unique ID code to the client. Fortunately since every client is on a different socket on the server the socket number can be used as the id code. The server should then wait for a communique from the client. ...In FD_CONNECT the client should create a new instance of cClient and then send a login request message. My login request message has the id code MSG_LOGINREQUEST (=0) and contains
  • The client's login name (32 characters)
  • The password (if any) (32 characters)
  • The client application's version (2 ints)
  • Just so we're all on the same page and as per last week's article, client and server DetermineSizeFromType() should return 32+32+sizeof(int)*3. Once the server receives the message in it's entirety it gets processed. The sequence of checks then is as follows:
  • 1. Is the number of players equal to the maximum number of players? (since we haven't incremented the number of players to include this player)
  • 2. Is the client version unsupported by this server?
  • 3. Are the client and the server passwords not matching?
  • 4. Is the user's login name already being used?
  • a cMsgLoginReply structure is filled out at this point.
  • The success flag or reason for being rejected (1 int).
  • The maximum number of players in the game (1 char).
  • The newly approved client's id code (1 int).
  • The cMsgLoginReply structure has the id code MSG_LOGINREPLY (=1) and is immediately followed by the connected clients' login states. When the client receives the cMsgLoginReply it checks the login success flag. If login was approved then it fills the local cClient class with the id code and can now start doing game type things.


    ID codes


    Id codes are useful for more than one reason. Not only do they let the clients (and server) know who has done what without getting confused but they can be used to prevent illegal messages. Think about it: you don't want players that have not passed login approval to send spawn, fire and movement messages, do you? So on the client side you can do a simple test. If the client ID has not been set to a non-zero value then do not allow the player to spawn or send any game-related messages. In fact it would be a good idea to set up server-side security flags so that people cannot send other types of illegal messages of one type while they are trying to do communications of a different type. Remember: clients can't lie about their ID codes because their ID codes have to match their server socket number. If someone does try to mess with the server by sending illegal messages, I just kick 'em out. More on server security in a later article.


    Client login states


    Every client has to know when every other client has entered or left the game, otherwise they won't display the same things, they'll have problems with RAM and all kinds of other horrible things I try not to think about. I made a cMsgConnectNotice (guess what the id code was) that contained the following:
  • The client's id code (1 int).
  • The client's name (32 chars).
  • The client's new connection state (connected, connecting, disconnecting) (1 int).
  • When a new client passes login approval it is sent one cMsgConnectNotice for every login approved client in the game and every one of those login approved clients gets a cMsgConnectNotice about the new client. When a client disconnects the disconnecting client receives nothing and every remaining client gets a cMsgConnectNotice.

    "But wait," you say, "Why only one char for the maximum number of players in the game? And why send the disconnecting client's name to the remaining clients when they don't need it?! You could save loads of bits and reduce the server network load!" All of these are truths and all of these are good things, I won't argue it. However: if I did every optimization now, what would I talk about in the next article? (:


    Game Action


    Oh, finally some of the good stuff. Or rather, the better stuff. It's getting better all the tiiiIIIiiime... The real question here isn't WHAT to send so much as HOW MUCH. If you could send a constant, uninterupted stream of what you were doing and you knew it was getting to the client or server instantaneously then there'd be no problem, you'd always be in synch. Unfortunately this kind of data would crush today's networks so instead we send a certain number of packets per second. Since everybody seems to be watching the development of Quake III at the moment, let's match their transmissions at 20 "snapshots" per second. A snapshot is a picture of every RELEVANT object in the world that might affect the player. Sent to every player. 20 times a second. We'll want to keep snapshots to an absolute minimum.

    The client is another story. The client receives 20 snapshots a second but doesn't send nearly that much data. Best of all the client isn't really expected to send anything at regular intervals (although it's a good idea as well see later). So really the client's job is easy: wait for the user to do something or change the world state in some way (fire a rocket, change acceleration, change rotation, etc...) and report it when it happens. Given that we're expecting a new snapshot to every client every 50 milliseconds we know that the longest delay that should normally occur is ...50 milliseconds. Plenty of time for a computer but so small the human mind has trouble registering it. It is my (unconfirmed) suspicion that the plasma rifle in Quake III's first Win32 test had an incredibly long fire delay because it was waiting for some kind of confirmation from the server, sort of like "I want to shoot!" "ok, but not until I tell EVERYBODY about it." What was that old Tom Lehrer song? Yes we'll all go together when we go...

    Speaking of which, ever notice in Quake II that there would be major lag problems if two or three people started shooting hyperblasters in a large open area at the same time? Ever wondered why sometimes rockets would "jump" along their paths instead of flying nice and smooth? Apparently the rockets and hyperblaster shots were being updated by the server every snapshot. Then sometime between Quake II and Quake III somebody said "hey, why not let the client handle as much of this stuff as possible and take a load off the server?" to which I hope Carmack said something along the lines of "well, DUH." I'm not trying to bash Carmack or Quake here, I'm just saying that a) Sometimes your first answer isn't your best and b) unload as much work as you can onto the clients, it will smooth gameplay and reduce the ammount of network traffic leading to bigger, fancier, more enjoyable games.




    I'm sure you can dream up a structure to send to the server when the player has a change of state and I'm sure you can design your own snapshot struct (hint: use the number of clients and DetermineSizeFromType()) so I leave it for you to work on until next time when I'll show you my method and how I set it up to be as lag friendly and "jump" free as possible.


    Article Series:
  • Network Game Programming - Issue 01 - Things that make you go "hmm..."
  • Network Game Programming - Issue 02 - cComm one, cComm all
  • Network Game Programming - Issue 03 - cClient. cClient run
  • Network Game Programming - Issue 04 - cServer? I barely know her!
  • Network Game Programming - Issue 05 - Watch your Language
  • Network Game Programming - Issue 06 - Talk about things you like to do...
  • Network Game Programming - Issue 07 - I bent my Wookie...
  •  

    Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
    Please read our Terms, Conditions, and Privacy information.