DirectXPlay:
Client-Server
Author:
Almar Joling
Written: 12th July 2001
Contact: [Web
site] [Email]
Download: DP_Sample.Zip
(34kb)
Contents
of this lesson
1.
What we are going to make
2. Client basics
3. Server basics
1.
What we are going to make
The
game we are going to make is not the type of game
you'd probably love to see. No Subspace, no Quake3.
This game will be much more intense and mind-struggling.
I know you're all wanting such code, but it seems
that multiplayer code is very rarely available,
and if you find out which tricks where used to compensate
lag and packet-loss, you'll have to be killed by
the large companies (At least, that's what I experienced,
except for the killing part). Our game will be Tic
Tac Toe. Yep, I bet you always wanted to play
that online :o)
Now back to the serious business. The game we are
going to make is Tic Tac Toe. Even if you might
find the code of this game simple, it can't be compared
to any code in real-time action games. We don't
care about lag in Tic-Tac-Toe. And we don't have
to care about packet loss because we will send the
data guarenteed anyway, because we don't have to
send a packet every 200ms. I've tried to make this
client-server as easy understandable as possible.
I'm not going to "enumerate" sessions; still haven't
looked how to do it :o). Maybe I'll add that later
anyway.
Our game will simply be built up of command buttons
for the "O", and "X"'s and we'll use a textbox
for our chat function in our game. What's a tic
tac toe game where you can't laugh at your losing
opponent? Exactly. I'm writing this tutorial for
people that have some experience in VB, and have
a bit of an idea what multiplayer gaming is. I've
seen and have been asked by many people to help
in multiplayer programming. And there are lots
of them who can barely use VB. The chance that
the game will be finished is minimal. Read the
note below, and re-read it a few times. Maybe
I'll add some interesting links at the end of
the tutorial that might help in developing your
multiplayer game.
Note: Multiplayer programming is hard,
know what you're getting into! It might seem
so simple but it isn't.
2.
Client basics
In
case you skipped my previous article, or just
forgot to read a sentence: Client/server means
that every client sents the data to the server
which will route it to other players accordingly
to the programmed code.
So let's first get working on the Client side
of our Tic Tac Toe game. Please note that I'm
not going to put every line of code in here. So
copy and paste won't work. You can download
the complete tutorial above.
'//We'll need the following declarations:
Option Explicit
Public objDPClient As DirectPlay8Client '//This is the most important part
Public objDPAddress As DirectPlay8Address '//Will be used for the IP address later
Public objDX8 As DirectX8 '//DirectX object.
|
|
|
|
For Tic-Tac-Toe, Client/Server is somewhat an
wrong connection method. There aren't going to
be more than two players in the game anyways.
with Peer-Peer every one receives notification
when someone leaves the game, or joins the game.
with Client/Server it doesn't come nearly as close
to make it this easy. Only the server receives
the join/leave events, so the server should broadcast
this to all other (active) players. The server
has the responsibility for the entire game. to
be honest, I had some trouble creating the Tic-Tac-Toe
Client/Server game, since I'm used to realtime
multiplayer programming (Quadrant
wars) instead of turn based. I've rewriten
it two times, and the second version used half
the code. Rewriting your multiplayer game is often
an essential part, where you can solve minor problems
and optimise your code.
Public Const AppGuid = "{5726CF1F-702B-5008-28BC-EF9C95D9E288}"
Public Const intServerPort As Integer = 4000 '//Use 4000 as serverport (we'll connect to that port)
|
|
|
|
The code above demonstrates the Application GUID.
This is an unique ID for the client and
the server. If the client or the server do not
have the same ID, they can never connect to each
other. It's very handy when new versions of client
and servers are released. Changing the GUID to
something else will make all other clients obsolete
(unless there are still old servers online). The
ServerPort is the port where we are going to connect
to on the server. The server has the same port
to listen on. When choosing ports, it's generally
recommended that you choose ports above
3000. If you choose lower you might try to listen
on ports used by proxies, firewalls, or something
else. Now we have all the declarations to get
DirectPlay8 going.
Set ObjDX8 = New DirectX8
Set ObjDPClient = ObjDX8.DirectPlayClientCreate '//Create DP Client
Set ObjDPClientAddress = ObjDX8.DirectPlayAddressCreate '//Create Client DP address
Set ObjDPServerAddress = ObjDX8.DirectPlayAddressCreate '//Create Server DP address
ObjDPClient.RegisterMessageHandler frmMain '//Register Server message handler, our form!
ObjDPClientAddress.SetSP DP8SP_TCPIP '//Configure Client address for TCP/IP
ObjDPServerAddress.SetSP DP8SP_TCPIP '//'Configure Server address for TCP/IP
ObjDPServerAddress.AddComponentLong DPN_KEY_PORT, intServerPort '//Add the serverport to the ServerAddress
|
|
|
|
The code aboves creates the DirectX8 object, DP8
Client object, and two DirectPlayAddresses. We'll
need those two later to connect to our server.
We set one Address (Client) to our local IP, and
one to the destination, server IP. Here we also
register the message handler. From this point
we receive every event fired by DirectPlay8. Don't
forget to add all the events to your form, or
you'll get an "Type mismatch" error. The "DP8SP_TCPIP"
tells DirectPlay to use the TCP/IP protocol, for
network/internet usage. If you specify these directly,
you do not have to enumerate the service providers
anymore, for example modem, serial connection,
IPX, etc. The AddComponentLong can be used to
set several properties for the connection. Here
we only add the port that we use for our game.
To simplify the client connection code, I've removed
the part that asks for the hostname/IP. check
the source for this.
Dim AppDesc As DPN_APPLICATION_DESC '//Application description
ObjDPServerAddress.AddComponentString DPN_KEY_HOSTNAME, strHostName '//Add this remote host to the server connection address
AppDesc.guidApplication = AppGuid '//This is a Guid, to indentify all your clients (for enumeration)
'//If we are connected a "connect complete" event will trigger in frmClient
ObjDPClient.Connect AppDesc, ObjDPServerAddress, ObjDPClientAddress, 0, strPlayerName, Len(strPlayerName)
|
|
|
|
You can seen another "AddComponentString". This
time we add the hostname or IP address of the
server. We'll connect to that address using "ObjDPClient.Connect".
Writing this tutorial learned me something new.
An easy method to send a playername while connecting
to the server! Normally this would involve being
connected already, and then after a few packets
finally commence gameplay.
Dim PlayerInfo As DPN_PLAYER_INFO
'//Set up peer info
PlayerInfo.Name = InputBox$("Playername?", "Playername", "Myself")
strPlayerName = PlayerInfo.Name
PlayerInfo.lInfoFlags = DPNINFO_NAME
ObjDPClient.SetClientInfo PlayerInfo, DPNOP_SYNC
|
|
|
|
This will simply add the specified name to the
client, and can be retrieved serverside using
GetClientInfo.
After connecting an "connect_complete" event will
be fired. You can here notify the user that a
connection has successfully been made.
That's all for the client side. Sending/Receiving
has already been covered on the previous page.
3.
Server basics
The
server of this game doesn't do very much. It's
often better to do the most CPU intensive calculations
on the clients, instead of the server. This way
you can have much more players while the server
doesn't go into overkill mode, becoming slow because
certain actions have to be performed. When a server
has to loop every 1\10 of a second through an
array of 500 players, latency will occur, and
it isn't caused by your internet connection, but
simply the processing time of your server.
But since I wanted to show some server side game
handling, I've made it so that the server checks
if the game was won by someone or if the game
became a tie. You could compare this to server
side cheat protection. If I would do this on the
client side, it would be easier to make changes
to the game (using packetsniffers or an hex editor)
and let other clients know player xx won.
Private Const AppGuid = "{5726CF1F-702B-5008-28BC-EF9C95D9E288}"
Private Const intServerPort As Integer = 4000 '//Use 4000 as serverport (we'll listen on that port)
Public objDX As DirectX8 '//Main DirectX8 object
Public objDPServer As DirectPlay8Server '//Server object, for message handling
Public objDPServerAddress As DirectPlay8Address '//Server's own IP, port
|
|
|
|
You can see that the first two lines are identical
to the variables used by the client. The Appguid
and serverport need to be identical for
the client and server to see each other. The DX8
object is also present again. But instead of having
a DirectPlay8Client object, we have an DirectPlay8Server
object, that will be used for our server. The
serveraddress is used for listening on the local
IP, and to set the TCP/IP provider.
Dim AppDesc As DPN_APPLICATION_DESC '//Application description
With AppDesc
.guidApplication = AppGuid '//Same as client! (Important!)
.lMaxPlayers = 3 '//TicTactoe = max 2 players + 1 server
.SessionName = "TicTacToe Game" '//Name our session like this
.lFlags = DPNSESSION_CLIENT_SERVER '//this game is 100% Client-Server
End With
Set objDX = New DirectX8 '//Create DirectX object
Set objDPServer = objDX.DirectPlayServerCreate '//Create Server object
Set objDPServerAddress = objDX.DirectPlayAddressCreate '//Create Server DP address
objDPServer.RegisterMessageHandler frmMain '//Register Server message handler
objDPServerAddress.SetSP DP8SP_TCPIP '//Configure Server address for TCP/IP
objDPServerAddress.AddComponentLong DPN_KEY_PORT, intServerPort '//Listen on this port
objDPServer.Host AppDesc, objDPServerAddress '//Start hosting the session
|
|
|
|
The
code above starts the server. We give the Application
description the guid, and the number of players.
Remember, the server is also a player. if you
want your game limited to 16 players, it has to
be 17! We also specify the session name, which
will be visible if we enumerate sessions (what
we won't do here in this game) and specify the
flag that tells DirectPlay that this game will
be client-server. There are lots of flags available,
and I recommend you to read those in the DX8 SDK
docs.
Like for the client, we again create a DirectX
object, a Server object and an address object.
The Server object will again use an event handler,
so do not forget to add all events to the form.
We set the service provider to TCP/IP, so this
game can run on networks and Internet. Further
we add the serverport again, just like the client
side code. When calling "Host" the server will
start and also be available for clients to join!
That is practically everything you need to know
to get a server running, but there are a few things
I'd like to explain. When you call Host, the server
will be started. If this succeeds a "CreatePlayer"
event will be fired. So, the first server CreatePlayer
event is always the playerid of the server!
Don't forget that one.
It is also possible to forwards packets directly,
by just passing the "dpnotify.receiveddata" to
the "DPServer.SendTo" method, but it seems that
it doesn't like to work on my PC, while it worked
before. It does work in someone else his game.
I've copied his code but it still didn't work.
I'm not sure if this might be caused by a difference
in DX8a and DX8, and Microsoft doesn't seem to
know it either. (Someone from Microsoft said it
should work, but he lacked VB, so he didn't know
it for sure)
You
can download the complete, working source code
from the top of the page, or from the downloads
page.
|