| ## Java 1.1 Unleashed # - 26 - Java Socket Programming by Stephen Ingram IN THIS CHAPTER An Introduction to Sockets Java Connection-Oriented Classes Java Datagram Classes An HTTP Server Application For full Java client/server applet connectivity, an applet server is required. This chapter initiates the development of a Java HTTP server. Before beginning the development of the server, however, you need some background knowledge of socket programming. This chapter begins with a socket overview followed by an exploration of Java's socket classes. The remainder of the chapter focuses on the construction of a Java HTTP Web server. 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 able to apply the Java socket classes Understand applet socket use and limitations Comprehend the HTTP Java server ## An Introduction to Sockets 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 may 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, each uses 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 server's destination address and 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 process is known as binding to a port. ### Socket Transmission Modes Sockets have two major modes of operation: connection-oriented and connectionless modes. Connection-oriented sockets operate like a telephone: they must establish a connection and then 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 an order different from that in which they were sent. The mode you use is determined by an application's needs. If reliability is important, connection-oriented operation is better. File servers must have all their data arrive correctly and in sequence. If some data is lost, the server's usefulness is invalidated. Some applications--time servers, for example--send discrete chunks of data at regular intervals. If data were to get lost, the server would not want the network to retry because by the time the re-sent 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 have 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 must 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 operation, 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. This chapter discusses the connection-oriented classes 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 we cover it first. Listing 26.1 shows a simple client application. It requests an HTML document from a server and displays the response to the console. #### Listing 26.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.
    */
    public class SimpleWebClient {
        public static void main(String args[])
        {
            try
            {
                // Open a client socket connection
                Socket clientSocket1 = new Socket("www.javasoft.com", 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() );
                BufferedReader inbound = new BufferedReader(
                    new InputStreamReader(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);
                }

                // Clean up
                outbound.close();
                inbound.close();
                clientSocket.close();
            }
            catch (IOException ioe)
            {
                System.out.println("IOException: " + ioe);
            }
        }
    }
NOTE: The examples in this chapter are coded as applications to avoid security restrictions. Run the code from the command line `as java` ClassName. Recall that a client socket issues a connect call 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, port number `80` was specified (the well-known port number for the HTTP protocol). Other well-known port numbers are shown in Table 26.1. Port numbers were not originally mandated by any governing body, but were assigned by convention--this is why they are said to be "well known." Currently, port numbers are assigned by the Internet Assigned Numbers Authority (IANA), although port numbers less than `1024` are still referred to as "well known." #### Table 26.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() );
    BufferedReader inbound = new BufferedReader(new
    InputStreamReader(clientSocket.getInputStream()) );
