Multiplayer Example A Java tutorial explaining the basics of multiplayer game design. This step-by-step guide will walk you through creating the example Java program discussed in Multiplayer Game Basics on http://blakecode.blogspot.com. It expects you to have prior knowledge with Java. The purpose of this tutorial is to explain how to use Java to communicate between two computers, as you would in a multiplayer game. It is NOT designed to "hold your hand" and teach you how to program Java. Basic terminology will be used and not all aspects of the program itself will be explained. If you want to learn Java, I suggest buying a book from your local bookstore, or Googling for a Java learning resource. NOTE: This document is best viewed with wordwrap on. --------------- GETTING STARTED --------------- You will need a text editor (I use Notepad), the Java Runtime Environment (JRE), and Java Development Kit (JDK). Both the JRE and JDK can be downloaded from Sun's website. http://java.sun.com/javase/downloads We will be working with three files. MplayServer.java will be responsible for handling incoming connection requests, creating new socket connections to fulfill those requests, then delegating the socket connections to another class via threading (see the next section for details). MplayClient.java will maintain contact with the client computer after it is given control of the socket, respond to messages from the client, and inform the server of changes in the socket connection status. The third file, MyTalker.java, will only be touched on briefly since most of its content is beyond the scope of this tutorial (it uses Swing objects to offer an easy-to-use interface for sending and receiving messages). MyTalker.java will be used to communicate with the server. Create the three empty files in the same, easily accessed directory. --------------- WHY USE THREADING? --------------- Threading is a Java technique in which a new process is created from an existing one. This is effective in the creation of applications that require more than one class or object to be running concurrently with others. In this specific case, we want to be able to listen for new connection requests and listen to all existing connections at the same time with equal priority. We use threading because otherwise, we would have to use an infinite loop that takes care of all connection requests, then all existing connections, or vice-versa, which would result in a waiting time for everyone. Instead, we use threading to have one process (also referred to as a thread) dedicated to the server class to handle connection requests, then have this main process spawn child threads dedicated to each new connection. --------------- CREATING THE SERVER AND CLIENT --------------- The server plays the important role of accepting connection requests from clients trying to join the game, then continuing to listen to them and relay important game data until they are ready to leave the game or otherwise disconnect. As explained before, our server will consist of two classes. So, lets get started with the first one; The MplayServer class. 1) We need to import the util package for some basic objects and methods, the io package for input and output streams, and the net package for socket communications. 2) All client objects need to be accessible by the server, so we are going to use a Map structure which holds the client ID and the actual client object. We want all clients to be independant and not be able to access eachother directly, so we declare the map as private. We also need an int to hold the number of clients we have in the map. This makes it a lot easier to append new clients. 3) ServerSocket objects are the main interest here. A ServerSocket object listens for communications on a given port. 4) Our methods will consist of our constructor, entry point (main), communication handler (handleIncoming), and any game-specific extras the server would need. Since this is just a simple example, the only extra methods are "kill" and "removeMplayClient", to allow us to gracefully stop the server and kick clients, respectively. 5) Lets start with our entry point method (main). A) Check to make sure the arguments passed at execution time are correct. If they are, create a new MplayServer with the given port. If they arent, display a usage message describing the arguments expected. 6) Next is our constructor (MplayServer(port)). A) Prepare an empty HashMap for our clients Map and set the client count to 0. B) Create a new ServerSocket on the port passed to the constructor. This is where the magic begins. C) Call our communication handler to start listening to our new ServerSocket. 7) Now for the fun part; Handling incoming communications (handleIncoming). A) We start an infinite loop, so that we make sure we catch all connection attempts. B) The ServerSocket.accept() method waits until a connection attempt is made. When one finally is, it returns a new Socket to the client computer. We save this Socket, create a new MplayClient to handle communication on that socket, and add the MplayClient to our clients Map. C) Since our MplayClient is designed to be a Thread, we call the Thread.start() method with it to finalize everything and spawn the new thread. (The Thread.start() method calls MplayClient.run(). We will program this method later on.) D) Thanks to our loop, we lather, rinse, and repeat for each new communication on our ServerSocket. 8) Our kill method is used to "gracefully" shut down, meaning end all client communications and alert them to the shutdown. This ensures that no data is lost. A) Loop through each client in our clients Map, and call its MplayClient.kill() method. (Once again, we will program this method later on) 9) Since we made our clients Map private, we have to have a method to allow dying clients to remove themselves from the Map. This is accomplished by creating a method to do just that (removeMplayClient). A) Map.remove() takes care of everything for us. If you recall, the Map entry actually contains the MplayClient object, so by removing this Map entry, we destroy the entire MplayClient object, all of its data, and the Socket with the client computer. The MplayServer class hands off most of the work to the MplayClient class. Now that we have a finished MplayServer, lets move on to the MplayClient. 1) Just like MplayServer, we start off by importing the util, io, and net packages. 2) We need to tell Java that this class is threadable. We do this by extending the Thread class. 3) Since MplayClient is a representation of the client in the game, we have a combination of variables from game-specific information to server/communication information. Since this is just a generic example, our only game-specific variable is the username. 4) It is important that the MplayClient object knows its session ID on the server, what server it is on, and the socket to the client it is supposed to be listening to. All of these are passed to the constructor by MplayServer. Along with these, we also need to be able to read and write to the socket datastream. 5) The methods we need for this class include the constructor, method called when the class is threaded (run), communication handler (handleIncoming), data sender (sendData), some methods for getting and changing data from our private variables, and any game-specific methods. Since this is just a simple example, the only extra game-related methods are "kill" and some other extra methods that just return variable values or change them. 6) We will start with our constructor (MplayServer(server, socket, sessid)). A) Set our local variables to the values we have just been given. B) Prepare the stream reader and writer for the socket MplayServer has given us. 7) Instead of beginning to handle incoming data in our constructor, as we traditionally would, we wait to do this after the object has been threaded. When the object is threaded, the run method is called. A) Simply call handleIncoming(). 8) Our handleIncoming() method listens for new data on the Socket associated with the client computer. A) Loop until the socket, and consequently the datastream, is closed. B) While we still have data to use, pass it to interpretData(). 9) We have a completely different method to decide what to do with incoming data. This makes expanding our code a lot easier and our code a lot cleaner. (interpretData(msg)) A) Since this is just a simple example, we just have the server reply to every incoming message with "welcome". 10) To send data to the client, we use another short method. (sendData(msg)) A) All we have to do is add the message to the stream writer we created earlier. 11) To signify that the client is done communicating and is ready to be removed from the server, we use the kill method. A) Just pass the session ID to MplayServer.removeMplayClient(). Thats all for our server. Now that we have a working server, we need a client interface to be able to communicate with. The client included with this tutorial contains a lot of GUI code to make it look pretty. Since this is beyond the scope of this tutorial, I will just cover what is needed to communicate with the server. 1) We will need the io and net packages. 2) Like the server, the client uses Socket objects to communicate. Unlike the server, we dont use ServerSockets. ServerSockets are designed to accept connections, where Sockets are designed to send and receive data on an established connection. 3) We create a new Socket in our init() method pointing to the host and port we give when we execute the program. 4) After our socket is connected, we set up our stream reader and writer on it. When we want to send data, we use our stream writer. We use a loop that continuously checks our stream reader for incoming data from the server. Its as simple as that! --------------- COMPILING AND RUNNING THE CODE --------------- To compile the server, we use the command "javac MplayServer.java". MplayClient.java is automatically compiled since it is a child class of MplayServer. To compile the client, we use "javac MyTalker.java". Now we are ready to test out the programs. First, we need to start the server. You can either get a friend to start it on his own computer, or do it on your own. The command to do so is "java MplayServer PORT", where PORT is the port number to start it on. It is recommended that you not use any commonly used ports. I usually use 8087, since the 8000 ports arent used for much. If everything runs correctly, you should start seeing some status messages from the server. When the server is ready to accept connections, you can use the example client to connect to it and start sending and recieving messages. To stop the server, hit the CTRL and C key together. To start the example client, use the command "java MyTalker PORT". PORT must be the same as the port number you used to start the server. If you have a friend running the server for you, you have to use "java MyTalker HOST PORT", where HOST is your friends IP address. If you have problems connecting, check your firewall settings, as well as your friends if they are running the server for you. To stop the client, type "quit" to gracefully exit.