In this section, you’ll see a simple socket-based client and a simple socket-based server.
The client is implemented as an applet, and the server is implemented as a server. As presented, the client and server must run on the same computer, but you can easily
change them to run on separate computers. You should initially run the client and server on the same computer, running the applet using appletviewer to satisfy yourself that it works. Only then should you consider running the client and server on different computers or running the client using a Web browser. You may find that you need to adjust the security configuration of your browser, particularly if you modify the applet to access a remote server.
The Socket-Based Client Applet
Figure 13.2 shows the SimpleClient applet, which connects to the SimpleServer
application you’ll meet in the next section. The application is a fortune cookie system (the type popular on UNIX systems) that provides a random message of the day. The client sends one of two messages—”hello” or “bye”—triggered by buttons on its window. In response, the server sends three random messages (fortune cookies). That way, you can choose the message you prefer and ignore the others, a feature not provided by run-of- the-mill fortune cookie servers.
Figure 13.2: The SimpleClient applet models a typical network client.
First, let’s see how to run the application so you can try it for yourself; then we’ll study its source code. Here are the steps you should follow. The following instructions assume you’re using Windows 9x/NT (you’ll need to adapt them if you’re using some other operating system):
1. Copy the source files SimpleClient.java, SimpleServer.java, and SimpleClient.html from the CD-ROM to your hard drive.
2. Compile the two Java source files.
3. Start the server by typing this:
java SimpleServer
Soon, the server should respond with a “ready” message.
4. Open a new DOS window and start the client by typing this:
appletviewer SimpleClient.html
5. Click the Hello button or the Bye button and observe the server response.
6. When you’re done, exit the appletviewer by clicking the window’s close box. Exit the server by pressing Ctrl+C.
If anything goes wrong, you’ll see a Java stack trace in either the server window or client window. One possible source of problems is the port number, which is hard-coded as 1234. If this port is unavailable, the application will fail. Fixing this is simple: Choose another port number, change both the client and server source files, recompile them, and run the application.
Listing 13.2 shows the source code of the SimpleClient applet. Let’s study it carefully.
Notice that the port number on which the server listens is hard-coded as a constant.
Notice also that the applet contains a BetterSocket as a field. The init method is straightforward, dealing exclusively with user-interface issues. The class also includes a fatalError method that prints a stack trace and closes the BetterSocket.
LISTING 13.2 SimpleClient.java —A SIMPLE SOCKET-BASED CLIENT
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
public class SimpleClient extends Applet {
public static final int PORT = 1234;
Button theHelloButton = new Button("Hello");
Button theByeButton = new Button("Bye");
TextArea theResponse = new TextArea(5, 64);
BetterSocket theSocket;
public void init() {
setLayout(new BorderLayout());
Panel p = new Panel();
add(p, BorderLayout.NORTH);
add(theResponse, BorderLayout.SOUTH);
p.setLayout(new GridLayout(1, 0));
p.add(theHelloButton);
p.add(theByeButton);
theResponse.setEditable(false);
theHelloButton.addActionListener(new HelloHandler());
theByeButton.addActionListener(new ByeHandler());
}
public void fatalError(Exception ex) {
ex.printStackTrace();
try {
theSocket.close();
}
catch (Exception ex1) { ; } }
class HelloHandler implements ActionListener {
public void actionPerformed(ActionEvent evt) {
try
{
theResponse.setText("");
theSocket = new BetterSocket(
getCodeBase().getHost(), PORT);
theSocket.println("Hello");
theResponse.append(theSocket.readLine() + "\n");
theResponse.append(theSocket.readLine() + "\n");
theResponse.append(theSocket.readLine() + "\n");
theSocket.close();
}
catch (Exception ex) { fatalError(ex); } }
}
class ByeHandler implements ActionListener {
public void actionPerformed(ActionEvent evt) {
try {
theResponse.setText("");
theSocket = new BetterSocket(
getCodeBase().getHost(), PORT);
theSocket.println("Bye");
theResponse.append(theSocket.readLine() + "\n");
theResponse.append(theSocket.readLine() + "\n");
theResponse.append(theSocket.readLine() + "\n");
theSocket.close();
}
catch (Exception ex) { fatalError(ex); } }
} }
The ActionListener s—HelloHandler and ByeHandler—do the interesting work.
Each is similar to the other, so we’ll focus on HelloHandler. Its actionPerformed method instantiates a BetterSocket, using the host address and port number. It obtains the host address by using the Applet method getCodeBase, which returns a URL that specifies the host that served the applet. The URL.getHost method returns the host portion of the URL. The actionPerformed method sends a println message to the BetterSocket, causing it to transmit the message argument—the String “Hello”—to the server. The actionPerformed method then reads the server’s three-line response, displays the results, and closes the socket.
That’s all there is to it: As promised, sockets are really quite simple to use. Let’s move on to study the server program.
The Socket-Based Server
Listing 13.2 shows the server program that works with the client you just studied. The server’s static main method instantiates a SimplerServer object and invokes its init method. The init method’s first task is to access the server’s cookies. To store the cookies, the server uses two text files—one containing “Hello” fortune cookies and one containing “Bye” fortune cookies. The init method reads these files and stores the
cookies in two Vectors—one containing “Hello” messages and the other containing
“Bye” messages.
LISTING 13.3 SimpleServer.java —A SIMPLE SOCKET-BASED SERVER
import java.net.*;
import java.io.*;
import java.util.*;
public class SimpleServer {
public static final int PORT = 1234;
ServerSocket theSocket;
BetterSocket theClient;
Vector theHello = new Vector();
Vector theBye = new Vector();
public static void main(String [] args)) {
new SimpleServer().init();
}
public void init() {
try {
BufferedReader in;
String line;
in = new BufferedReader(
new InputStreamReader(
new FileInputStream("hello.txt")));
while ((line = in.readLine()) != null) theHello.addElement(line);
in.close();
in = new BufferedReader(
new InputStreamReader(
new FileInputStream("bye.txt")));
while ((line = in.readLine()) != null) theBye.addElement(line);
in.close();
theSocket = new ServerSocket(PORT);
while (true) {
System.err.println("Server is ready.");
theClient = new BetterSocket(
theSocket.accept());
System.err.println("Server processing request.");
String s = theClient.readLine();
System.out.println("Read " ++ s + " from
client..");
if (s.equalsIgnoreCase("hello")) sendGreeting(theHello);
else if (s.equalsIgnoreCase("bye")) sendGreeting(theBye);
else
System.err.println("Invalid request: " ++ s);
theClient.close();
System.err.println("Processing complete.");
} }
catch (Exception ex) {
ex.printStackTrace();
} }
public void sendGreeting(Vector v) throws IOException
{
int size = v.size();
for (int i = 0; i < 3; i++) {
int n = (int) (Math.random() * size);
theClient.println((String) v.elementAt(n));
} } }
The server then creates a ServerSocket and enters an endless loop that processes client requests indefinitely. In the loop, it displays a “ready” message and then invokes the accept method. The accept method blocks execution until the ServerSocket receives a client request; then it returns a Socket. The init method wraps the Socket in a BetterSocket to simplify handling the input and output streams. Then it reads the command—”Hello” or “Bye”—sent by the client by using the readLine method. It tests the contents of the command String and passes an appropriate argument to the sendGreeting method, which sends the server’s response. Finally, it closes the socket and loops, issuing another accept message that readies the server to process another client request.
The sendGreeting method uses the Math.random method to generate random numbers it uses to pick fortune cookies. It sends each fortune cookie to the client by using the println method. As you can see, the server is only a little more complex than the client.
Socket programming is so easy that you may wonder how it could possibly lead to the complications cited earlier in the chapter. The sample application features quite simple interaction between the client and server. The client sends one of two possible messages, and the server responds, in either case, with a three-line response. It’s not socket
programming as such that can become complicated, it’s the client-server dialog. When clients and servers exchange dozens of message types, each requiring transmission of structured data, such as arrays or objects, programs rapidly become complicated.
Therefore, the original advice stands. Use sockets only for simple data transfers; choose another technology when the data structure is more sophisticated than a simple byte stream.