Once the streams are created, normal stream operations can be performed. The following code snippet requests a Web page and echoes the response to the screen:
    outbound.writeBytes("GET / HTTP/1.0\r\n\r\n);
    String responseLine;
    while ( (responseLine = inbound.readLine()) != null)
    {
        System.out.println(responseLine);
    }
When the program is done using the socket, the connection must be closed, like this:
    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. ### Client Socket Options JDK 1.1 has added capabilities that allow a subset of Berkeley-style socket options. Options give you tighter control over the socket's behavior. For client sockets, there are three socket options under user control: `SO_LINGER` `SO_TIMEOUT` `TCP_NODELAY` Each option entails significant complexity and should be exercised only when you have a thorough understanding of their operation. #### The SO_LINGER Socket Option The `SO_LINGER` option is referenced by these complimentary socket methods: `public int getSoLinger()` `public void setSoLinger(boolean` on`, int` val`)` When sockets are told to close, they undertake an orderly shutdown. This shutdown is an end-to-end cooperative process: First a TCP finish (`<FIN>`) command is sent. The other side responds with a TCP acknowledgment (`<ACK><FIN>`). Finally, the closing socket sends `<ACK>`. Each connected socket maintains a record of the average time required by round-trip transmissions. If no acknowledgment is received before the average round-trip timeout, a TCP abort (`<RST>`) is sent and the connection is closed unilaterally. This timeout is controllable with the `SO_LINGER` option. Additionally, setting a `SO_LINGER` time of zero causes an abortive close. Abortive closes cause any queued data to be discarded and a TCP `<RST>` command to be transmitted. CAUTION: It is normally best to leave the `SO_LINGER` option disabled. This is the default setting and should be changed only with great caution. Most TCP protocol stacks choose a close timeout based on a historical analysis of the connection. It is presumptive to assume that you can choose this value more accurately. #### The SO_TIMEOUT Socket Option The `SO_TIMEOUT` option is much more useful than `SO_LINGER`. Where `SO_LINGER` exists for expert-level users, `SO_TIMEOUT` has practical applications for the average Java programmer. Normally, reading from a connected socket blocks the calling thread until data is received or the socket is closed. Setting `SO_TIMEOUT` allows this behavior to change. There are two complimentary methods for `SO_TIMEOUT`: p`ublic synchronized int getSoTimeout()` `public synchronized void setSoTimeout(int` timeout`)` This option represents the timeout in milliseconds. Any read operation returns an `InterruptedIOException` if no data is received before the timeout expires. The default is `-1` (not set). #### The TCP_NODELAY Socket Option The final socket option allows selective application of the Nagle algorithm. John Nagle had a congestion problem in his large internetwork at Ford Aerospace. In response, he formulated a simple algorithm: no TCP packets can be sent until either all previous packets are acknowledged or a maximum segment size (MSS) is reached. Such a simple algorithm had dramatic results, but also caused data to be accumulated in buffers rather than being immediately transmitted. The default setting for the `TCP_NODELAY` option is disabled; in reality, there is little reason to change this setting. Only applications that require immediate application-level acknowledgment to sent packets would experience any noticeable effects of a change to this setting. The classic example is a UNIX X-server client. This application sends mouse movements and expects immediate responses in order to position the cursor. The Nagle algorithm would queue mouse movements, thus skewing the cursor's placement. For this application, `TCP_NODELAY` should be enabled. There are two complimentary methods for manipulating `TCP_NODELAY`: `public boolean getTcpNoDelay()` `public void setTcpNoDelay(boolean` on`)` ### TCP Interfaces JDK 1.1 also added support for multihomed computers. A multihomed computer has more than one TCP interface (typically found on machines that have both local Ethernet and Internet access). In response to the need to use a specific interface, the socket class has an additional pair of constructors that allow users to choose a local network interface address: `public Socket(String` host`, int` port`, InetAddress` localAddr`, int` localPort`)` `public Socket(InetAddress` address`, int` port`, InetAddress` localAddr`, int` localPort`)` Using a server socket is only slightly more complicated than using a client socket, as explained in the following section. ### Server Sockets Listing 26.2 is a partial listing of a simple server application. The complete server example can be found on the CD-ROM that accompanies this book in the file `SimpleWebServer.java`. #### Listing 26.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
        {

            BufferedReader inbound = null;
            OutputStream outbound = null;
            try
            {
                // Acquire the input stream
                inbound = new BufferedReader(
                    new InputStreamReader(client.getInputStream()) );

                // 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 = client.getOutputStream();
                        outbound.write(buffer.toString().getBytes());
                        break;

                    }
                }
            }
            finally
            {
                // Clean up
                System.out.println("Cleaning up connection: " + client);
                outbound.close();
                inbound.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 an optional listen stack depth. A server can receive connection requests from many clients at the same time, but each call is processed one at a time. The listen stack is a queue of unanswered connection requests. The preceding code instructs the socket driver to maintain the last five connection 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 spawns a new socket in the `accept()` method. The server socket is still open and queuing new connection requests. As you do with the client socket, the next step is to create input and output streams:
    BufferedReader inbound = new BufferedReader(new
    InputStreamReader(client.getInputStream()) );
    OutputStream outbound = client.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 waits for one to arrive. This behavior is known as blocking. The `accept()` method blocks the server thread from performing any other tasks until a new call arrives. When five connections have been serviced, the server exits by closing its server socket. Any queued calls are canceled. NOTE: The `SimpleWebServer` application produces no output. To exercise it, you can either use a browser or the `SimpleWebClient` application from Listing 26.1. Both cases require the current machine's name. You can substitute `localhost` if you are unsure of your machine's name. Browsers should be pointed to `http://localhost/`. 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 26.1 summarizes the steps needed for client/server connection-oriented applications. Figure 26.1. Client and server connection-oriented applications. ### Iterative and Concurrent Servers The client/server application just presented is known as an iterative server because the code accepts a client connection and completely processes it before it accepts another connection. More complex servers are concurrent servers: 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 the client and server portions of connection-oriented classes, the datagram versions of the client and server behave in nearly identical manners--the only difference occurs in implementation. For the datagram model, 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`. Because the client calls 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. Because streams can't be acquired for communication, how do you talk to a `DatagramSocket` object? 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 must know where to place the received data. A 512-byte buffer is created and passed to the constructor as the first parameter. The second constructor parameter is the size of the buffer. Like the `accept()` method in the `ServerSocket` class, the `receive()` method blocks 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 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 to send 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 multihomed: 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("localhost");
    InetAddress addr2[] = InetAddress.getAllByName("localhost");
    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 is 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[] = toSend.getBytes();
    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. Then a new `DatagramPacket` instance must be created. Notice the two extra parameters at the end of the constructor. Because 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 );
