Java has grabbed the undivided attention of Web developers and shows no sign of disappearing. What does all this Java hype mean for game developers? It means the potential for creating cross-platform Internet games that can be played within the context of the familiar Web page format. In other words, you develop one set of source code and publish one executable on the Web.
Because of its power and ease of use, standard networking support in Java is probably the most compelling reason for game developers to give Java a closer look. This article explains networking support in Java and how it can be used to build multiplayer Web games. You'll see how Java makes it surprisingly easy to tackle many of the network challenges faced with other languages and development environments.
Is Java Ready for Games?
If you shudder thinking about the fact that Java executables are interpreted, you are probably a game developer who understands the critical issue of performance in games. Performance is so important in game programming that languages and development tools are regularly chosen solely based on their performance. This stands in stark contrast to other areas of software development, where maintainability and ease of use generally take precedence over raw performance.
How does Java fare as a game programming language and run-time environment? In its traditional interpreted form, Java is pretty weak. In fact, few commercial games relying heavily on high-performance graphics would run tolerably in a Java environment. However, a recent technology has greatly improved this scenario. I'm referring to just-in-time Java compilers, which eliminate the costly interpretation of generic Java bytecodes. A just-in-time (JIT) compiler is a back-end compiler that converts generic Java bytecode executables into native executables. Because JIT compilers generate native code, Java performance starts approaching that of natively compiled languages like C and C++.
Even though JIT compilers greatly improve the performance of Java programs, it's still hard to pit Java head-to-head with a platform-dependent program written in C or C++. This is mainly due to the API layering involved in making the Java API cross-platform. If you've ever been involved in a media-intensive cross-platform project, you probably understand how messy it can be. Java, on the other hand, provides one API and requires you only develop a single set of source code that can be executed on any Java supported platform. So, when assessing the performance limitations of Java, you must acknowledge this benefit.
Java in its current state is still a little on the slow side, which rules it out as a possibility for high-performance, graphically intensive games. However, Java is certainly capable of being used for other types of games, such as multiplayer strategy games. As this article culminates with the development of a complete Web-based multiplayer NetConnect4 Java game, you can judge for yourself where Java stands as a game development environment.
Before we look into the network support Java provides, it's important you understand some fundamentals about the structure of the Internet as a network. The only way to allow a wide range of computer systems to coexist and communicate with each other effectively is to hammer out some standards. Fortunately, plenty of standards abound for the Internet, and they share wide support across many different computer systems. Let's take a look at a few of them.
Addresses. One of the first areas of standardization on the Internet was in establishing a means to uniquely identify each connected computer. The solution that was implemented was IP addresses, which come in the form of a 32-bit number that looks like this: 243.37.126.82. You're probably more familiar with the symbolic form of IP addresses, which looks like this: gdmag.com. Without an addressing scheme, there would be no way to distinguish among different computers.
Protocols. Many different types of communication can take place on the Internet, so there must be an equal number of mechanisms for facilitating them. Protocols are sets of rules and standards that define certain types of Internet communication. More specifically, a protocol specifies the format of data being sent over the Internet, along with how and when it is sent. On the other end of the communication, the protocol also defines how the data is received along with its structure and what it means.
Without a doubt, the Internet protocol getting the most attention these days is the hyper-text transfer protocol (HTTP), which is used to transfer HTML documents on the web. The file transfer protocol (FTP) is a more general protocol used to transfer files over the Internet. These two protocols both have their own unique set of rules and standards defining how information is transferred, and Java provides support for both of them.
Ports. Internet protocols make sense only in the context of a service. For example, the HTTP protocol comes into play when you are providing Web content (HTML pages) through an HTTP service. Each computer on the Internet has the capability to provide a variety of services through the various protocols supported. There is a problem, however--the type of service must be known before information can be transferred. This is where ports come in. A port is a software abstraction that provides a means to differentiate between different services. More specifically, a port is a 16-bit number identifying the different services offered by a network server.
Each computer on the Internet has a bunch of ports that can be assigned different services. To use a particular service and therefore establish a line of communication via a particular protocol, you must connect to the correct port. Ports are numbered, and some of the numbers are specifically associated with a type of service. Ports with specific service assignments are known as standard ports, meaning that you can always count on a particular port corresponding to a certain service.
For example, the FTP service is located on port 21, so any other computer wanting to perform an FTP file transfer would connect to port 21 of the host computer. Likewise, the HTTP service is located on port 80, so any time you access a Web site, you are really connecting to port 80 of the host using the HTTP protocol behind the scenes. Figure 1 illustrates the relationship between ports and protocols.
Protocols and Ports
All standard service assignments are given port values below 1024. Ports above 1024 are considered available for custom communications, such as those required by a network game implementing its own protocol. Keep in mind, however, that other types of custom communication also take place above port 1024, so you might have to try a few different ports to find an unused one.
One of Java's major strong suits as a programming language is its wide range of network support. Java has this advantage because it was developed with the Internet in mind. The result is that you have lots of options in regard to network programming in Java. Even though there are many network options, Java network game programming uses a particular type of network communication known as sockets. A socket is a software abstraction for an input or output communication medium.
Java performs all its low-level network communication through sockets. Logically, sockets are one step lower than ports; you use sockets to communicate through a particular port. So a socket is a communication channel enabling you to transfer data through a certain port. Check out Figure 2, which shows communication taking place through multiple sockets on a port.
This figure brings up an interesting point about sockets: data can be transferred through multiple sockets for a single port. Java provides socket classes to make programming with sockets much easier. Java sockets are broken down into two types: stream sockets and datagram sockets.
A stream socket, or connected socket, is a socket over which data can be transmitted continuously. By continuously, I don't necessarily mean that data is being sent all the time, but that the socket itself is active and ready for communication all the time. Think of a stream socket as a dedicated network connection in which a communication medium is always available for use. The benefit of using a stream socket is that information can be sent with less worry about when it will arrive at its destination. Because the communication link is always "live," data is generally transmitted immediately after you send it.
Java supports stream socket programming primarily through two classes: Socket and ServerSocket. Socket provides the necessary overhead to facilitate a stream socket client, and ServerSocket provides the core functionality for a server. Most of the actual code facilitating communication via sockets is handled through input and output streams connected to a socket. In this way, the communication itself is handled independently of the network socket connection. This might not seem like a big deal at first, but it is crucial in the design of the socket classes; after you've created a socket, you connect an input or output stream to it and forget about the socket.
The other type of socket supported by Java is the datagram socket. Unlike stream sockets, in which the communication is akin to a live network, a datagram socket is more akin to a dial-up network, in which the communication link isn't continuously active. A datagram socket is a socket over which data is bundled into packets and sent without requiring a live connection to the destination computer.
Because of the nature of the communication medium involved, datagram sockets aren't guaranteed to transmit information at a particular time or even in any particular order. The reason datagram sockets perform this way is that they don't require an actual connection to another computer; the address of the target computer is just bundled with the information being sent. This bundle is then sent out over the Internet, leaving the sender to hope for the best.
On the receiving end, the bundles of information can be received in any order and at any time. For this reason, datagrams also include a sequence number that specifies which piece of the puzzle each bundle corresponds to. The receiver waits to receive the entire sequence, then puts them back together to form a complete information transfer. As you might think, datagram sockets are less than ideal for network game programming because of the implied time delays, heightened reliability issues, and sequencing complexities.
A Reusable Socket Class
You've now learned enough about network theory and the Java networking support to write some code. Before you can think in terms of writing network game code, however, you need to develop some code that helps facilitate the core communications necessary for a game. In doing so, you'll have reliable, reusable code that can easily be applied to provide functionality specific to a particular game communication protocol.
The first layer of code necessary to facilitate network communications comes in the form of a socket helper class that handles the details of initializing a socket and managing the associated data streams. The SocketAction class was developed by Greg Turner to help ease the pain in establishing a communication channel using Java sockets. The SocketAction class is derived from the standard Thread class, so it has its own thread of execution. Let's start by looking at the member variables of SocketAction, which is shown in Listing 1.
The first two members, inStream and outStream, are the input and output streams used to receive and send data through the socket. The third member variable, socket, is the socket object. The constructor for SocketAction takes a Socket object as its only parameter, as shown in Listing 2.
The constructor creates the buffered data streams and initializes the socket member variable with the Socket object passed in. If there was a problem in initializing the streams, the constructor detects it by using the catch clause. If there is an error in creating the streams, something is seriously wrong, which explains why the entire program exits.
The send and receive methods are possibly the most useful methods in SocketAction, even though they contain very little code, as shown in Listing 3. The send method simply sends a string out over the socket connection by using the output data stream. Similarly, the receive method receives a string by using the input data stream.
The closeConnections method simply closes the socket, as shown in Listing 4. The isConnected method verifies that the input and output streams as well as the socket object are valid, as shown in Listing 5. Finally, the finalize method closes the socket as an added safety precaution, as shown in Listing 6.
And that's all there is to the SocketAction class, which is pretty elementary. Nevertheless, its simple function of providing a clean management class for sockets and their associated streams will make life much easier when building network Java games.
The core of Java network game programming revolves around a client/server communication strategy. In fact, the design of the NetConnect4 game can be divided cleanly into the client side and the server side. These two components are logically separate, communicating entirely through a game protocol defined between them. Let's take a look at what each of these pieces is responsible for.