Zero Configuration Networking: Using the Java APIs, Part 3

Editor's Note: Since you've all been good and read parts one and two of this excerpt from Zero Configuration Networking: The Definitive Guide, by Stuart Cheshire and Daniel H. Steinberg, it's time to have a little fun. In this code-heavy final installment, the authors take what they've shown in the previous sections and apply it to creating a game, using Zeroconf/Bonjour for the networking and Swing for the GUI. Each instance of the game advertises itself on the network and browses for potential opponents. While the game of tic-tac-toe is trifling--and this implementation doesn't even bother enforcing the rules, since that's off-topic--what you'll find here are the essentials of creating a self-networking Java game, or any kind of collaborative application, on the LAN. Note that you can also download the code directly from the book's example page. Enjoy!

An Extended Example: Tic-Tac-Toe

Tic-tac-toe is a game ordinarily played on paper by two people sitting near each other. The board is a three-by-three grid and usually one player marks squares using X's and the other using O's. The players alternate marking the squares, trying to end up with three of their marked squares in a line (i.e., across, down, or diagonally).

In the Zeroconf version, players register service instances of type _tic-tac-toe-ex._tcp and browse for other players. The game program has two main classes, TicTacToe and GameBoard.

Class TicTacToe browses for other players and displays the list of what it finds. It also opens a listening socket, advertises it with DNS-SD, and then fires off an independent background thread to sit and wait for incoming connections.

Class GameBoard can get instantiated in two ways. If the user clicks on one of the discovered players in the list, then we make a new GameBoard, and start a DNSSD.resolve( ) running for the named service. When the newly created GameBoard object receives the serviceResolved( ) callback, it connects to the specified host and port and begins playing the game, listing for messages received over the network from the peer, and sending messages to the peer every time the user clicks in a square. The other way a GameBoard can get instantiated is on the receiving end of a connection request. If another user clicks on us in their list, then our TicTacToe background thread will receive an incoming connection request. In this case it also makes a new GameBoard object, but in this case no resolve-and-connect is needed, because the TCP connection is already open. A player can have any number of active games, connected to different opponents, at once.

Figure 8-2 shows a GameBoard window for a game in progress with a player called "Mike."

Figure 8-2. TicTacToe game board

Note that the purpose of this example is to demonstrate the Zeroconf-related aspects of writing a Java program. As a result, this example does not try to implement the rules of tic-tac-toe; for example, it does not enforce that the players are supposed to take turns clicking squares.

Each time you run the program, it asks the system for a new unallocated TCP port to listen on and then advertises that port number to its peers using DNS-SD. One of the benefits of DNS-SD is that, because it advertises port numbers as well as hostnames and addresses, programs are no longer restricted to using fixed, hard-coded port numbers. This means you can write a program and it can use any available port when run, instead of your having to apply to IANA to get a new well-known port number reserved for every program you write. There are only 65,535 possible TCP port numbers, and they'll run out quickly if every person in the world gets one reserved for every program they write. In addition, even if you get a well-known port number reserved, you get only one, so that doesn't help when you want to run two copies of your program on the same machine. You'll see that, with the TicTacToe program, you can run as many copies as you like on the same machine, which can be very helpful when testing, especially if you're working on your laptop computer on an airplane and don't have a whole network of machines available.

Most Unix systems allocate dynamic TCP port numbers starting at 49152 and working upward. If you have some kind of personal firewall running on your machine, ensure that it is configured to allow incoming connections to high-numbered ports (49152-65535). Otherwise, the firewall will do exactly what it is supposed to do: prevent your networking program from receiving any incoming connection requests. Most firewall programs don't give you any feedback to tell you when they've silently discarded an incoming connection request, so this can be quite frustrating to debug if your program is failing and you don't realize that the personal firewall is the cause. The TicTacToe program window title shows the port number it's listening on, so that you can cross-check with your firewall settings and verify that your personal firewall is allowing the necessary packets through.

Our TicTacToe program calls DNSSD.register without specifying an instance name, so it automatically gets the system default. When it gets the serviceRegistered callback, it updates the window title to show its advertised name. You can try some experiments to see how Multicast DNS name conflict detection works. If you run a second copy of the TicTacToe program on the same machine, you'll see the second copy gets the same name with "(2)" appended. If you plug your Ethernet cable into a network where your name is already being advertised by another TicTacToe program, you'll see your window title update to show a new name. You can also change the system default name while the TicTacToe program is running, and you'll see that it gets informed of the new name and updates its windows. On Mac OS X, you set the system default name by setting the "Computer Name" in the Sharing Preferences.

The TicTacToe program also pays attention to its own advertised name in order to exclude itself from the list of discovered games on the network.

Example 8-7 shows the source code for TicTacToe.java, and Example 8-8 shows the source code for GameBoard.java. You can compile them both directly on the command line by typing javac TicTacToe.java and then run the program by typing java TicTacToe, or you can use the Makefile shown in Example 8-9. The Makefile builds the classes, placing them in a subdirectory called classes, then makes a Java jar file from the classes, and finally runs the resulting jar file with java -jar TicTacToe.jar.