### Datagram Servers Unlike connection-oriented operation, datagram servers are actually less complicated than the datagram client. The basic script for a datagram server 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 26.3 shows a simple datagram echo server. This server echoes back any packets it receives. #### Listing 26.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 datagram client uses the same process as the datagram server with one exception: the 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 incoming data. 5. Go back to step 3 (send more data), go back to step 4 (wait for incoming data) or go to step 6 (exit). 6. Close the datagram socket. Figure 26.2 summarizes the steps needed for client/server datagram applications. The symmetry between client and server is evident in this figure; compare Figure 26.2 with Figure 26.1. Figure 26.2. Client and server datagram applications. Listing 26.4 shows a simple datagram client. It reads user input strings and sends them to the echo server presented in Listing 26.3. The echo server sends the data right back, and the client prints the response to the console. #### Listing 26.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("localhost");

                BufferedReader userData = new BufferedReader(
                    new InputStreamReader(System.in) );
                while (socket != null)
                {
                    String userString = userData.readLine();
                    if (userString == null || userString.equals(""))
                        return;

                    byte sendbuf[] = userString.getBytes();
                    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);
            }
        }
    }
### Multicast Sockets In JDK 1.1, Sun chose to move the `Multicast` class into the `java.net` package. `Multicast` descends from `Datagram` and so shares many similarities. A multicast address is a class D address in the range `224.0.0.1` to `239.255.255.255`, inclusive. The most noticeable difference between `Datagram` and `Multicast` is `Multicast`'s capability to transmit to all listening hosts simultaneously. In fact, when you send on a multicast socket, you also receive everything you send. The downside to multicast sockets is the lack of support for multicast routing. Normally, multicasting operates only over a local network. Most routers still will not forward multicast packets to the Internet at large. Multicasting is based on the Internet Group Management Protocol (IGMP). To begin receiving multicast transmissions, a host must join a multicast group using an IGMP packet. The `Multicast` class provides two methods for manipulating group membership: `public void joinGroup(InetAddress` mcastaddr`)` `public void leaveGroup(InetAddress` mcastaddr`)` Two additional methods control the time to live (TTL): `public byte getTTL()` `public void setTTL(byte` ttl`)` Each network router examines a packet's ttl parameter and decrements it before forwarding the packet. If the ttl parameter goes to zero, the packet is not forwarded. In the event that a network's router supports multicasting, the ttl parameter becomes critical. To avoid flooding the Internet with your multicast packets, it is best to leave the ttl parameter set to `1`. NOTE: Chapter 24, "Introduction to Network Programming," mentions the UNIX utility `traceroute`. This program reveals the address and names of the routes traversed to arrive at a specific destination. This utility works by manipulating the ttl parameter and relying on the intermediate routers to send back an error when ttl goes to zero. By continuously incrementing ttl and resending the same packet, `traceroute` can log the resulting errors, thus revealing the exact route to a destination. Listing 26.5 contains the source for a multicast test class. When executed, the test class sends and receives its own string. #### Listing 26.5. A multicast test class.
    class MultiTest
    {
        public static void main(String[] args)
        {
            try
            {
                //byte[] msg = {'H', 'e', 'l', 'l', 'o'};
                InetAddress group = InetAddress.getByName("227.1.2.3");
                MulticastSocket s = new MulticastSocket(4567);
                s.joinGroup(group);
                String usermsg = "Hello";
                byte msg[] = usermsg.getBytes();
                DatagramPacket hi = new DatagramPacket(msg, msg.length,
                    group, 4567);
                s.send(hi);
                byte[] buf = new byte[1000];
                DatagramPacket recv = new DatagramPacket(buf, buf.length);
                s.receive(recv);
                System.out.write(buf, 0, recv.getLength());
                s.leaveGroup(group);
            }
            catch (Exception se)
            {
                System.out.println("Exception: " + se);
            }
        }
    }
