developer.com - Reference
Click here to support our advertisers
SHOPPING
JOB BANK
CLASSIFIEDS
DIRECTORIES
REFERENCE
Online Library
LEARNING CENTER
JOURNAL
NEWS CENTRAL
DOWNLOADS
COMMUNITY
CALENDAR
ABOUT US
Journal:
Get the weekly email highlights from the most popular journal for developers!
Current issue
developer.com
developerdirect.com
htmlgoodies.com
javagoodies.com
jars.com
intranetjournal.com
javascripts.com
All Categories :
Java
Chapter 27
Multiuser Network Programming
by Michael Afergan
CONTENTS
Our Mission-Should We Choose to Accept It
The Requirements of the Server
Integrating a Communication Class in Your Applet
How to Connect to a Server
How to Communicate with the Server
How to Disconnect from the Server
The Graphical Interface
How to Thread the Client Class
How to Share Information
The Translating Method
Advanced Topics of Applet Development
Summary
Without doubt, Java is one of the more spectacular products to
hit the computer market in recent years. Inasmuch as its initial
support came from academia, most initial Java applets have been
limited to decorative roles. However, now that Java's popularity
has increased, Java has recently been used for more practical
purposes. Applets have been used to provide live-time, ticker-like
feeds and to create interactive environments for such purposes
as retrieving financial information. In essence, by employing
the power of Java, users can now do much of what they think they
should be able to do on the Internet-but have not been able to
do before.
One of the more exciting powers of Java is that it gives programmers
the ability to create multiuser environments in which many users
can interact and share information. In a simple multiuser environment
such as the one in Figure 27.1, several users are connected to
each other through a server running on a mutually accessible host.
As a result, all actions by one user can instantaneously be displayed
on the screens of other users across the world-without any requirement
that the users know each other beforehand.
Figure 27.1 : A multiuser environment.
This ability to link computers in live time enables Java programmers
to accomplish feats never before possible in a Web page. A business
can now set up a system so that people who desire information
can talk with a customer service representative directly on the
Web page rather than sending e-mail to a service department. Moreover,
because of the graphical nature of the Web and Java, the customer
service representative can demonstrate information in a visual
manner (for example, by pointing out items on a diagram). Additionally,
an enterprising company can allow people from around the world
to participate in a real-time live auction. In general, by facilitating
multiple-user environments that enable such sharing of information,
Java has the power to revolutionize the Web.
Why Java?
The concept of servers, sockets, and communication is nothing new, nor is the idea of linking many users together in a live-time environment. In fact, the Web, without Java, can be considered a multiuser environment inasmuch as multiple users can access the same information in the form of a simple HTML page. Furthermore, methods exist by which a user can directly affect what another user sees on a given page (such as the hit counters found at the bottom of many HTML pages).
What, then, does Java offer that is so revolutionary? It offers the capacity to perform actions while the user is viewing the same HTML page. The architecture of the Web enables users to view Web pages from around the world. However, once this page is displayed on your screen, it cannot change. In the example of the hit counters-although more users may access the given page-you are never informed of this fact unless you reload that page. Java, however, brings life into Web pages by means of applets, enabling the pages to perform actions such as communicating with a server. Java enables programmers to create applets as potent and effective as the applications on your own computer. It is this capacity that enables us to create multiuser environments.
This chapter discusses the elements required to create a multiuser
environment in Java. Although no entirely new topics in Java are
presented, this chapter shows you how to bring several powerful
aspects of Java together and highlights some of the more interesting
applications of Java. Through explanation of the processes as
well as sample code, this chapter enables you, the Java programmer,
to code and establish your own multiuser environment. This chapter
deals with subjects including the handling of the connections
to the server, the graphical interface, and various animation
and threading techniques necessary to make your applet Web-worthy.
Although each of these topics is explained with code, keep in
mind that the essence of a multiuser environment is what
you do, not how you do it. Furthermore, the code for each
multiuser application depends heavily on the environment itself,
and therefore is significantly different in each. Consequently,
although the code offered here can supply you with a suitable
framework, it may be advisable to envision your own multiuser
application. As each topic is presented, imagine how you would
deal with each of the issues involved. Remember that in programming-and
Java in particular-the limits of the language are the limits of
your imagination. Be creative and have fun!
Sockets and Netscape
With any new technology come new concerns and problems. Java is definitely not an exception to this rule. The chief concern of many has been the fact that Java applets can connect to servers from around the world and transmit information from the host that its owner might not want to make public. Although there are several solutions to this problem, Netscape has chosen to place stringent restrictions on Java socket connections for now.
Currently, Java sockets can connect only to servers running on the host that served the applet. For example, an applet residing in http://www.xyz.com/page.html can connect back only to a server running on www.xyz.com. Therefore, when developing a multiuser environment as discussed in this chapter, make sure that the server to which the applet connects is running on the same host as the HTML page in which the applet resides. (See Chapter 35, "Java Security," for more details about applet security.)
Our
Mission-Should We Choose to Accept It
In this chapter, we develop a multiuser environment for a museum
that is opening an exhibition of Haphazard Art. The museum believes
that the most beautiful art is created by human-controlled whims
and has hired you to create an environment through which users
from around the world can "weave" a quilt. The user
should be able to access the Web page, select a color and a blank
tile, and "paint" the tile. Not only should this tile
change colors on the screen of the user, but it also should instantaneously
become painted on the screens of all the users around the world
who are working on that quilt. (The museum will then save these
designs and display them at its upcoming exhibit.)
Although this is a rather simplistic example of the power of multiuser
environments, it is an excellent model for the explanation of
the concepts involved.
The
Requirements of the Server
Although it is not of our direct concern, the server in this environment
plays an extremely large role. Because the server can be written
in virtually any language, we won't spend much time dealing with
it here. However, it is necessary that we discuss the essentials
for the server in a multiuser environment.
As you can see from Figure 27.1, the server acts as the intermediate
agent between the various users. Thus, the server must be able
to do the following:
Accept multiple connections from clients
on a given port
Receive information from the clients
Send information to the clients
As the application becomes more involved, it is necessary to add
additional functionality to the server. Even in the simple example
of the museum quilt, the server must keep track of the color of
all the tiles. Furthermore, if a new client begins work on an
in-progress quilt, it's necessary for the server to inform the
client of the current status of the quilt.
Nevertheless, these subjects are extremely context-dependent and
deal more with computer science than with Java. Therefore, we
now move on to more exciting matters, such as the socket communication
required to provide the interaction.
Integrating
a Communication Class in Your Applet
For an effective implementation, it's necessary to create a class
that handles all the interaction with the server. This class manages
such responsibilities as connecting and disconnecting from the
server as well as the actual sending and receiving of data. By
encapsulating this functionality in a separate class, we can deal
with our problem in a more effective and logical manner.
A more important reason for encapsulating this functionality in
a separate class, however, is the fact that, in a multiuser environment,
the applet must do two things at the exact same time: listen for
user interactions (such as a mouse click) and listen for new information
from the server. The best way to do this is to create a separate
class to handle the server interaction and make that class a thread.
(See Chapter 9, "Threads and Multithreading,"
for details on creating threads.) This threaded class runs continually
for the lifetime of the applet, updating the quilt when required
to do so. With this problem out of our hands, we can allow the
applet class to respond to user interactions without worrying
about the socket connection.
How
to Connect to a Server
Assuming that we have a suitable server running, the actual communication
is not a very complicated process. In essence, the applet needs
only to connect to the server and read and write information to
the appropriate streams. Conveniently, Sun has wrapped most of
these methods and variables in the java.net.Socket
class.
Here is the beginning of our Client
class that handles the actual communication:
import java.net.Socket;
import java.io.*;
public class Client extends Thread
{
private Socket soc;
public void connect ()
{
String host = "www.museum.com";
int port = 2600;
soc = new Socket(host, port);
}
}
Note that it is necessary for you to know both the name of the
host on which the server is running as well as the port on which
the server can accept connections.
In theory, the connect() method
in the preceding class is sufficient to connect to a server on
a known port. However, any experienced programmer knows that what
works in theory does not always work in practice. Consequently,
it is good practice to place all communication statements in a
try-catch block. (Those unfamiliar
with the throw and try-catch
constructs can refer to Chapter 10, "Exception
Handling.") In fact, forgetting to place all statements that
deal with the server in a try-catch
block will produce a compile-time error with the Java compiler.
Consequently, the preceding method should actually look like this:
import java.net.Socket;
import java.io.*;
public class Client extends Thread
{
private Socket soc;
public boolean connect ()
{
String host = "www.museum.com";
int port = 2600;
try {
soc = new Socket(host, port);
}
catch (Exception e)
return(false);
return(true);
}
Note that the method now returns a boolean
variable that corresponds to whether or not it was successful
in connecting.
How
to Communicate with the Server
Inasmuch as different situations require different constructs,
Java supplies several options for the syntax of communication
with a server. Although all such approaches can work, some are
more useful and flexible than others. Consequently, what follows
is a rather generic method of communicating with a server. Keep
in mind that there are some nice wrapper methods in the java.io.InputStream
subclasses that you may want to use to simplify the processing
of the parsing involved with the communication.
Sending Information
Once we have established our connection, the output is then passed
through the java.net.Socket.outputStream,
which is a java.io.outputStream.
Because OutputStream is a
protected variable, however, we cannot reference it as simply
as you may imagine. To access this stream, we must employ the
java.net.Socket.getOutputStream()
method. A simple method to send data resembles the following:
public boolean SendInfo(int choice) {
OutputStream out;
try {
out = soc.getOutputStream();
out.write(choice);
out.flush();
}
catch (Exception e)
return(false);
return(true);
}
Although the preceding code is sufficient to send information
across a socket stream, communication is not that simple in a
true multiuser environment. Because we will have multiple clients
in addition to the server, it is necessary to define a common
protocol that all parties can speak. Although this may be as simple
as a specific order to a series of integers, it is nevertheless
vital to the success of the environment.
Protocols
Although we will adopt a rather simple protocol for this applet, there are a few issues you should keep in mind when developing more complex protocols:
Preface each command with a title, such as a letter or short word. For example, B123 could be the command to buy property lot 123, and S123 could be the command to sell lot 123.
Make all titles of the same format. Doing so makes parsing the input stream much easier. For example, B and S are good titles. B and Sell would cause some headaches.
Terminate each command with a common character. Doing so signifies the end of information and also gives you a starting point in case some of the information becomes garbled. For the sake of simplicity, the newline character (\n) is usually best.
In the case of our quilt-making applet for the museum, each update
packet consists of three things: the x-coordinate of the recently
painted tile, the y-coordinate of the recently painted tile, and
the color to be applied (which can also be represented by an integer).
Additionally, we will use the newline character (\n)
as our terminating character. All this can be accomplished with
the following method:
public boolean SendInfo(int x, int y, int hue) {
OutputStream out;
try {
out = soc.getOutputStream();
out.write(x);
out.write(y);
out.write(hue);
out.write('\n');
/* Now flush the output stream to make sure that everything is sent. */
out.flush();
}
catch (Exception e)
return(false);
return(true);
}
Reading Information
Another factor that complicates the reading of information from
the socket is that we have absolutely no idea when new information
will travel across the stream. When we send data, we are in complete
control of the stream and thus can employ a rather simple method
as we did earlier. When reading from the socket, however, we do
not know when new information will arrive. Thus we need to employ
a method that runs constantly in a loop. As discussed previously,
the best way to do this is to place our code in the run()
method of a threaded class.
A refresher on threads
Before we proceed in our development of this threaded subclass, it is important to review a key concept. As presented in Chapter 9, "Threads and Multithreading," a class that extends the definition of the java.lang.Thread class can continually execute code while other portions of the applet are running. Nevertheless, this is not true of all the methods of the threaded class.
Only the run() method has the power attributed to threads (namely the capacity to run independently of other operations). This method is automatically declared in every thread, but has no functionality unless you override the original declaration in your code. Although other methods can exist within the threaded subclass, remember that only the code contained in the run() method can execute concurrently with the rest of the processes in the application. Furthermore, remember that the run() method of class foo is begun by invoking foo.start() from the master class.
Also note that because we are overriding a method first declared in the java.lang.Runnable interface, we are forced to employ the original declaration: public void run(). Your code won't compile if you choose to use another declaration, such as public boolean run(). (We deal with ways of returning data in the section on sharing information, later in this chapter.)
What results from all this planning is the method shown in Listing 27.1. In reading it, pay attention to how we are able to stop the method from reading from the socket once the user is done; how the method deals with garbled input; and how the method returns information to the main applet. (You will be quizzed later!)
Listing 27.1. Our method to read information from the socket.
public void run() {
int spot = 0;
int stroke[];
stroke = new int[10]; // an array for storing the commands
DataInputStream in;
in = new DataInputStream(soc.getInputStream());
while (running) {
do { // reads the information
try {
stroke[spot] = in.readByte();
}
catch (Exception e)
stroke[spot] = '\n'; // restarts read on error
} while ( (stroke[spot] != '\n') && (++spot < 9) );
// reads until the newline flag or limit of
spot = 0; // array
// resets the counter
quilt.sew(stroke[0],stroke[1],stroke[2]);
}
}
Okay. Time is up. Here are the answers.
First of all, remember that we are designing this class to serve
within a larger applet. It would be rather difficult for the Client
class to decide when the "painting session" is over
because it has no idea what is going on inside the applet. Thus,
the applet, not the Client
class, must have control of when the class will be looking for
information. The best way to retain control of the run()
method is to create a boolean
field (running) inside the
Client class itself. This
variable can be set to true
on connection to the server and can be set to false
when the applet decides to close the connection. Thus, once the
user has decided to close the connection, the run()
method exits the while loop
and runs through to its completion.
Second, remember that while we plan on receiving three integers
followed by a newline character, in real life, things do not always
work as we plan them. Inasmuch as we do not know exactly what
the error will be, there is no ideal way of handling garbled information.
The run() method in Listing
27.1 uses the newline character as its guide, and continues to
read until it reaches one. If, however, there are nine characters
before a newline character, it exits the do
loop regardless and calls the sew()
method. This means that the sew()
method in our main applet must be well constructed to handle such
errors.
Third, and most important, is the means by which the class returns
data to the applet. As discussed earlier, this Client
class will be a subclass of our main applet. Consequently, we
will be able to call all public methods of the applet class. (In
this example, the applet is referenced by the variable quilt.)
Although we will discuss this process in more detail later, keep
in mind that the last line of code simply passes the appropriate
values to a method in the applet that will in turn process them:
quilt.sew(stroke[0],stroke[1],stroke[2]);
A note for advanced readers
You will notice that in our example, all the information coming across the stream is read in the form of integers. Obviously, in the real world, other data types such as characters-or even mixed data types such as three numbers and a letter-could be passed. Although accommodating these data types would require slight alterations to the preceding code, doing so would not be a dramatic hack.
Although you could deal with such a situation by having separate read() statements for each data type, remember the ASCII relationship between characters and numbers. Because the ASCII value of a number is 48 more than that number (for example, the ASCII value for '1' is 1 + 48), you can choose to read all the data in as integers and then translate certain values to their character equivalents-or vice versa, depending on how you sent the information to the server.
Finally, keep in mind that serverInput is a basic java.lang.Inputstream that can be manipulated in several ways using the powers of the java.lang and java.io classes and methods. (See Chapter 12, "The Language Package," and Chapter 14, "The I/O Package," for more details on java.lang and java.io, respectively.)
How
to Disconnect from the Server
So far, we have been able to connect to the server and communicate
with it. From the point of view of the user, we have begun the
application and have successfully painted our quilt. There is
only one essential functionality we haven't included in our Client
class thus far: the capacity to disconnect from the server.
Although the capacity to disconnect from the server may appear
trivial, it is in fact very essential to a successful multiuser
environment. A server can be constructed to accept numerous clients,
but because of hardware constraints, there is an inherent limit
to the number of clients a server can have connected at the same
time. Consequently, if the socket connections remain open even
when the clients leave, the server eventually becomes saturated
with lingering sockets. In general, it's good practice to close
sockets; doing so benefits all aspects of the environment, ranging
from the Internet-access software the user is running to the person
managing the server.
Without further ado, here is an appropriate disconnect()
method for our Client class:
public boolean disconnect () {
running = false;
try {
soc.close()
}
catch (Exception e)
return(false);
return(true);
}
Although the code itself is nothing extraordinary, note the use
of the boolean variable running
(also used in the run() method).
Remember that the function of the running
variable is to serve as a flag that allows the client to listen
to the socket stream. Inasmuch as we do not want the client to
listen to the socket stream after the user has decided to disconnect,
we set the variable to false
in this method. Also note that the running
= false statement comes before the soc.close()
statement. This arrangement is made because regardless of the
success of the disconnect
statement, we no longer want to listen to the stream.
The
Graphical Interface
Now that we have developed the framework of our Client
subclass, let's switch gears to develop the applet class that
will act as the heart of our application. Although it is not extremely
intricate, the applet must be able to respond to user input and
present the user with a friendly and attractive display. Fortunately,
as a Web-based programming language, Java is very well suited
for this. Each applet you create inherits various methods that
enable it to respond to virtually anything the user can do. Furthermore,
the java.awt package provides
us with some excellent classes for creating good-looking interactive
features such as buttons and text areas.
Responding to Input
The most important aspect of a graphical interface is its capacity
to monitor the user's responses to the system. As you saw in Chapter 16,
"The Windowing (AWT) Package," Java supplies us with
an excellent system for doing this in the form of the java.awt.Event
class. Additionally, the java.awt.Component
class, of which java.applet.Applet
is a subclass, provides many methods that we can use to catch
and process events. These methods seize control of the applet
under appropriate conditions and enable the applet to react to
specific user events.
For our museum quilt applet, the user must be able to change the
current color, select a tile, and quit the application. If we
decide that the user will select the tile through a mouse click
and will change colors and quit with the keyboard, we require
two interactive methods from the java.awt.Component
class: keyDown(Event, int)
and mouseDown(Event, int,
int).
In this setup, the keyboard has two purposes: to enable the user
to change colors and to enable him or her to quit. If we choose
to present the user with a pallet of numbered colors (0 to 9,
for example) from which he or she can select, our keyDown()
method can be as simple as this:
public boolean keyDown(Event evt, int key) {
if ( (key >= '0') && (key <= '9') ) { // if a valid key
current_color = key - 48; // converts ASCII key to
return(true); // numeric equivalent
}
else if ( key = 'Q') {
leave(); // a method that will handle
return(true); // cleanup
}
return(false);
}
In this method, current_color
is a field that keeps track of the currently selected color.
If we declare museum to be
an instance of the Client
socket class (which we have almost completed), the mouseDown()
method can be accomplished with the following code:
public boolean mouseDown(Event evt, int x, int y) {
int x_cord, y_cord;
if ( (x >= xoff) && (x =< xoff + xsize) && (y >= yoff) && (y =< yoff + ysize) {
// checks to see if the click was within the
// grid
x_cord = (x - xoff)/scale; // determines the x and y coordinates
y_cord = (y- yoff)/scale; // of the click
museum.sendInfo(x_cord, y_cord, current_color); // makes use of the
// sendInfo method to send
// the data
return(true); // the click was valid
}
return(false); // the click was outside the bounds of the
// grid
}
Note that in the previous method, it is necessary to have already
declared the x and y offsets, the size of each tile (scale), and
the size of the grid itself as global fields of the class.
Displaying Information
Chapter 19, "Java Graphics Fundamentals,"
gave an overview of how to create a background and fill it in
with whatever you want. In a dynamic graphical interface, however,
various properties will change and thus must be tracked in some
manner. Our quilt application may consist of nothing more than
a single colored background on which tiles of different colors
are drawn. Consequently, the only information we are concerned
with is the colors (if any) that have been assigned to the individual
tiles.
A simple way of doing this is to create an array of java.awt.Color
(hues[ ]) and assign each color to be used a given
value (hues[1] = Color.blue,
for example). Thus, we can store the design in simple array of
integers, such as tiles[ ][ ],
with each integer element representing a color. If we do so, our
paint() method can be accomplished
by the following code:
public void paint(Graphics g) {
for (int i = 0; i < size; i++) {
for(int j = 0; j < size; j++) {
g.setColor(hues[ (tiles[i][j]) ]);
g.fillRect( (i*scale+xoff) , (j*scale+yoff), scale,scale);
}
}
}
Note that in the preceding example, g.fillRect(
(i*scale+xoff) , (j*scale+yoff), scale,scale) paints
squares with length and width equal to a final field, scale.
These squares are located on the grid with respect to the initial
x and y offsets.
How
to Thread the Client
Class
So far, we have been successful in creating the interface that
the user will find on our Web page, as well as some of the methods
necessary to facilitate the user's interaction with the applet.
We have also assembled a Client
subclass that will serve as our means of communication. Our next
step is to integrate the Client
class within the applet class to produce a cohesive application.
Nevertheless, it is important to keep in mind that we are not
using this subclass merely as a means of keeping the information
separate. The main reason for creating a separate class is that,
by making it a thread as well, we can perform two tasks at
the exact same time.
Note
Although it may seem as if we can perform two tasks at the exact same time, this is not entirely true on a standard single-processor computer. What Java does (and other time-sharing applications such as Windows as well) is to allocate "time-slices" to each thread, allowing each thread to run for a brief moment before switching control to another thread. Because this process is automatically done by the Java virtual machine, and because the time for which a given thread is not running is so small, we can think of the two threads as running at the same time.
Although we almost forget about its existence, we must retain
some control over the Client
class. We do not want it to check for new commands before the
socket connection has been opened or after it has been closed.
Additionally, we want to be able to control socket events such
as opening and closing in a manner that isolates them from the
main applet, but also impacts the status of the run()
method. Conveniently for us, we developed the Client
class in such a manner: We kept the connect()
and disconnect() methods
separate entities, both of which have control over the run()
method (by means of the boolean
variable running).
Consequently, within the applet class, the code necessary to control
communications is quite simple:
public class Project extends Applet {
public Client museum;
.
.
.
public void init() {
museum = new Client();
.
.
museum.connect();
museum.start();
}
.
.
public void leave() {
museum.disconnect();
}
}
First of all, note that this is the first time we have dealt with
the applet itself. So far, its only important property is that
it is named Project.
Also note the use of the constructor Client().
Like any other class, the Client
class requires not only a declaration, but also a new
statement to actually create and allocate memory for the class.
We deal with this constructor again in the next section.
Finally, note that we established the connection by means of the
museum.connect() statement
located in the init() method.
Most likely (and in the case of the museum applet), you will want
to establish the socket stream as soon as the applet starts up.
The most logical place for the museum.connect()
statement is in the init()
method. Nevertheless, you can place this code anywhere you like
in your applet. (You can, for example, have a Connect to Server
button somewhere in your applet.)
Caution
Although giving the user the ability to initiate connection may be a good idea, be careful that you do not allow the same user to connect to the server more than once without first disconnecting. Neglecting to screen for multiple connections for the same user can lead to all sorts of headaches and improper results.
How
to Share Information
We have created the framework of both the Project
(applet) and Client classes
and have begun the process of intertwining them by creating an
instance of the Client class
in the Project class. However,
there is more to the interaction between the two classes than
what we have seen thus far.
Remember that the purpose of the Client
subclass is to provide information to the applet. Nevertheless,
we are required to employ the run()
method of the threaded Client
class, which does not allow us to return information through a
simple return() statement.
Also, in most cases (as well as in the museum application), we
must return several pieces of information (the x and y coordinates
of the tile being painted as well as its color).
With a little construction, we can easily solve this problem.
First, we must enable the Client
class to refer to the Project
class; this involves a few alterations to the Client
class. The changes will allow the Client
class to accept the Project
applet class in its constructor method and make use of it during
its lifetime:
public class Client extends Thread
{
private Project quilt;
public Client (Project proj) {
quilt = proj;
}
...
}
Note
In this setup, the parent class is required to pass itself as an argument to the constructor method. Therefore, when we add the code to do so to our program, the syntax resembles the following:
museum = new Client(this);
What exactly does the preceding code accomplish? First, it establishes
a constructor method for the Client
class. (Remember that, as in C++, constructor methods must have
the same name as the class itself.) Although this constructor
may serve other purposes, its most important function is to accept
a reference to a Project
class as one of its arguments.
Further, the code creates a public variable, quilt,
of type Project. By doing
so, each method of the Client
class can now reference all public methods and variables in the
Project class. For example,
if we had the following definitions in the Project
class, the Client subclass
could reference quilt.tiles_remaining
and quilt.sew():
public int tiles_remaining;
public void sew(int x, int y, int hue) {
...
}
The
Translating Method
Okay, time for an assessment of where we stand. The museum applet
is now able to do the following:
Connect to the server
Monitor the user's actions
Display the current design
Receive and send information
Create a client "within" the
main applet
Enable the client to reference the main
applet
At this point, whenever the user clicks a square, the "request
to paint" is immediately sent to the server by the sendInfo()
method. But we have not yet developed a true method of translating
a command after it comes back across the server stream.
We have, however, laid the foundation for such a process. By enabling
the Client subclass to refer
to all public variables and methods of the Project
applet class, we have given ourselves access to all the necessary
information. In fact, we have many options for updating the data
in the applet once it has been parsed from the server stream.
Nevertheless, some approaches are much better than others.
The most secure and flexible approach is to create a "translating"
method in the applet class, such as the sew()
method in the Project class-the
sew() method has been mentioned
several times in this chapter. This method serves as the bridge
between the client and applet classes. Why is this approach the
best? Why can't we create some public variables in the Project
class that can be changed by the Client
class? There are several reasons.
Why Use a Translating Method?
The first reason we use a translating method is that doing so
makes life a great deal easier for the programmer. By employing
a translating method, we can maintain the encapsulation enjoyed
thus far in our application. The sew()
method is contained entirely within the Project
class and thus has all the necessary resources (such as private
variables that would be hidden from the Client
subclass). Furthermore, when we actually code the application,
we can focus on each class independently. When we code the Client
class, we can effectively forget how the sew()
method works; when we code the Project
class, we can trust that the appropriate information will be sent
to it.
The second reason for having the Client
class rely on a foreign method is flexibility. If we decided to
revamp this application and changed the manner in which we store
the data, we would have no need to drastically change the Client
class. In fact, in the worst case, the only changes necessary
would be to increase the amount of data being read from the stream
and the number of parameters passed to the sew()
method.
The third reason for employing a translating method is that by
linking the two classes with a method rather than direct access,
we can prevent the corruption and intermingling of data that can
occur when two processes attempt to access the same data at the
same time. For example, if we made tiles[
][ ] (the array that contains the design in the applet
class) to be public, we could
change the colors with a statement such as this one:
quilt.tiles[(stroke[0])][(stroke[1])] = stroke[2];
However, what would happen if, at the exact moment that the paint()
method was accessing tiles[1][2],
the Client class was changing
its value? What if more data had to be stored (the color, the
design, the author's name, and so on)? Would the paint()
method get the old data, the new data, a mixture, or none? As
you can see, this could be a catastrophic problem. However, if
we use a separate translating method in our applet class, this
problem can easily be solved through use of the synchronized modifier.
If our applet grew to the point that this problem presented itself,
we could make the paint()
method synchronized and place any necessary statements in a synchronized
block. As a result, these portions of our code would never be
able to run at the exact same time, thereby solving our problem.
How to Create a Translating Method
Now that we have seen why the sew()
method is necessary, let us discuss its actual code. As mentioned
earlier, each command in the museum application consists of three
integers: the x coordinate, the y coordinate, and the new color.
Also remember from the discussion of the Client
class that the sew() method
must be able to handle corrupted data. Consequently, the sew()
method could look something like this:
public void sew(int x, int y, int hue) {
if ( (x>=0) && (x <= size) && ( y >= 0) &&
(y <= size) && (hue >= 0) && (hue <= _9) ) { // 9 colors
tiles[x][y] = hue;
repaint();
}
}
The first statement is self-explanatory, but the second statement
deserves some comment (if not a complete explanation). The first
statement performs the actual task of changing the color on the
tile-even though the user does not see anything unless the repaint()
method is called. Thus, by setting the array and calling repaint(),
the sew() method updates
the quilt in the mind of the computer as well as on the screen
itself.
In a moment, we will also harness the sew()
method to perform another vital function for us.
Now that we have developed the entire functionality of the Client
subclass, let's tie it together once and for all and put it aside.
First look the final product over, as shown in Listing 27.2.
Listing 27.2. The final version of the Client
class.
import java.net.Socket;
import java.io.*;
public class Client extends Thread
{
private Socket soc;
private Boolean running;
private Project quilt;
public Client (Project proj) {
quilt = proj;
}
public void connect () {
String host;
int port = 2600;
host = "www.museum.com";
try {
soc = new Socket(host, port);
}
catch (Exception e)
return(false);
}
public boolean SendInfo(int x, int y, int hue) {
OutputStream out;
try {
out = soc.getOutputStream();
out.write(x);
out.write(y);
out.write(hue);
out.write('\n');
out.flush();
}
catch (Exception e)
return(false);
return(true);
}
public void run() {
int spot;
int stroke[];
stroke = new int[10]; // an array for storing the commands
spot = 0;
DataInputStream in;
in = new DataInputStream(soc.getInputStream());
while (running) {
do { // reads the information
stroke[spot] = in.readByte();
} while ( (stroke[spot] != '\n') && (++spot < 9) );
// until the newline flag or limit of
// array
spot = 0; // resets counter
quilt.sew(stroke[0],stroke[1],stroke[2]);
}
public boolean disconnect () {
running = false;
try {
soc.close()
}
catch (Exception e)
return(false);
return(true);
}
}
Although none of the methods used in Listing 27.2 are new, there
is a very important piece of the class that has been completely
installed for the first time-the boolean
field running. As a field,
it is accessible to all methods within the Client
subclass. Nevertheless, by making it private, we have ensured
that, if it is changed, its change will be associated with an
appropriate change in the status of the socket connection-a connection
or disconnection, for example.
Although the Client class
may require application-dependent changes, the version shown in
Listing 27.2 is sufficient to satisfy most requirements. However,
as of yet, we have dealt very little with the applet itself. This
is primarily because each multiuser applet is inherently very
different. Nevertheless, there are a few topics that should be
discussed if you want to develop a quality multiuser applet.
Advanced
Topics of Applet Development
What we have developed thus far in the chapter is entirely sufficient
to satisfy the demands of the museum. Nevertheless, there are
several other topics regarding efficiency, architecture, and appearance
that we have not dealt with yet. The next few sections discuss
these issues as well as those regarding the development of an
effective server on the other end.
Animation and Dynamic Changes to the Screen
Although a programmer may appreciate the intricacies of a multiuser
environment, users are attracted to those applications that look
nice and help them to do interesting things. Although a multiuser
environment is certainly one of the latter variety, we must still
keep in mind the first criteria: making the applet look nice and
run smoothly.
Although this chapter's purpose is not to deal with such issues
as the graphical layout of applets, the communication structures
developed in this chapter do have a direct impact on the "smoothness"
of the changes that will occur on the user's screen. In the case
of the museum applet developed earlier in this chapter, every
time a user clicks on a tile, his or her request to paint must
be sent to the server and then returned to the client. Once this
information is finally returned to the client, the applet must
then repaint each and every tile. Although this process occurs
in a matter of seconds, for users who have to wait three seconds
to see their changes reflected, as well as users who can actually
observe each tile being repainted, this process may be a few seconds
too slow.
A shot of Java jargon
As we have learned, the method that actually handles the creation of graphics in a Java applet is paint(). Although various actions such as drawing lines, changing the background color, and displaying messages may be performed in this method, this functionality is nevertheless commonly referred to as the "painting" of the applet.
This slow graphical process can produce a very choppy effect,
commonly referred to as "flickering," and can nevertheless
be easily remedied. Although this requires a few changes to our
applet, the main idea behind it is noticing that we do not
have to repaint those items that have not changed. Even though
this may seem a rather elementary observation, when developing
an applet, it may seem a great deal easier to simply repaint the
entire screen. (In fact, if you take a look at some of the Java
applets on the Web, you will notice that many applets exhibit
the problem of flickering.)
In the case of our museum applet, you will see that we have to
paint only the tile that has been changed. Because we know the
coordinates and size of each tile, this should not be much of
a problem at all. It would seem as if all we have to do is modify
the paint() method to paint
only one tile; when the sew()
method calls repaint(), only
the most recently changed tile must change.
There are two problems with such a simplistic approach. First
of all, the paint() method
is called not only by explicit calls to repaint(),
it is also called whenever the applet "appears" (such
as when it first starts up or after the browser has been hidden
behind another application). If we change the paint()
method to paint only one tile, we may be left with only one tile
on the screen, rather than the full quilt.
Another problem is that we cannot pass information to the paint()
method because (as with the run()
method for threads) we are overriding an already created method.
Don't worry. These problems can be handled easily. Peruse the
additions to our code in Listing 27.3 and see whether you can
determine how the two problems were solved.
Listing 27.3. Revised code to provide smoother animation.
public class Project extends Applet {
private boolean FullPaint;
private java.awt.Point Changed;
public void init() {
...
FullPaint = true;
Changed = new Point();
}
public void paint(Graphics g) {
if (FullPaint) {
for (int i = 0; i < size; i++)
for(int j = 0; j < size; j++) {
g.setColor(hues[ (tiles[i][j]) ]);
g.fillRect( (i*scale+xoff) , (j*scale+yoff), scale,scale);
}
}
else
g.fillRect( (Changed.x*scale+xoff) , (Changed.y*scale+yoff), scale,scale);
FullPaint = true;
}
public void sew(int x, int y, int hue) {
if ( (x>=0) && (x <= size) && ( y >= 0) &&
(y <= size) && (hue >= 0) && (hue _<= 9) ) { // 9 colors
tiles[x][y] = hue;
Changed.x = x;
Changed.y = y;
FullPaint = false;
repaint();
}
}
}
Okay, let's see how the problems were solved.
Most notably, we added two new variables, one for each problem.
The first variable is a boolean
named FullPaint. As you can
probably guess, its function is to tell the paint()
method whether it should repaint all the tiles or just the newest
one. Note that the key to this variable is that we want it to
be true as much as possible
so that we prevent the "one tile quilts" scenario discussed
earlier. In this setup, the FullPaint
variable is set to false
only one line before the call to repaint()
and is reset to true at the
end of the paint() method.
Caution
Remember to reset the FullPaint to true at the end of your paint() statement! If you don't, you'll defeat the purpose of the variable-and ruin the appearance of your applet.
How did we deal with the second problem of being unable to inform
the paint() method which
tile has changed? Notice the use of the private
variable Point Changed.
Because the field is accessible by all methods, by setting the
x and y values of this variable in the sew()
method, we can indirectly pass this information along to the paint()
method.
A look inside the evolution of the language
The code in Listing 27.3 employed Changed, a variable of type Point, which can be found in the java.awt package. This is a useful class, but it has not always been part of Java. When the alpha releases came out, authors (especially those who were making graphical interfaces) were forced to develop their own Point-type classes. Even though this was not much of a problem, Sun chose to respond to this need by including the Point class in the beta and later API libraries.
Ensuring that the Sockets Close Properly
Although we have developed a sufficient disconnect()
method, remember that things do not always work as well as they
should. In fact, the disconnect()
method in the socket class has had some problems in Java-further
complicated by the fact that we are running our applet through
a browser. As discussed earlier, not closing sockets can become
a serious problem in a multiuser environment. It is good practice
to develop into your protocol a "close" command. Although
it should resemble the other commands in your protocol, the close
command need not be anything elaborate. In the museum example,
if 123 were the command to
paint tile (1,2) in color
3, sending -123
could be the defined close command because it would be an aberration
from the rest of the commands-something easily distinguishable.
Are You Still There?
Another related issue is that some users may not tell the applet
that they are leaving-so the disconnect()
method does not have a chance to execute. For example, the user
may go to another HTML page or his or her computer might be struck
with a sudden power outage. To handle either case, it is advisable
to have some method by which the server can ensure that all its
clients are still active.
A simple way of doing this is to develop another command into
your protocol, the semantics of which are irrelevant. Regardless
of the syntax, the server should send out some kind of "are
you there?" command periodically. In terms of the applet
itself, you should develop the translating method in such a manner
that when such a command is received, it responds with an appropriate
answer.
Consequently, if this functionality is built into the applet,
the server will know that any client that does not respond within
a reasonable amount of time (20 seconds, for example) is no longer
active and can be closed.
Requests versus Commands
This chapter has repeatedly referred to the information passed
from each client to the server as a request that describes
what the user would like as opposed to a command that defines
a particular action to be taken. Furthermore, note that in the
system developed in this chapter, although a user may click a
square, his or her screen is not updated until the request has
been echoed back from the server. These two facts may seem a bit
abnormal: It may seem more natural to have the applet update itself
and then inform the server of what was done. As you will soon
see, these two procedures are very necessary for the exact same
reason.
In the example of the museum applet, users can paint only blank
(unpainted) tiles. Imagine for a moment what would happen if user
A decided to paint tile (1,2) black, and a moment later user B
decided to paint tile (1,2) yellow. User A's command is received
first and user B's command is received second. (It is impossible
for the server to receive two commands at the same time on the
same socket. Subsequent commands are normally placed in a queue.)
If the applets were designed to paint themselves before
the commands were echoed, user A and user B would have different
images of the same quilt at the same time. Disaster!
Consequently, in a multiuser environment in which the status is
really important (or even in a game application), it is essential
that you develop your environment in such a manner that the status
of whatever is being displayed on the screen (such as the position
of the players) is updated only by what is returned from
the server. That means that the server should be developed so
that any invalid requests (such as a request to paint an already
painted tile) are simply "sucked up" by the server and
are not echoed to any clients. If two users attempt to paint the
same tile, only the request of the first user should be honored,
and the second request should simply be swallowed up by the server.
Even better, you could enable your server to send user-specific
error messages that cause some form of error message on the user's
screen.
Limiting and Keeping Track of Users
In several situations-such as error messages-the server wants
to speak with just one user. Additionally, your situation may
require that only five users be in the environment at a given
moment. These are real-world problems, but ones that are easily
solved.
At the heart of these solutions is the idea of having more than
one server for your multiuser environment. Such a configuration
may involve one central server that has the power to spawn other
servers (on other ports), or a central server that acts as a "gatekeeper,"
accepting clients only if there are openings (see Figure 27.2).
Figure 27.2 : An example of a gatekeeper system.
Either case requires the development of a richer protocol, but
also provides you with opportunities for greater power over your
environment. For example, you can establish several two-player
games managed by a central server. By designing the applet to
connect on a given port (1626,
for example) and to request entrance to a game, the server on
that port can act as a manager by beginning a new game on another
port (1627, for example)
and informing the applet of the new port number. The applet can
then disconnect from port 1626,
connect to port 1627, and
wait for the central server to send another user to the same port.
Once the central server has sent two players to port 1627,
it starts a new server on the next available port (1628,
for example) and can continue in this manner indefinitely (see
Figure 27.3).
Figure 27.3 : An example of a central server/children server system.
In the cases of the central server/children server system and
the gatekeeper system, the server can assign each client an identification
number at connection time. This arrangement not only allows the
server to keep track of who is sending the information, it also
enables the server to send user-specific messages (for example,
Player One: You can't do that).
Both of these tasks can be facilitated by appending a simple identification
letter or number to each command in the protocol. (B112
could mean, for example, that player 1
just bought plot 12.)
Summary
Even though this chapter did not deal with many topics, keep in
mind that the power of Java is its abstract capability to facilitate
a multiuser environment, not its specific lexical constructs.
Here are a few issues relating to the Java language that you should
keep in mind when developing a multiuser environment:
Develop a user-friendly graphical interface.
By using the java.awt.Event
class, you can respond to the user's interactions in an effective
manner.
Create a client class that extends the
java.lang.Thread class.
Although you can create as many methods as you want within this
class, the best approach is to harness the run()
method's capacity to run concurrently to develop an effective
means of reading data from the stream. Also, remember to use some
form of flag that provides you with a way of controlling the lifetime
of the run() method.
Develop the framework by which the applet
and client classes will be able to communicate. This is best done
by making an instance of the client class a field in the applet
class. By making the appropriate methods of the client class public,
your applet class can perform such tasks as connecting, disconnecting,
and sending data.
Create a "translating" method
in your applet class. Doing so enables the client class to send
the parsed data to the applet class to elicit the proper response.
Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.
Wyszukiwarka
Podobne podstrony:
ch27ch27 (13)CH27 (11)ch27ch27 (4)ch27DK2192 CH27ch27ch27 (3)ch27 (2)CH27więcej podobnych podstron