Java °æ (¾«»ªÇø)
×÷ ¼Ò: topcon (×½¹í) on board 'java'
Ìâ Ä¿: Java Socket Programming(70K)
À´ Ô´: ¹þ¶û±õ×϶¡ÏãÕ¾
ÈÕ ÆÚ: Thu Sep 26 16:55:37 1996
³ö ´¦: jqc@pclinux.hit.edu.cn
+ An Introduction to Sockets
+ Socket Transmission Modes
+ Java Connection-Oriented Classes
+ Server Sockets
+ Iterative and Concurrent Servers
+ Java Datagram Classes
+ Receiving Datagrams
+ Sending Datagrams
+ Datagram Servers
+ Datagram Clients
+ Applet Security and Sockets
+ Chapter Project: HTTP Server Application
and Client Applet
+ HTTP Primer
+ Basic Web Server
+ HTTPrequest Class
+ BasicWebServer Class
+ Client Datagram Applet
+ DGTP Client
+ DGTP Server Class
+ Client Applet
+ Adding the DGTP Server Thread
+ Summary
— 9 —
Java Socket Programming
To demonstrate full Java client/server applet connectivity, an applet
server is necessary. This chapter initiates the development of a Java HTTP
server. Before beginning the server, however, you need some background
knowledge of socket programming. This chapter begins with a socket overview
and is followed by an exploration of Java's socket classes. The remainder
of the chapter will delve into constructing a Java HTTP web server and a
client/server applet.
After reading this chapter, you should be able to do the following:
* Understand the socket abstraction
* Know the different modes of socket operation
* Have a working knowledge of the HTTP protocol
* Be capable of applying the Java socket classes
* Understand applet socket use and limitations
* Comprehend the HTTP Java server
An Introduction to Sockets
The computers on the Internet are connected by the TCP/IP protocol. In the
1980s, the Advanced Research Projects Agency (ARPA) of the U.S. government
funded the University of California at Berkeley to provide a UNIX
implementation of the TCP/IP protocol suite. What was developed was termed
the socket interface, although you might hear it called the Berkeley-socket
interface or just Berkeley sockets. Today, the socket interface is the most
widely used method for accessing a TCP/IP network.
A socket is nothing more than a convenient abstraction. It represents a
connection point into a TCP/IP network, much like the electrical sockets in
your home provide a connection point for your appliances. When two
computers want to converse, they each use a socket. One computer is termed
the server—it opens a socket and listens for connections. The other
computer is termed the client; it calls the server socket to start the
connection. To establish a connection, all that's needed is a destination
address and a port number.
Each computer in a TCP/IP network has a unique address. Ports represent
individual connections within that address. This is analogous to corporate
mail—each person within a company shares the same address, but a letter is
routed within the company by the person's name. Each port within a computer
shares the same address, but data is routed within each computer by the
port number. When a socket is created, it must be associated with a
specific port—this is known as binding to a port.
Socket Transmission Modes
Sockets have two major modes of operation: connection-oriented and
connectionless. Connection-oriented sockets operate like a telephone; they
must establish a connection and a hang up. Everything that flows between
these two events arrives in the same order it was sent. Connectionless
sockets operate like the mail—delivery is not guaranteed, and multiple
pieces of mail may arrive in a different order than they were sent.
Which mode to use is determined by an application's needs. If reliability
is important, then connection-oriented operation is better. File servers
need to have all their data arrive correctly and in sequence. If some data
was lost, the server's usefulness would be invalidated. Some applications—a
time server, for example—send discrete chunks of data at regular intervals.
If the data became lost, the server would not want the network to retry
until the data was sent. By the time the data arrived, it would be too old
to have any accuracy. When you need reliability, be aware that it does come
with a price. Ensuring data sequence and correctness requires extra
processing and memory usage; this extra overhead can slow down the response
times of a server.
Connectionless operation uses the User Datagram Protocol (UDP). A datagram
is a self-contained unit that has all the information needed to attempt its
delivery. Think of it as an envelope—it has a destination and return
address on the outside and contains the data to be sent on the inside. A
socket in this mode does not need to connect to a destination socket; it
simply sends the datagram. The UDP protocol promises only to make a
best-effort delivery attempt. Connectionless operation is fast and
efficient, but not guaranteed.
Connection-oriented operation uses the Transport Control Protocol (TCP). A
socket in this mode needs to connect to the destination before sending
data. Once connected, the sockets are accessed using a streams interface:
open-read-write-close. Everything sent by one socket is received by the
other end of the connection in exactly the same order it was sent.
Connection-oriented operation is less efficient than connectionless, but
it's guaranteed.
Sun Microsystems has always been a proponent of internetworking, so it
isn't surprising to find rich support for sockets in the Java class
hierarchy. In fact, the Java classes have significantly reduced the skill
needed to create a sockets program. Each transmission mode is implemented
in a separate set of Java classes. The connection-oriented classes will be
discussed first.
Java Connection-Oriented Classes
The connection-oriented classes within Java have both a client and a server
representative. The client half tends to be the simplest to set up, so it
will be covered first.
Listing 9.1 shows a simple client application. It requests an HTML document
from a server and displays the response to the console.
Listing 9.1. A simple socket client.
import java.io.*;
import java.net.*;
/**
* An application that opens a connection to a web server and reads
* a single Web page from the connection.
* NOTE: "merlin" is the name of my local machine.
*/
public class SimpleWebClient {
public static void main(String args[])
{
try
{
// Open a client socket connection
Socket clientSocket1 = new Socket("merlin", 80);
System.out.println("Client1: " + clientSocket1);
// Get a Web page
getPage(clientSocket1);
}
catch (UnknownHostException uhe)
{
System.out.println("UnknownHostException: " + uhe);
}
catch (IOException ioe)
{
System.err.println("IOException: " + ioe);
}
}
/**
* Request a Web page using the passed client socket.
* Display the reply and close the client socket.
*/
public static void getPage(Socket clientSocket)
{
try
{
// Acquire the input and output streams
DataOutputStream outbound = new DataOutputStream(
clientSocket.getOutputStream() );
DataInputStream inbound = new DataInputStream(
clientSocket.getInputStream() );
// Write the HTTP request to the server
outbound.writeBytes("GET / HTTP/1.0\r\n\r\n");
// Read the response
String responseLine;
while ((responseLine = inbound.readLine()) != null)
{
// Display each line to the console
System.out.println(responseLine);
// This code checks for EOF. There is a bug in the
// socket close code under Win 95. readLine() will
// not return null when the client socket is closed
// by the server.
if ( responseLine.indexOf("</HTML>") != -1 )
break;
}
// Clean up
outbound.close();
inbound.close();
clientSocket.close();
}
catch (IOException ioe)
{
System.out.println("IOException: " + ioe);
}
}
}
---------------------------------------------------------------------------
[Image]NOTE: The examples in this chapter are coded as applications so as
to avoid security restrictions. Run the code from the command line java
ClassName.
---------------------------------------------------------------------------
Recall that a client socket issues a connect to a listening server socket.
Client sockets are created and connected by using a constructor from the
Socket class. The following line creates a client socket and connects it to
a host:
Socket clientSocket = new Socket("merlin", 80);
The first parameter is the name of the host you want to connect to; the
second parameter is the port number. A host name specifies only the
destination computer. The port number is required to complete the
transaction and allow an individual application to receive the call. In
this case, 80 was specified, the well-known port number for the HTTP
protocol. Other well-known port numbers are shown in Table 9.1. Port
numbers are not mandated by any governing body, but are assigned by
convention—this is why they are said to be "well known."
Table 9.1. Well-known port numbers.
Service Port
echo 7
daytime 13
ftp 21
telnet 23
smtp 25
finger 79
http 80
pop3 110
Because the Socket class is connection oriented, it provides a streams
interface for reads and writes. Classes from the java.io package should be
used to access a connected socket:
DataOutputStream outbound = new DataOutputStream(
clientSocket.getOutputStream() );
DataInputStream inbound = new DataInputStream( clientSocket.getInputStream()
);
Once the streams are created, normal stream operations can be performed:
outbound.writeBytes("GET / HTTP/1.0\r\n\r\n);
String responseLine;
while ( (responseLine = inbound.readLine()) != null)
{
System.out.println(responseLine);
}
The above code snippet requests a Web page and echoes the response to the
screen. When the program is done using the socket, the connection needs to
be closed:
outbound.close();
inbound.close();
clientSocket.close();
Notice that the socket streams are closed first. All socket streams should
be closed before the socket is closed. This application is relatively
simple, but all client programs follow the same basic script:
1. Create the client socket connection.
2. Acquire read and write streams to the socket.
3. Use the streams according to the server's protocol.
4. Close the streams.
5. Close the socket.
Using a server socket is only slightly more complicated, as explained in
the following section.
Server Sockets
Listing 9.2 is a partial listing of a simple server application. The
complete server example can be found on the CD-ROM in SimpleWebServer.java.
Listing 9.2. A simple server application.
/**
* An application that listens for connections and serves a simple
* HTML document.
*/
class SimpleWebServer {
public static void main(String args[])
{
ServerSocket serverSocket = null;
Socket clientSocket = null;
int connects = 0;
try
{
// Create the server socket
serverSocket = new ServerSocket(80, 5);
while (connects < 5)
{
// Wait for a connection
clientSocket = serverSocket.accept();
//Service the connection
ServiceClient(clientSocket);
connects++;
}
serverSocket.close();
}
catch (IOException ioe)
{
System.out.println("Error in SimpleWebServer: " + ioe);
}
}
public static void ServiceClient(Socket client)
throws IOException
{
DataInputStream inbound = null;
DataOutputStream outbound = null;
try
{
// Acquire the streams for IO
inbound = new DataInputStream( client.getInputStream());
outbound = new DataOutputStream( client.getOutputStream());
// Format the output (response header and tiny HTML document)
StringBuffer buffer = PrepareOutput();
String inputLine;
while ((inputLine = inbound.readLine()) != null)
{
// If end of HTTP request, send the response
if ( inputLine.equals("") )
{
outbound.writeBytes(buffer.toString());
break;
}
}
}
finally
{
// Clean up
System.out.println("Cleaning up connection: " + client);
outbound.close();
inbound.close();
client.close();
client.close();
}
}
Servers do not actively create connections. Instead, they passively listen
for a client connect request and then provide their services. Servers are
created with a constructor from the ServerSocket class. The following line
creates a server socket and binds it to port 80:
ServerSocket serverSocket = new ServerSocket(80, 5);
The first parameter is the port number on which the server should listen.
The second parameter is optional. The API documentation indicates that this
parameter is a listen time, but in traditional sockets programming the
listen function's second parameter is the listen stack depth. As it turns
out, this is also true for the second constructor parameter. A server can
receive connect requests from many clients at the same time, but each call
must be processed one at a time. The listen stack is a queue of unanswered
connect requests. The above code instructs the socket driver to maintain
the last five connect requests. If the constructor omits the listen stack
depth, a default value of 50 is used.
Once the socket is created and listening for connections, incoming
connections are created and placed on the listen stack. The accept() method
is called to lift individual connections off the stack:
Socket clientSocket = serverSocket.accept();
This method returns a connected client socket used to converse with the
caller. No conversations are ever conducted over the server socket itself.
Instead, the server socket will spawn a new socket in the accept() method.
The server socket is still open and queuing new connection requests.
Like the client socket, the next step is to create an input and output
stream:
DataInputStream inbound = new DataInputStream( clientSocket.getInputStream()
);
DataOutputStream outbound = new DataOutputStream(
clientSocket.getOutputStream() );
Normal I/O operations can now be performed by using the newly created
streams. This server waits for the client to send a blank line before
sending its response. When the conversation is finished, the server closes
the streams and the client socket. At this point, the server tries to
accept more calls. What happens when there are no calls waiting in the
queue? The method will wait for one to arrive. This behavior is known as
blocking. The accept() method will block the server thread from performing
any other tasks until a new call arrives. When five connects have been
serviced, the server exits by closing its server socket. Any queued calls
will be canceled.
All servers follow the same basic script:
1. Create the server socket and begin listening.
2. Call the accept() method to get new connections.
3. Create input and output streams for the returned socket.
4. Conduct the conversation based on the agreed protocol.
5. Close the client streams and socket.
6. Go back to step 2 or continue to step 7.
7. Close the server socket.
Figure 9.1 summarizes the steps needed for client/server
connection-oriented applications.
Figure 9.1. Client and server connection-oriented applications.
Iterative and Concurrent Servers
The application just presented is known as an iterative server because the
code accepts a client connection and completely processes it before it will
accept another connection. More complex servers are concurrent. Instead of
accepting connections and immediately processing them, a concurrent server
spawns a new thread to process each new request, so it seems as though the
server is processing many requests simultaneously. All commercial Web
servers are concurrent servers.
Java Datagram Classes
Unlike connection-oriented classes, the datagram versions of the client and
server behave in nearly identical manners—the only difference occurs in
implementation. The same class is used for both client and server halves.
The following lines create client and server datagram sockets:
DatagramSocket serverSocket = new DatagramSocket( 4545 );
DatagramSocket clientSocket = new DatagramSocket();
The server specifies its port using the lone constructor parameter 4545.
Since the client will call the server, the client can use any available
port. The omitted constructor parameter in the second call instructs the
operating system to assign the next available port number. The client could
have requested a specific port, but the call would fail if some other
socket had already bound itself to that port. It's better not to specify a
port unless the intent is to be a server.
Since streams can't be acquired for communication, how do you talk to a
DatagramSocket? The answer lies in the DatagramPacket class.
Receiving Datagrams
The DatagramPacket class is used to receive and send data over
DatagramSocket classes. The packet class contains connection information as
well as the data. As was explained earlier, datagrams are self-contained
transmission units. The DatagramPacket class encapsulates these units. The
following lines receive data from a datagram socket:
DatagramPacket packet = new DatagramPacket(new byte[512], 512);
clientSocket.receive(packet);
The constructor for the packet needs to know where to place the received
data. A 512-byte buffer was created and passed to the constructor as the
first parameter. The second constructor parameter was the size of the
buffer. Like the accept() method in the ServerSocket class, the receive()
method will block until data is available.
Sending Datagrams
Sending datagrams is really very simple; all that's needed is a complete
address. Addresses are created and tracked by using the InetAddress class.
This class has no public constructors, but it does contain several static
methods that can be used to create an instance of the class. The following
list shows the public methods that create InetAddress class instances:
Public InetAddress Creation Methods
InetAddress getByName(String host);
InetAddress[] getAllByName(String host);
InetAddress getLocalHost();
Getting the local host is useful for informational purposes, but only the
first two methods are actually used for sending packets. Both getByName()
and getAllByName() require the name of the destination host. The first
method merely returns the first match it finds. The second method is needed
because a computer can have more than one address. When this occurs, the
computer is said to be multi-homed. The computer has one name, but multiple
ways to reach it.
All the creation methods are marked as static. They must be called as
follows:
InetAddress addr1 = InetAddress.getByName("merlin");
InetAddress addr2[] = InetAddress.getAllByName("merlin");
InetAddress addr3 = InetAddress.getLocalHost();
Any of these calls can throw an UnknownHostException. If a computer is not
connected to a Domain Name Server (DNS), or if the host is really not
found, an exception will be thrown. If a computer does not have an active
TCP/IP configuration, then getLocalHost() is likely to fail with this
exception as well.
Once an address is determined, datagrams can be sent. The following lines
transmit a String to a destination socket:
String toSend = "This is the data to send!");
byte[] sendbuf = new byte[ toSend.length() ];
toSend.getBytes( 0, toSend.length(), sendbuf, 0 );
DatagramPacket sendPacket = new DatagramPacket( sendbuf, sendbuf.length,
addr, port);
clientSocket.send( sendPacket );
First, the string must be converted to a byte array. The getBytes() method
takes care of the conversion. Next, a new DatagramPacket instance must be
created. Notice the two extra parameters at the end of the constructor.
Since this will be a send packet, the address and port of the destination
must also be placed into the packet. An applet may know the address of its
server, but how does a server know the address of its client? Remember that
a datagram is like an envelope—it has a return address. When any packet is
received, the return address can be extracted from the packet by using
getAddress() and getPort(). This is how a server would respond to a client
packet:
DatagramPacket sendPacket = new DatagramPacket( sendbuf, sendbuf.length,
recvPacket.getAddress(), recvPacket.getPort() );
serverSocket.send( sendPacket );
Unlike connection-oriented operation, datagram servers are actually less
complicated than the datagram client.
Datagram Servers
The basic script for datagram servers is as follows:
1. Create the datagram socket on a specific port.
2. Call receive to wait for incoming packets.
3. Respond to received packets according to the agreed protocol.
4. Go back to step 2 or continue to step 5.
5. Close the datagram socket.
Listing 9.3 shows a simple datagram echo server. It will echo back any
packets it receives.
Listing 9.3. A simple datagram echo server.
import java.io.*;
import java.net.*;
public class SimpleDatagramServer
{
public static void main(String[] args)
{
DatagramSocket socket = null;
DatagramPacket recvPacket, sendPacket;
try
{
socket = new DatagramSocket(4545);
while (socket != null)
{
recvPacket= new DatagramPacket(new byte[512], 512);
socket.receive(recvPacket);
sendPacket = new DatagramPacket(
recvPacket.getData(), recvPacket.getLength(),
recvPacket.getAddress(), recvPacket.getPort() );
socket.send( sendPacket );
}
}
catch (SocketException se)
{
System.out.println("Error in SimpleDatagramServer: " + se);
}
catch (IOException ioe)
{
System.out.println("Error in SimpleDatagramServer: " + ioe);
}
}
}
Datagram Clients
The corresponding client uses the same process with one exception: A client
must initiate the conversation. The basic recipe for datagram clients is as
follows:
1. Create the datagram socket on any available port.
2. Create the address to send to.
3. Send the data according to the server's protocol.
4. Wait for receive data.
5. Go to step 3 (send more data), 4 (wait for receive) or 6 (exit).
6. Close the datagram socket.
Figure 9.2 summarizes the steps needed for client/server datagram
applications. The symmetry between client and server is evident from this
picture; compare Figure 9.2 with Figure 9.1.
Figure 9.2. Client and server datagram applications.
Listing 9.4 shows a simple datagram client. It reads user input strings and
sends them to the echo server from Listing 9.3. The echo server will send
the data right back, and the client will print the response to the console.
Listing 9.4. A simple datagram client.
import java.io.*;
import java.net.*;
public class SimpleDatagramClient
{
private DatagramSocket socket = null;
private DatagramPacket recvPacket, sendPacket;
private int hostPort;
public static void main(String[] args)
{
DatagramSocket socket = null;
DatagramPacket recvPacket, sendPacket;
try
{
socket = new DatagramSocket();
InetAddress hostAddress = InetAddress.getByName("merlin");
DataInputStream userData = new DataInputStream( System.in );
while (socket != null)
{
String userString = userData.readLine();
if (userString == null || userString.equals(""))
return;
byte sendbuf[] = new byte[ userString.length() ];
userString.getBytes(0, userString.length(), sendbuf, 0);
sendPacket = new DatagramPacket(
sendbuf, sendbuf.length, hostAddress, 4545 );
socket.send( sendPacket );
recvPacket= new DatagramPacket(new byte[512], 512);
socket.receive(recvPacket);
System.out.write(recvPacket.getData(), 0,
recvPacket.getLength());
System.out.print("\n");
}
}
catch (SocketException se)
{
System.out.println("Error in SimpleDatagramClient: " + se);
}
catch (IOException ioe)
{
System.out.println("Error in SimpleDatagramClient: " + ioe);
}
}
}
All the examples so far have been Java applications. Running these in an
applet presents an extra complication: security.
Applet Security and Sockets
When writing applications, you don't need to be concerned with security
exceptions. This changes when the code under development is executed from
an applet. Netscape Navigator 2.0 uses very stringent security measures
where sockets are concerned. An applet may open a socket only back to the
host name from which it was loaded. If any other connection is attempted, a
SecurityException will be thrown.
Datagram sockets don't open connections, so how is security insured for
these sockets? When an inbound packet is received, the host name is
checked. If the packet did not originate from the server, a
SecurityException is immediately thrown. Obviously, sending comes under the
same scrutiny. If a datagram socket tries to send to any destination except
the server, a SecurityException is thrown. These restrictions apply only to
the address, not the port number. Any port number on the host may be used.
All the socket techniques demonstrated so far will be developed further in
this chapter's project.
Chapter Project: HTTP Server Application and Client Applet
This project at first glance seems a bit ambitious, but writing a
rudimentary Web server is not as hard as it sounds. Client applets need a
HTTP Web server so they can open sockets. If an applet is loaded into
Netscape from a hard drive, then no socket activity is allowed to take
place. A simple solution is to write a HTTP server application. Once
written, additional server threads can be added to provide all types of
back-end connectivity. This project will add a multipurpose datagram
protocol that will be used for live data in both Chapter 10, "Native
Methods and Java," and 11 "Building a Live Data Applet."
HTTP Primer
Before diving into the project, you need some background information on the
HTTP protocol. The Hypertext Transfer Protocol (HTTP) has been in use on
the World-Wide Web since 1990. All applet-bearing Web pages are sent over
the net with HTTP. The server will support a subset of version 1.0 in that
only file requests will be handled. As long as Netscape page requests can
be fulfilled, the server will have accomplished its goal.
HTTP uses a stream-oriented (TCP) socket connection. Typically, port 80 is
used, but other port numbers can be substituted. All the protocol is sent
in plain-text format. An example of a conversation was demonstrated in
Listings 9.1 and 9.2. The server listens on port 80 for a client request,
which takes this format:
GET FILE HTTP/1.0
The first word is referred to as the "method" of the request. Table 9.2
lists all the request methods for HTTP/1.0.
Table 9.2. HTTP/1.0 request methods.
Method Use
GET Retrieve a file
HEAD Retrieve only file information
POST Send data to the server
PUT Send data to the server
DELETE Delete a resource
LINK Link two resources
UNLINK Unlink two resources
The second parameter of a request is a file path. Each of the following
URLs is followed by the request that will be formulated and sent:
HTTP://www.qnet.com/
GET / HTTP/1.0
HTTP://www.qnet.com/index.html
GET /index.html HTTP/1.0
HTTP://www.qnet.com/classes/applet.html
GET /classes/applet.html HTTP/1.0
The request does not end until a blank line containing only a carriage
return (\r) and a line feed (\n) is received. After the method line, a
number of optional lines can be sent. Netscape Navigator 2.0 will produce
the following request:
GET / HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/2.0 (Win95; I)
Host: merlin
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Responses use a header similar to the request:
HTTP/1.0 200 OK
Content-type: text/html
Content-Length: 128
Like the request, the response header is not complete until a blank line is
sent containing only a carriage return and a line feed. The first line
contains a version identification string, followed by a status code
indicating the results of the request. Table 9.3 lists all the defined
status codes. The server will send only two of these: 200 and 404. The text
that follows the status code is optional. It may be omitted, or, if
present, it might not match the definitions given in the table.
Table 9.3. HTTP response status codes.
Status Code Optional Text Description
200 OK
201 Created
202 Accepted
204 No Content
300 Multiple Choices
301 Moved Permanently
302 Moved Temporarily
304 Not Modified
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error
501 Not Implemented
502 Bad Gateway
503 Service Unavailable
Immediately after the response header, the requested file is sent. When the
file is completely transmitted, the socket connection is closed. Each
request-response pair consumes a new socket connection.
That's enough information to construct a basic Web server. Full information
on the HTTP protocol can be retrieved from HTTP://www.w3.org/
Basic Web Server
The basic Web server will follow the construction of the SimpleWebServer
from Listing 9.2. Many improvements will have to be made to method and
response handling. The simple server does not parse or store the request
header as it arrives. The new Web server will have to parse and store the
requests for later processing. To do this, you need a class to contain a
HTTP request.
HTTPrequest Class
Listing 9.5 shows the complete HTTPrequest class. The class must contain
all the information that could be conveyed in a request header.
Listing 9.5. The HTTPrequest class.
import java.io.*;
import java.util.*;
import java.net.*;
import NameValue;
/**
* This class maintains all the information from a HTTP request
*/
public class HTTPrequest
{
public String version;
public String method;
public String file;
public Socket clientSocket;
public DataInputStream inbound;
public NameValue headerpairs[];
/**
* Create an instance of this class
*/
public HTTPrequest()
{
version = null;
method = null;
file = null;
clientSocket = null;
inbound = null;
headerpairs = new NameValue[0];
}
/**
* Add a name/value pair to the internal array
*/
public void addNameValue(String name, String value)
{
try
{
NameValue temp[] = new NameValue[ headerpairs.length + 1 ];
System.arraycopy(headerpairs, 0, temp, 0, headerpairs.length);
temp[ headerpairs.length ] = new NameValue(name, value);
headerpairs = temp;
}
catch (NullPointerException npe)
{
System.out.println("NullPointerException while adding name-value:
" + npe);
}
}
/**
* Renders the contents of the class in String format
*/
public String toString()
{
String s = method + " " + file + " " + version + "\n";
for (int x = 0; x < headerpairs.length; x++ )
s += headerpairs[x] + "\n";
return s;
}
}
The NameValue class simply stores two strings: name and value. You can find
the source code for it on the CD-ROM in NameValue.java. When a new pair
needs to be added, a new array is allocated. The new array receives a copy
of the old array as well as the new member. The old array is then replaced
with the newly created entity.
Two data fields in the class are not directly part of an HTTP request. The
clientSocket member allows response routines to get an output stream, and
the inbound member allows easy closure after a request has been processed.
The remaining members are all part of an HTTP request. The method
toString() allows class objects to be printed using "plus notation." The
following line will display the contents of a request by invoking the
toString() method:
System.out.println("Request: " + request);
Now that the request container is finished, it's time to populate it.
BasicWebServer Class
This is the main class for the server. It can broken down into request and
response routines. Since this is a server, the request routines will be
activated first. After some validation, the response routines will be
called. Listing 9.6 provides the routines to parse an HTTP request.
Listing 9.6. HTTP request routines.
/**
* Read a HTTP request into a continuous String.
* @param client a connected client stream socket.
* @return a populated HTTPrequest instance
* @exception ProtocolException If not a valid HTTP header
* @exception IOException
*/
public HTTPrequest GetRequest(Socket client)
throws IOException, ProtocolException
{
DataInputStream inbound = null;
HTTPrequest request = null;
try
{
// Acquire an input stream for the socket
inbound = new DataInputStream(client.getInputStream());
// Read the header into a String
String reqhdr = readHeader(inbound);
// Parse the string into an HTTPrequest instance
request = ParseReqHdr(reqhdr);
// Add the client socket and inbound stream
request.clientSocket = client;
request.inbound = inbound;
}
catch (ProtocolException pe)
{
if ( inbound != null )
inbound.close();
throw pe;
}
catch (IOException ioe)
{
if ( inbound != null )
inbound.close();
throw ioe;
}
return request;
}
/**
* Assemble a HTTP request header String
* from the passed DataInputStream.
* @param is the input stream to use
* @return a continuous String representing the header
* @exception ProtocolException If a pre HTTP/1.0 request
* @exception IOException
*/
private String readHeader(DataInputStream is)
throws IOException, ProtocolException
{
String command;
String line;
// Get the first request line
if ( (command = is.readLine()) == null )
command = "";
command += "\n";
// Check for HTTP/1.0 signature
if (command.indexOf("HTTP/") != -1)
{
// Retreive any additional lines
while ((line = is.readLine()) != null && !line.equals(""))
command += line + "\n";
}
else
{
throw new ProtocolException("Pre HTTP/1.0 request");
}
return command;
}
/**
* Parsed the passed request String and populate a HTTPrequest.
* @param reqhdr the HTTP request as a continous String
* @return a populated HTTPrequest instance
* @exception ProtocolException If name,value pairs have no ':'
* @exception IOException
*/
private HTTPrequest ParseReqHdr(String reqhdr)
throws IOException, ProtocolException
{
HTTPrequest req = new HTTPrequest();
// Break the request into lines
StringTokenizer lines = new StringTokenizer(reqhdr, "\r\n");
String currentLine = lines.nextToken();
// Process the initial request line
// into method, file, version Strings
StringTokenizer members = new StringTokenizer(currentLine, " \t");
req.method = members.nextToken();
req.file = members.nextToken();
if (req.file.equals("/")) req.file = "/index.html";
req.version = members.nextToken();
// Process additional lines into name/value pairs
while ( lines.hasMoreTokens() )
{
String line = lines.nextToken();
// Search for separating character
int slice = line.indexOf(':');
// Error if no separating character
if ( slice == -1 )
{
throw new ProtocolException(
"Invalid HTTP header: " + line);
}
else
{
// Separate at the slice character into name, value
String name = line.substring(0,slice).trim();
String value = line.substring(slice + 1).trim();
req.addNameValue(name, value);
}
}
return req;
}
The method readHeader() interrogates the inbound socket stream searching
for the blank line. If the request is not of HTTP/1.0 format, this method
will throw an exception. Otherwise, the resulting String is passed to
parseReqHdr() for processing.
These routines will reject any improperly formatted requests, including
requests made in the older HTTP/0.9 format. Parsing makes heavy use of the
StringTokenizer class found in the java.util package.
Normally, it would be preferable to close the inbound stream as soon as the
request has been completely read. If this is done, then subsequent output
attempts will fail with an IOException. This is why the inbound stream is
placed into the HTTPrequest instance. When the output has been completely
sent, both the output and the input streams will be closed.
---------------------------------------------------------------------------
[Image]CAUTION: Do not be tempted to close an inbound stream after all
input has been read. Closing the input stream will cause subsequent output
attempts to fail with an IOException. Close both streams only after all
socket operations are finished.
---------------------------------------------------------------------------
Currently, the server makes no use of the additional lines in a HTTP
request header. The HTTPrequest class does save them in an array, however,
so they can be used in future enhancements. Wherever possible, the server
has been written with future enhancements in mind.
Once you've built the request, you need to form a response. Listing 9.7
presents the response routines used by the server.
Listing 9.7. HTTP response routines.
/**
* Respond to a HTTP request
* @param request the HTTP request to respond to.
* @exception ProtocolException If unimplemented request method
*/
private void implementMethod(HTTPrequest request)
throws ProtocolException
{
try
{
if (debug && level < 4)
System.out.println("DEBUG: Servicing:\n" + request);
if ( (request.method.equals("GET") ) ||
(request.method.equals("HEAD")) )
ServicegetRequest(request);
else
{
throw new ProtocolException("Unimplemented method: " +
request.method);
}
}
catch (ProtocolException pe)
{
sendNegativeResponse(request);
throw pe;
}
}
/**
* Send a response header for the file and the file itself.
* Handles GET and HEAD request methods.
* @param request the HTTP request to respond to
*/
private void ServicegetRequest(HTTPrequest request)
throws ProtocolException
{
try
{
if (request.file.indexOf("..") != -1)
throw new ProtocolException("Relative paths not supported");
String fileToGet = "htdocs" + request.file;
FileInputStream inFile = new FileInputStream(fileToGet);
if (debug & level < 4)
{
System.out.print("DEBUG: Sending file ");
System.out.print(fileToGet + " " + inFile.available());
System.out.println(" Bytes");
}
sendFile(request, inFile);
inFile.close();
}
catch (FileNotFoundException fnf)
{
sendNegativeResponse(request);
}
catch (ProtocolException pe)
{
throw pe;
}
catch (IOException ioe)
{
System.out.println("IOException: Unknown file length: " + ioe);
sendNegativeResponse(request);
}
}
/**
* Send a negative (404 NOT FOUND) response
* @param request the HTTP request to respond to.
*/
private void sendNegativeResponse(HTTPrequest request)
{
DataOutputStream outbound = null;
try
{
// Acquire the output stream
outbound = new DataOutputStream(
request.clientSocket.getOutputStream());
// Write the negative response header
outbound.writeBytes("HTTP/1.0 ");
outbound.writeBytes("404 NOT_FOUND\r\n");
outbound.writeBytes("\r\n");
// Clean up
outbound.close();
request.inbound.close();
}
catch (IOException ioe)
{
System.out.println("IOException while sending -rsp: " + ioe);
}
}
/**
* Send the passed file
* @param request the HTTP request instance
* @param inFile the opened input file stream to send\
*/
private void sendFile(HTTPrequest request, FileInputStream inFile)
{
DataOutputStream outbound = null;
try
{
// Acquire an output stream
outbound = new DataOutputStream(
request.clientSocket.getOutputStream());
// Send the response header
outbound.writeBytes("HTTP/1.0 200 OK\r\n");
outbound.writeBytes("Content-type: text/html\r\n");
outbound.writeBytes("Content-Length: " + inFile.available() +
"\r\n");
outbound.writeBytes("\r\n");
// Added to allow Netscape to process header properly
// This is needed because the close is not recognized
sleep(500);
// If not a HEAD request, send the file body.
// HEAD requests solicit only a header response.
if (!request.method.equals("HEAD"))
{
byte dataBody[] = new byte[1024];
int cnt;
while ((cnt = inFile.read(dataBody)) != -1)
outbound.write(dataBody, 0, cnt);
}
// Clean up
outbound.flush();
outbound.close();
request.inbound.close();
}
catch (IOException ioe)
{
System.out.println("IOException while sending file: " + ioe);
}
}
Only GET and HEAD requests are honored. The primary goal is to provide an
applet server, not a full-featured Web server. File requests are all that's
needed for applet loading, though additional handlers can certainly be
added for other request methods. The serviceGetRequest() function handles
all responses. When the input stream for a file is acquired, the file is
opened. At this point, the routine knows whether the file exists and its
size. Once a valid file is found, the sendFile() function can be called.
The file is read and sent in 1K blocks. This keeps memory usage down while
seeking to balance the number of disk accesses attempted. Negative
responses are sent only for errors occurring after the request has been
built. As a consequence, improperly formatted requests will generate no
response.
The response routines rely on ProtocolExceptions to signal error
conditions. When one of these exceptions reaches the implementMethod()
function, a negative response is sent. Notice the catch clause in
serviceGetRequest(). The ProtocolException must be caught and thrown again,
or the following IOException will catch the event. This is because
ProtocolException is a child class of IOException. If it had been placed
after the IOException, the compiler would have generated an error:
BasicWebServer.java:303: catch not reached.
The remainder of the BasicWebServer application can be found on the CD-ROM.
The remaining code calls the input routine getRequest() and then the output
routine implementMethod() for each client connection.
The next section develops a client applet that will be loaded with the
server just constructed. Another service thread will be added to the server
to conduct a datagram socket protocol with the client.
Client Datagram Applet
Applets need to communicate with a server for a variety of applications.
What is needed is a generic protocol that any applet can use to communicate
with its server. This protocol should not be connection oriented because of
the additional load that would be placed on a server. Datagrams present a
lighter load and allow the same socket to be used no matter how many actual
connections are being serviced. What is envisioned is a broadcast
capability for data. It isn't reasonable for applets to query a server
every five seconds to see whether data has changed. The server should be
able to send to all of its connections whenever the data changes. With this
in mind, the Datagram Transfer Protocol (DGTP) was developed. The primary
requirements of this protocol were as follows:
* Use datagrams to lessen server socket use.
* Implement a hook-in and an unhook mechanism.
* Allow generic data of any type to be transferred.
* Allow any object type to use DGTP services.
* Provide a broadcast capability.
Figure 9.3 shows a client applet using DGTP to communicate with a server.
Notice how the HTTP data connection does not extend to the applet. The
browser spawns the applet from the data received from the server.
Figure 9.3. DGTP communication in the client/server model.
The DGTP protocol uses a header much like HTTP; its basic methods are
REGISTER, UNREGISTER, DATA, PING, and PONG. The two register methods
accomplish hooking and unhooking. PING and PONG are currently unused, but
could provide a mechanism to periodically check the connection list. The
DATA method facilitates the transfer capability. To allow any object to use
DGTP services, a standard interface was developed. These interfaces specify
the set of functions that an object must use to communicate with DGTP
service threads. Listing 9.8 shows the client and server interfaces.
Listing 9.8. DGTP client and server interfaces.
public interface LiveDataNotify
{
public String getDestHost();
public int getDestPort();
public void recvNewData(byte[] newDataBlock);
public void connectRefused();
}
public interface LiveDataServer
{
public boolean ValidateRegistrant(ClientAddr user);
public void NewRegistrant(ClientAddr user);
public void DropRegistrant(ClientAddr user);
public void recvNewData(byte[] newDataBlock, DatagramPacket fromWho);
}
Notice that both the client and the server have a method to receive blocks
of data. The client has methods to specify the destination host and port,
and the server has methods to validate and register new connections. The
DGTP client will be covered first.
DGTP Client
Since listening for receive will block until there is data, the
registration requests will have to be sent using a different thread. The
first thing the client's run() method does is to start the registration
thread. At that point, it can begin receiving data. Listing 9.9 displays a
partial listing of the DGTP client. The complete source code for the client
and the registration threads is on the CD-ROM in DGTPClient.java.
Listing 9.9. The DGTPClient class.
/**
* The runmethod for the client. Start the register thread and
* begin listening for incoming packets.
*/
public void run()
{
DatagramPacket packet = null;
try
{
regThread.start();
while (socket != null)
{
packet = new DatagramPacket(new byte[512], 512);
socket.receive(packet);
try
{
parsePacketData(packet);
}
catch (ProtocolException pe)
{
System.out.println("ProtocolException: " + pe);
}
}
}
catch (IOException ioe)
{
System.out.println("IOException: in DGTPClient: " + ioe);
}
}
/**
* Handle a DGTP incoming header
* @param packet the incoming packet to parse
* @exception ProtocolException
* @exception IOException
*/
public void parsePacketData(DatagramPacket packet)
throws IOException, ProtocolException
{
String command = null;
ByteArrayInputStream barray = null;
DataInputStream is = null;
barray = new ByteArrayInputStream(
packet.getData(), 0, packet.getLength() );
is = new DataInputStream( barray );
command = readHeader(is);
StringTokenizer lines, cmds;
lines = new StringTokenizer(command, "\r\n");
cmds = new StringTokenizer(lines.nextToken(), " \t");
String ver = cmds.nextToken();
String cmd = cmds.nextToken();
if ( cmd.equals("PING") )
send("PONG" + cmds.nextToken());
else if ( cmd.equals("REGISTER") )
{
lastResponse = cmds.nextToken();
registered = true;
if ( !lastResponse.equals("CONFIRM") )
{
dataClient.connectRefused();
socket.close();
socket = null;
}
}
else if ( cmd.equals("UNREGISTER") )
{
lastResponse = cmds.nextToken();
if ( lastResponse.equals("CONFIRM") )
{
registered = false;
socket.close();
socket = null;
}
}
else if ( cmd.equals("DATA") )
{
int length = Integer.valueOf(cmds.nextToken()).intValue();
byte[] data = new byte[length];
try
{
is.readFully(data);
dataClient.recvNewData(data);
}
catch (EOFException eof)
{
throw new ProtocolException(
"Server packet too short: " + eof);
}
catch (IOException ioe)
{
throw new ProtocolException(
"While reading server data: " + ioe);
}
}
else
{
throw new ProtocolException(
"Unknown DGTP command: " + cmd);
}
is.close();
}
/**
* Unregister the DGTPClient
*/
public void terminate()
{
unregThread = new ClientUnregistration(this);
unregThread.start();
}
The read routines are largely the same as the HTTP server's. What is
significant is the translation of the packet data to stream format. Once
that is done, the header can be parsed in the same manner as a HTTP
request. To perform the translation, ByteArrayInputStream is used; this
class is extremely useful when working with byte arrays. Once the array is
in a stream format, it can be turned into a DataInputStream—the same format
the BasicWebServer used to read its requests.
The terminate() function spawns a new thread to send the UNREGISTER command
because the main client thread is blocked in a receive call.
DGTP Server Class
Since DGTP is a datagram protocol, the server will be very similar to the
client. There are two main changes, the largest of which occurs in the
parsePacketData() handler function. Listing 9.10 shows the data parse
function for the DGTPServer class. The complete source code can be found on
the CD-ROM in DGTPServer.java.
Listing 9.10. DGTPServer data parsing routine.
/**
* Process all incoming packets
* @param packet contains the DGTP request
* @exception ProtocolException
* @exception IOException
*/
public void ParsePacketData(DatagramPacket packet)
throws IOException, ProtocolException
{
String command = null;
ByteArrayInputStream barray = null;
DataInputStream is = null;
String cmd = null;
barray = new ByteArrayInputStream(
packet.getData(), 0, packet.getLength() );
is = new DataInputStream( barray );
command = readHeader(is);
try
{
StringTokenizer lines = new StringTokenizer(command, "\r\n");
StringTokenizer cmds = new StringTokenizer(lines.nextToken(), "
\t");
String ver = cmds.nextToken();
cmd = cmds.nextToken();
if ( cmd.equals("PING") )
{
ClientAddr addr = new ClientAddr(
packet.getAddress(), packet.getPort());
send(addr, "PONG" + cmds.nextToken());
}
else if ( cmd.equals("REGISTER") )
{
ClientAddr addr = new ClientAddr(
packet.getAddress(), packet.getPort());
if (!Clients.containsKey(addr))
{
if ( dataServer.ValidateRegistrant(addr) )
{
Clients.put(addr, addr);
send(addr, "REGISTER CONFIRM");
dataServer.NewRegistrant(addr);
}
else
{
send(addr, "REGISTER DENIED");
}
}
else
{
send(addr, "REGISTER CONFIRM");
}
}
else if ( cmd.equals("UNREGISTER") )
{
int port = Integer.valueOf(cmds.nextToken()).intValue();
dumpUser( new ClientAddr(packet.getAddress(), port) );
}
else if ( cmd.equals("DATA") )
{
int length = Integer.valueOf(cmds.nextToken()).intValue();
byte[] data = new byte[length];
try
{
is.readFully(data);
dataServer.recvNewData(data, packet);
}
catch (EOFException eof)
{
throw new ProtocolException(
"Client packet too short: " + eof);
}
catch (IOException ioe)
{
throw new ProtocolException(
"While reading client data: " + ioe);
}
}
else
{
throw new ProtocolException(
"Unknown DGTP command: " + cmd);
}
}
catch (NoSuchElementException ne)
{
throw new ProtocolException(
"Command arg mismatch: " + cmd);
}
is.close();
}
The changes occur when adding new users. The server thread will receive a
REGISTER request, which it will pass to the interface object for
validation. If the interface object accepts the new user, a REGISTER
CONFIRM response is sent, and the interface object is alerted to the
addition. If the user is rejected, a REGISTER DENIED response is sent. The
second change is one of omission. The run() method for the server will not
spawn a registration thread. Otherwise, it is identical to the client's
run() method.
The server keeps track of user connections in a Hashtable. The ClientAddr
class object encapsulates the address and port as well as providing a hash
key. This allows the server to add a new user quickly. The code for the
REGISTER method creates the address and checks to see whether it's already
present. Multiple REGISTER requests may have been sent before the REGISTER
CONFIRM packet could travel back to the sender. If the server doesn't have
this connection yet, it adds the address to the Clients list. Listing 9.11
shows the ClientAddr class. Pay particular attention to the hashCode() and
equals() functions; they allow the object to act as a hash key.
Listing 9.11. The ClientAddr class.
import java.net.InetAddress;
public class ClientAddr
{
public InetAddress address;
public int port;
ClientAddr(InetAddress addr, int hostPort)
{
address = addr;
port = hostPort;
}
public int hashCode()
{
int result = address.hashCode();
result += port;
return result;
}
public boolean equals(Object obj)
{
return (obj != null) && (obj instanceof ClientAddr) &&
(address.equals(((ClientAddr)obj).address)) &&
(port == ((ClientAddr)obj).port);
}
}
Since this is a broadcast server, there is a varied array of send methods
embedded in the class. The DGTP server has two main send routines:
sendData(ClientAddr dest, byte[] data, int srcOffset, int length);
send(ClientAddr dest, String toSend);
The first routine sends the byte array as a DGTP DATA block. The second
routine sends the passed String as a DGTP command header. All the remaining
send routines call sendData() to do the actual transmission. This is the
code for one version of sendToUsers():
public void sendToUsers(byte[] data, int srcOffset, int length)
{
for (Enumeration e = Clients.elements(); e.hasMoreElements();)
sendData((ClientAddr)e.nextElement(), data, srcOffset, length);
}
This routine uses an Enumeration object to loop through the client
hashtable and send to each member. All the remaining send methods are
variations on this theme. Some send to all users; some send to a specific
subset of users. These are all the public send methods:
Public Send Methods in DGTPServer.
sendToUsers(String toSend);
sendToUsers(byte[] data, int Offset, int length);
sendToUsers(ClientAddr[] users, String toSend);
sendToUsers(ClientAddr[] users, byte[] data, int Offset, int length);
sendData(ClientAddr addr, String block);
sendData(ClientAddr dest, byte[] data, int srcOffset, int length);
send(ClientAddr dest, String toSend);
Now that the threads are in place, it's time to apply them in the actual
client/server applet.
Client Applet
The client applet will be simple in appearance. The emphasis here will be
on using the DGTP protocol. Figure 9.4 shows the applet in action.
Figure 9.4. A simple client applet display.
The purpose of this applet is to display the number of active connections
to this page. Whenever a new user connects, the display will automatically
update to reflect the new count. Likewise, when a user disconnects, the
count will update. Listing 9.12 shows the client applet class.
Listing 9.12. A client applet.
import java.applet.*;
import java.awt.*;
import java.net.*;
import java.io.*;
import java.util.*;
import DGTPClient;
public class SimpleClientApplet extends Applet
implements LiveDataNotify
{
private boolean init = false;
DGTPClient ct = null;
int destPort;
String destHost = null;
String numUsers = "Unknown at this time";
String users = null;
public void init()
{
if ( init == false )
{
init = true;
resize(500,500);
String strPort = getParameter("PORT");
if ( strPort == null )
{
System.out.println("ERROR: PORT parameter is missing");
strPort = "4545";
}
destPort = Integer.valueOf(strPort).intValue();
destHost = getDocumentBase().getHost();
}
}
public void paint(Graphics g)
{
g.drawString("Registered Users: " + getUsers(), 0, 100);
}
public String getDestHost()
{
return destHost;
}
public int getDestPort()
{
return destPort;
}
public synchronized void recvNewData(byte[] newDataBlock)
{
users = new String(newDataBlock, 0);
repaint();
}
public synchronized String getUsers()
{
if (users != null)
{
StringTokenizer cmds = new StringTokenizer(users, " \t");
if (cmds.nextToken().equals("CLIENTS"))
numUsers = cmds.nextToken();
}
return numUsers;
}
public void start()
{
ct = new DGTPClient(this);
ct.start();
}
public void stop()
{
System.out.println("SimpleClientApplet.stop()");
ct.terminate();
}
public void connectRefused()
{
}
}
The applet layers a simple protocol on top of DGTP. Whenever the server
detects a change in the number of users, it sends a DATA block with the
following text:
CLIENTS number CRLF
The applet receives the new DATA block and converts it to a String in
recvNewData(). Note that this routine as well as getUsers() is marked as
synchronized. This prevents the applet from attempting to read the String
while DGTP is updating it.
The applet uses an applet parameter to know which port the server is
monitoring. The following line reads the PORT parameter:
String strPort = getParameter("PORT");
The server host name is retrieved from the document itself:
destHost = getDocumentBase().getHost();
This is all the information needed to establish a server connection.
---------------------------------------------------------------------------
[Image]Using getHost() Over a Dial-up Connection
If you connect to the Internet through a dial-up account, then you might
have trouble with this application because of a host name issue.
Specifically, when a dial in PPP connection is made, your computer is
assigned an IP address by the provider. This address is displayed by the
server when it is started. Users on the Internet can now reach your server
by typing in your IP address:
HTTP://xxx.xxx.xxx.xxx/
This will access your server, and pages can be sent. The trouble arises
when your applet attempts to receive data from your server. The call to
getDocumentBase().getHost() will return the IP address that the user typed
in to reach your server:
"xxx.xxx.xxx.xxx"
In reality, your service provider has already assigned your connection a
name:
"dialup_xx.internet.service.provide.com"
When the server sends data to the applet, the host name on the data will be
that of the service provider. Netscape will flag this as a security
violation and raise the dreaded SecurityException. The solution is to enter
the actual connection name into the initial URL, but determining this name
is a problem. The easiest method I've found is to go ahead and use the IP
address initially. When the exception is raised, open the Java console to
discover the actual connection name. Use this name instead and your applet
will work wonderfully.
---------------------------------------------------------------------------
The PORT parameter needs to be coded into the HTML applet file so the
applet knows on which port the server is listening. The HTML tag for this
applet looks like this:
<applet
codebase="/classes"
code="SimpleClientApplet.class"
width=500
height=500
>
<param name="PORT" value="4545">
</applet>
Adding the DGTP Server Thread
Because the BasicWebServer project was written in Java, it's trivial to add
an instance of the DGTPServer. The problem is some object needs to
implement the LiveDataServer interface. The base server class could be
changed to add this behavior, but then it would have to be rewritten any
time you wanted a new service thread. A better solution is to create a
separate thread whose only purpose is to spawn and communicate with the
DGTPServer. To this end, the NumUsersServer class was created. It really
doesn't do much, but it does create the needed interface and enable simple
integration with the web server. Listing 9.13 shows the NumUsersServer.
Listing 9.13. The NumUsersServer class.
import java.lang.Thread;
import java.net.DatagramPacket;
import DGTPServer;
import LiveDataServer;
import ClientAddr;
public class NumUsersServer extends Thread
implements LiveDataServer
{
private DGTPServer servThread = null;
public NumUsersServer(int hostPort)
{
servThread = new DGTPServer(this, hostPort);
}
public void run()
{
servThread.start();
while(true) yield();
}
public boolean ValidateRegistrant(ClientAddr user)
{
return true;
}
public void NewRegistrant(ClientAddr user)
{
servThread.sendToUsers("CLIENTS " + servThread.Clients.size());
}
public void DropRegistrant(ClientAddr user)
{
servThread.sendToUsers("CLIENTS " + servThread.Clients.size());
}
public void recvNewData(byte[] newDataBlock, DatagramPacket fromWho)
{
System.out.println("Receive data block...discarding");
}
}
The run() method starts the DGTP server and then enters into an infinite
while loop. It calls the yield() function to avoid interfering with other
active threads.
The thread is now added to the BasicWebServer class in the start() method:
// Create the server socket
serverSocket = new ServerSocket(HTTP_PORT, 5);
// Create and start any additional
// server thread services here
st = new NumUsersServer(4545);
st.start();
The project is now finished, so compile all the source code and start the
server. If you maintained the directory structure of the CD-ROM, you should
be able to start the server and connect to it! The client applet classes
are contained under htdocs/classes. The default HTML document is in
htdocs/index.html.
Summary
In this chapter, you learn about the socket abstraction as well as the Java
implementation of sockets. After some basic client/server applications, a
full HTTP server is undertaken. You should have a working knowledge of HTTP
and an appreciation for socket applet security. The last part of the
chapter introduces the DGTP protocol for applet/server interaction. This
protocol will be reused in Chapter 11, "Building a Live Data Applet." as
the basis for a live data server, but first, you must learn to work with
native methods for database access.
--
¡ù À´Ô´:¡¤¹þ¶û±õ×϶¡ÏãÕ¾ bbs1.hit.edu.cn¡¤[FROM: jqc@pclinux.hit.edu.]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
Ò³ÃæÖ´ÐÐʱ¼ä£º823.954ºÁÃë