Lab: Network

In this lab, you will work in a group of 2 (or 3) students to create a network project, such as a multi-player video game. Turn-based games are likely to be more successful than real-time games. Groups without seniors will have time to complete more ambitious projects. Senior-only groups should limit the scopes of their projects. Mixed groups will eventually need to continue working without seniors.

Below, you will find an introduction to networking concepts, starter code for two complete network games in Java, along with reference material for the Java classes and methods you'll be using. Take your time reading through this information before you begin any coding.

When you do start coding, don't try to code all of the server-side or all of the display at once. Instead, your group's short-term goal is to see an event on one machine successfully trigger an event on another machine. For example, in Tic-Tac-Toe, a mark placed by one player should appear on their opponent's screen. As another example, the movement of a character by one player should appear on another player's screen.


Networking Concepts

Each device on the Internet is assigned an IP address, such as 192.168.0.55. You can send a message to a device if you know its IP address. (IP stands for Internet Protocol.)

Each program using the Internet is assigned a port number. You can send a message to a particular program running on a particular device if you know the device's IP address and the program's port number.

A network connection is like a telephone call. The program waiting to receive the call is called the server. The server listens for new connections on a particular port number. In the diagram below, the Server program uses Java's ServerSocket class to listen for new connections on port 9000.

The program that will place the call is called the client. It must know the server's IP address and port number in order to connect to it.

A socket is like a telephone. A program must use a socket to communicate over a network connection.

Below, a client uses a socket to call the server at IP address 192.168.0.55 and port 9000.

The server accepts the connection. This creates a new socket that the server will use to communicate with that client. Now, the client or server can send a message to their socket's output stream, and can receive a message from their socket's input stream.

In many applications, the server must communicate with multiple clients at once. Therefore, the server creates a new thread for handling all communication with a client.

This frees up the server to accept connections from other clients.

Protocols

Once clients have connected to the server, what does their communication look like? To communicate, a client and server use a particular protocol--the rules for what messages look like and who sends what kind of message when.

The following shows the protocol used by a hypothetical game of Tic-Tac-Toe. Notice that each message consists of text featuring a command (START, PLACE, WENT) and potentially a list of arguments, all separated by spaces. Some commands are always sent from the server to a particular client (START, WENT), while others are always sent from a client to the server (PLACE).

Client 1              Server              Client 2

        ← START

          PLACE 0 2 →

                              WENT 0 2 →

                            ← PLACE 0 0

        ← WENT 0 0

          PLACE 2 0 →

                              WENT 2 0 →

                            ← PLACE 2 2

        ← WENT 2 2

          PLACE 1 1 →

                              WENT 1 1 →

A big part of designing a protocol is deciding which responsibilities belong to the client and which to the server. In the Tic-Tac-Toe example, the client is responsible for most of the work. The server's only job is to decide who will play first. After that, the server simply relays information from one client to the other. Most of the game logic appears in the client. It's the client's job to prevent a player from placing a mark in an occupied space, and to determine when a player has won or lost. Because there is very little code on the server-side, we say that this architecture uses a thin server. For a project like this one, it's likely that one person will be primarily responsible for coding the networking parts, while the other focuses on the user interface and game logic.

The example below shows a protocol used by a thin client. Unlike Tic-Tac-Toe, this game is not turn-based. Both of these players can move at any time, and only the server can validate moves and resolve conflicts. In this example, both clients attempt to move to (3, 5) at nearly the same time. Neither client knows at that time if their move will be successful. The server receives the message from client 1 first, determines this move is valid, and tells both clients that player 1 successfully moved to (3, 5). The server then determine's that client 2's request is invalid. For a project like this one, it's likely that one person will be primarily responsible for coding the server-side and one will focus on coding the client-side.

Client 1              Server                Client 2

          MOVETO 3 5 →

                            ← MOVETO 3 5

        ← MOVED 1 3 5

                              MOVED 1 3 5 →


Sample Network Games

Two complete examples of Java network games are provided to you. Find a partner and download one (or both) of these programs.


Download starter code for a thin server.

Download starter code for a thin client.


You'll find code to run the game in Main.java.

public static void main(String[] args)

{

    //if hosting a game run this code:

    //hostGame();

    //joinGame("localhost");

        

    //if joining a game, run this code with host's IP address:

    //joinGame("127.0.0.1");

}


Make sure you are both on the same WiFi. First, one of you will run the code to host a game (and to join your own game), and then the other will run the code to join a game (passing the host's IP address). The program prints out all messages exchanged between the clients and server. Study these messages to understand the game's protocol.

Once you've done that, look inside the code to see how it works. The following sections explain the Java classes and methods you'll be using for this project.


Networking in Java

To use Java's networking classes, include the following import statements.

import java.io.*;

import java.net.*;


The following code connects to a server with IP address 127.0.0.1 and port number 9000.

Socket socket = new Socket("127.0.0.1", 9000);

PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));


Variable out is used to send text messages to the server. For example:

out.println("hello");


Variable in is used to receive text messages from the server. For example:

String received = in.readLine();


Java provides a class called ServerSocket. The server uses this class to listen for connections on a particular port. 

ServerSocket serverSocket = new ServerSocket(9000);

Socket socket = serverSocket.accept();


The ServerSocket's accept method waits for a client to connect. When this occurs, the method returns a Socket that the server can use to communicate with that client.


Catching Exceptions

Nearly all constructors and methods used to communicate with the network may throw an IOException. Your code cannot ignore these exceptions, but you can catch them as follows.


try

{

    ... code that may throw an IOException ...

}

catch (IOException e)

{

    throw new RuntimeException(e);

}



Threads

BufferedReader's readLine method doesn't return until a message is received from the network. Likewise, ServerSocket's accept method doesn't return until someone connects to the server. This is called blocking. During that time, no other code can run, unless our program uses multiple threads.

A thread is similar to a process. Multiple threads can appear to run at the same time, each running their own code. In Java, we create a thread by extending Java's Thread class and overriding Thread's run method, as in the Walker class below.


public class Walker extends Thread

{

    public void run() { Robot.walkForAnHour(); }
}


The Thread class's start method magically starts a new thread that executes the code in the run method. For example, the following code walks and chews gum at the same time.


Walker w = new Walker();

w.start();  // magically starts executing Robot.walkForAnHour() in a new thread

Robot.chewGumForAnHour();


Parsing Messages

Both your client and server will need to parse any messages they receive from the network. Here are two methods that can make this job a lot easier.


String's split method
If String str is "PLACE 0 2",
then str.split(" ") returns the array ["PLACE", "0", "2"].


Integer's parseInt method
Integer.parseInt("2") returns the int value 2.


Testing / Debugging


You can use this FakeServer.jar to test your client-side code

and this FakeClient.jar to test your server-side code.