So far, all the examples in this chapter have been Java standalone applications. Running these examples within the framework of an applet presents an extra complication: security. APPLET SECURITY AND SOCKETS When writing applications, you don't have to be concerned with security exceptions. This fact changes when the code under development is executed from an applet. Browsers use very stringent security measures where sockets are concerned. An applet can open a socket only back to the host name from which it was loaded. If any other connection is attempted, a `SecurityException` is thrown. Because datagram sockets don't open connections, how is security ensured 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. Multicast sockets are completely illegal within an applet. Only applications can create and use multicast sockets. ## An HTTP Server Application Client applets need an HTTP Web server so that they can open sockets under the prevailing security restrictions. If an applet is loaded into a browser from a hard drive, any socket actions are permissible. This presents a significant hurdle to Java client applet development and testing. A simple solution is to write an HTTP server application. Once written, additional server threads can be added to provide all types of back-end connectivity. The server example can also demonstrate how to subclass `Socket` and `ServerSocket`. JDK 1.1 has removed the `final` keyword restriction from the socket classes; thus, it now allows subclassing. In addition, slight modifications were added to `ServerSocket` to allow the newly allowed descendant classes access to the underlying implementation. ### HTTP Primer Before diving into this project, you need some background information about 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. Our server will support a subset of HTTP version 1.0 in that it will handle only file requests. As long as browser 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 of the protocol is sent in plain-text format. An example of a conversation was demonstrated in Listings 26.1 and 26.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 26.2 lists all the request methods for HTTP version 1.0. #### Table 26.2. HTTP version 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 linefeed (`\n`) is received. After the method line, a number of optional lines can be sent. Netscape Navigator 2.0 produces 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 linefeed. The first line contains a version identification string followed by a status code indicating the results of the request. Table 26.3 lists all the defined status codes. Our server sends only two of these: `200` and `404`. The text that follows the status code is optional; it may be omitted. If it is present, it may not match the definitions given in the table. #### Table 26.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 for you to construct a basic Web server. Full information on the HTTP protocol can be retrieved from this URL: `HTTP://www.w3.org/` ### A Basic Web Server This basic Web server example follows the construction of the `SimpleWebServer` presented in Listing 26.2. Many improvements will be made to method and response handling. Perhaps the largest improvement is the addition of specialized socket classes. Because `ServerSocket` and `Socket` are no longer `final` classes, you are free to create more specialized descendant classes. This project creates `HttpServerSocket` and `HttpSocket` classes. Before developing the specialized classes, the `HttpServer` shell class is presented. #### HttpServer Application Class The outer class for the server is actually quite simple. All the complex protocol work is relegated to the specialized HTTP sockets. Listing 26.6 shows the socket access routines of the `HttpServer` class. #### Listing 26.6. HttpServer class socket access routines.
    /**
         * The only method to begin server operation.
         * The server port is hard coded to 80 by HTTP_PORT.
         * Any additional server threads should be started from
         * this routine.
         */
        public void start()
        {
            HttpServerSocket serverSocket = null;
            HttpSocket clientSocket = null;

            try
            {
                // Create the server socket
                serverSocket = new HttpServerSocket(HTTP_PORT, 5);
            }
            catch (IOException e)
            {
                System.out.println(
                    "Couldn't open listen socket " + HTTP_PORT + " " + e);
                System.exit(10);
            }

            try
            {
                do
                {
                    /* the main loop for processing incoming requests */
                    clientSocket = (HttpSocket)serverSocket.accept();
                    System.out.println("Connect from: " + clientSocket );
                    ServiceClientRequest(clientSocket);
                    clientSocket.close();
                } while (clientSocket != null);
            }
            catch (IOException e)
            {
                System.out.println(
                    "Accept failure on port " + HTTP_PORT + " " + e);
                System.exit(10);
            }
        }

        /**
         * Read the client request and formulate a response.
         */
        private void ServiceClientRequest(HttpSocket client)
        {
            if ( client.method.equals("GET") || client.method.equals("HEAD") )
                ServicegetRequest(client);
            else
            {
                System.out.println("Unimplemented method: " + client.method);
                client.sendNegativeResponse();
            }
        }

        /**
         * Get the file stream and pass it to the HttpSocket.
         * Handles GET and HEAD request methods.
         * @param client = the HttpSocket to respond to
         */
        private void ServicegetRequest(HttpSocket client)
        {
            String mimeType = "application/octet-stream";

            try
            {
                if (client.file.indexOf("..") != -1)
                    throw new ProtocolException("Relative paths not supported");
                String fileToGet = "htdocs" + client.file;
                FileInputStream inFile = new FileInputStream(fileToGet);
                if (fileToGet.endsWith(".html")) mimeType = "text/html";
                client.sendFile(inFile, inFile.available(), mimeType);
                inFile.close();
            }
            catch (FileNotFoundException fnf)
            {
                client.sendNegativeResponse();
            }
            catch (ProtocolException pe)
            {
                System.out.println("ProtocolException: " + pe);
                client.sendNegativeResponse();
            }
            catch (IOException ioe)
            {
                System.out.println("IOException: Unknown file length: " + ioe);
                client.sendNegativeResponse();
            }
        }
Implementing subclassed socket servers actually involves creating two cooperating classes. `ServerSocket` performs only listening chores; the real protocol work is done by the connected `Socket` class. `HttpServerSocket` subclasses `ServerSocket` and creates a connected `HttpSocket` object to parse the incoming HTTP request. Because the `accept()` method of `ServerSocket` returns only a plain `Socket`, you must perform an explicit cast to store the actual object type:
    clientSocket = (HttpSocket)serverSocket.accept();
Listing 26.7 shows the `HttpServerSocket` class. The key element is the overridden `accept()` method. To allow subclasses to perform connection services, `ServerSocket` provides the `implAccept()` method. This method takes an existing `Socket` object and connects it to the next call. This is how the `HttpServerSocket` connects an `HttpSocket` to incoming requests. After connecting a socket, the server simply calls the `HttpSocket` `getRequest()` method to parse the protocol. Once it has been processed, the connected socket is simply returned to the caller. #### Listing 26.7. The HttpServerSocket implementation.
    /**
     * A class that provides HTTP protocol services.
     */
    public class HttpServerSocket extends ServerSocket
    {
        HttpServerSocket(int port, int depth) throws IOException
        {
            super(port, depth);
        }

        public Socket accept () throws IOException
        {
            HttpSocket s = new HttpSocket();
            implAccept(s);
            s.getRequest();
            return s;
        }
    }
The `HttpSocket` performs all the protocol-specific work. Listing 26.8 shows the methods used to parse and store the inbound HTTP request. First, an input stream is acquired. The specifics of the HTTP header are then parsed and stored as class member variables. These are `public` variables so that consuming classes can make decisions based on the HTTP header. #### Listing 26.8. The HttpSocket class.
    public class HttpSocket extends Socket
    {
        protected BufferedReader inbound = null;

        public String version = null;
        public String method = null;
        public String file = null;
        public NameValue headerpairs[];
        public String extraHdr = null;

        HttpSocket()
        {
            super();
            headerpairs = new NameValue[0];
        }

        /**
         * Read a HTTP request and parse it into class attributes.
         * @exception ProtocolException If not a valid HTTP header
         * @exception IOException
         */
        public void getRequest()
            throws IOException, ProtocolException
        {
            try
            {
                // Acquire an input stream for the socket
                inbound = new BufferedReader(
                    new InputStreamReader(getInputStream()) );

                // Read the header into a String
                String reqhdr = readHeader(inbound);

                // Parse the string into parts
                parseReqHdr(reqhdr);
            }
            catch (ProtocolException pe)
            {
                if ( inbound != null )
                    inbound.close();
                throw pe;
            }
            catch (IOException ioe)
            {
                if ( inbound != null )
                    inbound.close();
                throw ioe;
            }
        }

        /**
         * Assemble a HTTP request header String
         * from the passed BufferedReader.
         * @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(BufferedReader 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 void parseReqHdr(String reqhdr)
            throws IOException, ProtocolException
        {
            // 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");
            method = members.nextToken();
            file = members.nextToken();
            if (file.equals("/")) file += "../index.html";
            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();
                    addNameValue(name, value);
                }
            }
        }

        /**
         * Add a name/value pair to the internal array
         */
        private 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);
            }
        }
    }
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 throws an exception. Otherwise, the resulting string is passed to `parseReqHdr()` for processing. These routines 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 described in Chapter 11, "The Utilities Package." Normally, it is preferable to close the inbound stream as soon as the request has been completely read. If this is done, subsequent output attempts will fail with an `IOException`. This is why the inbound parameter is stored as a class attribute rather than allocated within the `getRequest()` method. When the output has been completely sent, both the output and the input streams are closed. CAUTION: Do not be tempted to close an inbound stream after all input has been read. Prematurely closing the input stream causes subsequent output attempts to fail with an `IOException`. Close both streams only after all socket operations are finished. Once a request has been processed, methods for sending responses must be provided. Because only the `GET` and `HEAD` requests are honored, `HttpSocket` only provides methods for sending files or negative responses. Listing 26.9 shows the two response routines. Once a valid file is found, the `sendFile()` function can be called. The file is read and sent in 1K blocks. This design keeps memory usage down while seeking to balance the number of disk accesses attempted. Negative responses are sent only for errors that occur after the request has been built. As a consequence, improperly formatted requests generate no response. #### Listing 26.9. HttpSocket response methods.
    /**
         * Send a negative (404 NOT FOUND) response
         */
        public void sendNegativeResponse()
        {
            OutputStream outbound = null;

            try
            {
                // Acquire the output stream
                outbound = getOutputStream();

                // Write the negative response header
                String hdr = "HTTP/1.0 404 NOT_FOUND\r\n\r\n";
                outbound.write(hdr.getBytes());

                // Cleanup
                outbound.close();
                inbound.close();
            }
            catch (IOException ioe)
            {
                System.out.println("IOException while sending -rsp: " + ioe);
            }
        }

        /**
         * Send the passed file
         * @param inFile the opened input file stream to send
         * @param fileSize the size of the stream (used to report Content-Length)
         * @param MimeType String used to report Content-Type
         */
        public void sendFile(FileInputStream inFile, int fileSize, String MimeType)
        {
            OutputStream outbound = null;

            try
            {
                // aquire the output stream
                outbound = getOutputStream();

                // Send the response header
                String hdr = "HTTP/1.0 200 OK\r\n";
                hdr += "Content-type: " + MimeType + "\r\n";
                hdr += "Content-Length: " + fileSize + "\r\n";
                if (extraHdr != null) hdr += extraHdr;
                hdr += "\r\n";

                outbound.write(hdr.getBytes());

                // If not a HEAD request, send the file body.
                // HEAD requests only solicit a header response.
                if (!method.equals("HEAD"))
                {
                    byte dataBody[] = new byte[1024];
                    int cnt;
                    while ((cnt = inFile.read(dataBody)) != -1)
                    {
                        outbound.write(dataBody, 0, cnt);
                    }
                }
            }
            catch (IOException ioe)
            {
                System.out.println("IOException while sending file: " + ioe);
            }
            try
            {
                // Cleanup
                outbound.close();
                inbound.close();
            }
            catch (IOException ioe)
            {
                System.out.println("IOException closing streams in sendFile: " + ioe);
            }
        }
The `SimpleWebServer` project is now finished; compile all the source code and start the server. If you maintained the directory structure used on the CD-ROM that accompanies this book, you should be able to start the server and connect to it. The default HTML document is in `htdocs/../index.html`. ## Summary In this chapter, you learned about the socket abstraction as well as the Java implementation of sockets. Remember that socket use requires at least two applications: a client and a server. The server waits for a client application to call and request attention. Multiple clients can make use of the same server--either at the same time (concurrent server) or one at a time (iterative server). Server behavior was demonstrated with the development of an iterative Java HTTP server. You should now have a working knowledge of HTTP and an appreciation of the limitations imposed by the socket security model. Namely, an applet can only open a socket back to the same server that loaded the applet. Sockets provide a rich communications medium that allows your Java applications to exploit a wired world.
| ©Copyright, Macmillan Computer Publishing. All rights reserved.