Chapter 12. Network Clients

12.0 Introduction

Java can be used to write many types of networked programs. In traditional socket-based code, the programmer is responsible for structuring the interaction between the client and server; the TCP “socket” code simply ensures that whatever data you send gets to the other end. In higher-level types, such as HTTP, RMI, CORBA, and EJB, the software takes over more control. Sockets are often used for connecting to “legacy” servers; if you were writing a new application from scratch, you’d be better off using a higher-level service.

It may be helpful to compare sockets with the telephone system. Telephones were originally used for analog voice traffic, which is pretty unstructured. Then it began to be used for some “layered” applications; the first widely popular one was facsimile transmission, or fax. Where would fax be without the widespread availability of voice telephony? The second wildly popular layered application historically was dial-up TCP/IP. This coexisted with the Web to become popular as a mass-market service. Where would dial-up IP be without widely deployed voice lines? And where would the Internet be without dial-up IP? FAX and dial-up are mostly gone now, but they paved the way for your smartphone’s networked ability, which is what makes it useful (and even seductive as a timesink).

Sockets are layered like that too. The Web, RMI, JDBC, CORBA, and EJB are all layered on top of sockets. HTTP is now the most common protocol, and should generally be used for new applications when all you want is to get data from point b to point a.

Ever since the alpha release of Java (originally as a sideline to the HotJava browser) in May 1995, Java has been popular as a programming language for building network applications. It’s easy to see why, particularly if you’ve ever built a networked application in C. First, C programmers have to worry about the platform they are on. Unix uses synchronous sockets, which work rather like normal disk files vis-a-vis reading and writing, whereas Microsoft OSes use asynchronous sockets, which use callbacks to notify when a read or write has completed. Java glosses over this distinction. Further, the amount of code needed to set up a socket in C is intimidating. Just for fun, Example 12-1 shows the “typical” C code for setting up a client socket. And remember, this is only the Unix part. And only the part that makes and closes the connection. To be portable to Windows, it would need some additional conditional code (using C’s #ifdef mechanism). And C’s #include mechanism requires that exactly the right files be included, and some files have to be listed in particular orders (Java’s import mechanism is much more flexible).

Example 12-1. main/src/main/java/network/Connect.c - C client setup
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int
main(int argc, char *argv[])
{
    char* server_name = "localhost";
    struct hostent *host_info;
    int sock;
    struct sockaddr_in server;

    /* Look up the remote host's IP address */
    host_info = gethostbyname(server_name);
    if (host_info == NULL) {
        fprintf(stderr, "%s: unknown host: %s
", argv[0], server_name);
        exit(1);
    }

    /* Create the socket */
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("creating client socket");
        exit(2);
    }

    /* Set up the server's socket address */
    server.sin_family = AF_INET;
    memcpy((char *)&server.sin_addr, host_info->h_addr,
                     host_info->h_length);
    server.sin_port = htons(80);

    /* Connect to the server */
    if (connect(sock,(struct sockaddr *)&server,sizeof server) < 0) {
        perror("connecting to server");
        exit(4);
    }

    /* Finally, we can read and write on the socket. */
    /* ... */

    (void) close(sock);
}

In the first recipe, we’ll see how to do the connect in essentially one line of Java (plus a bit of error handling). We’ll then cover error handling and transferring data over a socket. Next, we’ll take a quick look at a datagram or UDP client that implements most of the TFTP (trivial file transfer protocol) that has been used for two decades to boot diskless workstations. We’ll end with a program that connects interactively to a chat server.

A common theme through most of these client examples is to use existing servers so that we don’t have to generate both the client and the server at the same time. Most of these are services that exist on any standard Unix platform. If you can’t find a Unix server near you to try them on, let me suggest that you take an old PC, maybe one that’s underpowered for running the latest Microsoft software, and put up a free, open source Unix system on it. My personal favorite is OpenBSD, and the market’s overall favorite is Linux. Both are readily available and can be installed for free over the Internet, and offer all the standard services used in the client examples, including the time servers and TFTP. Both have free Java implementations available.

I also provide basic coverage of “web services” clients. The term “web services” has come to mean “program-to-program communication using HTTP.” The two general categories are SOAP-based and REST-based. REST services are very simple—you send an HTTP request and get back a response in plain text, or JSON (Chapter 14) or XML. SOAP is more complicated, and not covered in this book. There is more information on the client-side connections in Java Network Programming (O’Reilly). I don’t cover the server-side APIs for building web services—JAX-RS and JAX-WS—because these are covered in several O’Reilly books.

12.1 HTTP/REST Web Client

Problem

You need to read from a URL, e.g., to connect to a RESTful web service or to download a web page or other resource over http/https.

Solution

Use the standard Java 11 HttpClient or the URLConnection class.

This technique applies anytime you need to read from a URL, not just a RESTful web service.

Discussion

Prior to Java 11, you had to either use the URLConnection class or download and use the older Apache HTTP Client Library . With Java 11, there is a fairly easy-to-use and flexible API in standard Java. This also supports HTTP/2.0; which the Apache HttpClient and the URLConnection do not, as of this writing.

As our simple example, we’ll use Google’s “suggestion” service, that is, what you see when you type the first few characters of a search into the Google web search engine.

This Google service supports various output formats. The base URL is just the following: append the word you want suggestions on.

https://suggestqueries.google.com/complete/search?client=firefox&q=

The client=firefox tells it we want a simple JSON format; with client=chrome it contains more fields.

To use the Java HTTPClient API, you need a HttpClient object which you get using the Builder pattern, then create a Request object.

        // This object would be kept for the life of an application
        HttpClient client = HttpClient.newBuilder()
            .followRedirects(Redirect.NORMAL)
            .version(Version.HTTP_1_1)
            .build();

        // Build the HttpRequest object to "GET" the urlString
        HttpRequest req =
            HttpRequest.newBuilder(URI.create(urlString +
                URLEncoder.encode(keyword)))
            .header("User-Agent", "Dept of Silly Walks")
            .GET()
            .build();

The HttpRequest object can be sent using the client to get a HttpResponse object, from which you can get the status and/or the body. Sending can be done either synchronously (if you need the results right away) or asynchronously (if you can usefully do something else in the meantime).

        // Send the request - synchronously
        HttpResponse<String> resp =
            client.send(req, BodyHandlers.ofString());

        // Collect the results
        if (resp.statusCode() == 200) {
            String response = resp.body();
            System.out.println(response);
        } else {
            System.out.printf("ERROR: Status %d on request %s
",
                resp.statusCode(), urlString);
        }
        // Send the request - asynchronously
        client.sendAsync(req, BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAccept(System.out::println)
            .join();

Here is the output; the line has been broken at commas to make it fit on the page:

$ java HttpClientDemo.java
["darwin",["darwin thompson","darwin","darwin awards","darwinism",
 "darwin australia","darwin thompson fantasy","darwin barney",
 "darwin theory","darwinai","darwin dormitorio"]]

Should you not wish to use the HttpClient library, you could use the legacy code in java.net, since all we usually need here is the ability to open and read from a URL. Here is the code using a URLConnection:

public class RestClientURLDemo {
    public static void main(String[] args) throws Exception {
        URLConnection conn = new URL(
            HttpClientDemo.urlString + HttpClientDemo.keyword)
            .openConnection();
        try (BufferedReader is =
            new BufferedReader(new InputStreamReader(conn.getInputStream()))) {

            String line;
            while ((line = is.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

The output should be identical to what the HttpClient version produced.

See Also

Don’t confuse this HttpClient with the older Apache HttpClient Library, still available at https://hc.apache.org/httpcomponents-client-ga/index.html.

You can find more information on REST services (including implementing the server-side components for them) in Bill Burke’s RESTful Java with JAX-RS 2.0, 2nd Edition (O’Reilly).

12.2 Contacting a Socket Server

Problem

You need to contact a server using TCP/IP.

Solution

Just create a java.net.Socket, passing the hostname and port number into the constructor.

Discussion

There isn’t much to this in Java. When creating a socket, you pass in the hostname and the port number. The java.net.Socket constructor does the gethostbyname() and the socket() system call, sets up the server’s sockaddr_in structure, and executes the connect() call. All you have to do is catch the errors, which are subclassed from the familiar IOException. Example 12-2 sets up a Java network client, but doesn’t actually do any I/O yet. It uses try-with-resources to ensure that the socket is closed automatically when we are done with it.

Example 12-2. main/src/main/java/network/ConnectSimple.java (simple client connection)
import java.net.Socket;

/* Client with NO error handling */
public class ConnectSimple {

    public static void main(String[] argv) throws Exception {

        try (Socket sock = new Socket("localhost", 8080)) {

            /* If we get here, we can read and write on the socket "sock" */
            System.out.println(" *** Connected OK ***");

            /* Do some I/O here... */

        }
    }
}

This version does no real error reporting, but a version called ConnectFriendly does; we’ll see this version in Recipe 12.4.

See Also

Java supports other ways of using network applications. You can also open a URL and read from it (see Recipe 12.8). You can write code so that it will run from a URL, when opened in a web browser, or from an application.

12.3 Finding and Reporting Network Addresses

Problem

You want to look up a host’s address name or number or get the address at the other end of a network connection.

Solution

Get an InetAddress object.

Discussion

The InetAddress object represents the Internet address of a given computer or host. It has no public constructors; you obtain an InetAddress by calling the static getByName() method, passing in either a hostname like darwinsys.com or a network address as a string, like 1.23.45.67. All the “lookup” methods in this class can throw the checked UnknownHostException (a subclass of java.io.IOException), which must be caught or declared on the calling method’s header. None of these methods actually contact the remote host, so they do not throw the other exceptions related to network connections.

The method getHostAddress() gives you the numeric IP address (as a string) corresponding to the InetAddress. The inverse is getHostName(), which reports the name of the InetAddress. This can be used to print the address of a host given its name, or vice versa:

public class InetAddrDemo {
    public static void main(String[] args) throws IOException {
        String hostName = "darwinsys.com";
        String ipNumber = "8.8.8.8"; // currently a well-known Google DNS server

        // Show getting the InetAddress (looking up a host) by host name
        System.out.println(hostName + "'s address is " +
            InetAddress.getByName(hostName).getHostAddress());

        // Look up a host by address
        System.out.println(ipNumber + "'s name is " +
            InetAddress.getByName(ipNumber).getHostName());

        // Look up my localhost addresss
        final InetAddress localHost = InetAddress.getLocalHost();
        System.out.println("My localhost address is " + localHost);

        // Show getting the InetAddress from an open Socket
        String someServerName = "google.com";
        // assuming there's a web server on the named server:
        try (Socket theSocket = new Socket(someServerName, 80)) {
            InetAddress remote = theSocket.getInetAddress();
            System.out.printf("The InetAddress for %s is %s%n",
                someServerName, remote);
        }
    }
}

You can also get an InetAddress from a Socket by calling its getInetAddress() method. You can construct a Socket using an InetAddress instead of a hostname string. So, to connect to port number myPortNumber on the same host as an existing socket, you’d use:

InetAddress remote = theSocket.getInetAddress( );
Socket anotherSocket = new Socket(remote, myPortNumber);

Finally, to look up all the addresses associated with a host—a server may be on more than one network—use the static method getAllByName(host), which returns an array of InetAddress objects, one for each IP address associated with the given name.

A static method getLocalHost() returns an InetAddress equivalent to “localhost” or 127.0.0.1. This can be used to connect to a server program running on the same machine as the client.

If you are using IPv6, you can use Inet6Address instead.

See Also

See NetworkInterface in Recipe 13.2, which lets you find out more about the networking of the machine you are running on.

There is not yet a way to look up services—i.e., to find out that the HTTP service is on port 80. Full implementations of TCP/IP have always included an additional set of resolvers; in C, the call getservbyname("http", "tcp"); would look up the given service1 and return a servent (service entry) structure whose s_port member would contain the value 80. The numbers of established services do not change, but when services are new or installed in nonroutine ways, it is convenient to be able to change the service number for all programs on a machine or network (regardless of programming language) just by changing the services definitions. Java should provide this capability in a future release.

12.4 Handling Network Errors

Problem

You want more detailed reporting than just IOException if something goes wrong.

Solution

Catch a greater variety of exception classes. SocketException has several subclasses; the most notable are ConnectException and NoRouteToHostException. The names are self-explanatory: the first means that the connection was refused by the machine at the other end (the server machine), and the second completely explains the failure. Example 12-3 is an excerpt from the Connect program, enhanced to handle these conditions.

Example 12-3. ConnectFriendly.java
public class ConnectFriendly {
    public static void main(String[] argv) {
        String server_name = argv.length == 1 ? argv[0] : "localhost";
        int tcp_port = 80;
        try (Socket sock = new Socket(server_name, tcp_port)) {

            /* If we get here, we can read and write on the socket. */
            System.out.println(" *** Connected to " + server_name  + " ***");

            /* Do some I/O here... */

        } catch (UnknownHostException e) {
            System.err.println(server_name + " Unknown host");
            return;
        } catch (NoRouteToHostException e) {
            System.err.println(server_name + " Unreachable" );
            return;
        } catch (ConnectException e) {
            System.err.println(server_name + " connect refused");
            return;
        } catch (java.io.IOException e) {
            System.err.println(server_name + ' ' + e.getMessage());
            return;
        }
    }
}

12.5 Reading and Writing Textual Data

Problem

Having connected, you wish to transfer textual data.

Solution

Construct a BufferedReader or PrintWriter from the socket’s getInputStream() or getOutputStream().

Discussion

The Socket class has methods that allow you to get an InputStream or OutputStream to read from or write to the socket. It has no method to fetch a Reader or Writer, partly because some network services are limited to ASCII, but mainly because the Socket class was decided on before there were Reader and Writer classes. You can always create a Reader from an InputStream or a Writer from an OutputStream using the conversion classes. The paradigm for the two most common forms is:

BufferedReader is = new BufferedReader(
    new InputStreamReader(sock.getInputStream( )));
PrintWriter os = new PrintWriter(sock.getOutputStream( ), true);

Example 12-4 reads a line of text from the “daytime” service, which is offered by full-fledged TCP/IP suites (such as those included with most Unixes). You don’t have to send anything to the Daytime server; you simply connect and read one line. The server writes one line containing the date and time and then closes the connection.

Running it looks like this. I started by getting the current date and time on the local host, then ran the DaytimeText program to see the date and time on the server (machine darian is one of my Unix servers):

C:javasrc
etwork>date 
Current date is Sun 01-23-2000
Enter new date (mm-dd-yy):
C:javasrc
etwork>time
Current time is  1:13:18.70p
Enter new time:
C:javasrc
etwork>java network.DaytimeText darian
Time on darian is Sun Jan 23 13:14:34 2000

The code is in class DaytimeText, shown in Example 12-4.

Example 12-4. DaytimeText.java
public class DaytimeText {
    public static final short TIME_PORT = 13;

    public static void main(String[] argv) {
        String server_name = argv.length == 1 ? argv[0] : "localhost";

        try (Socket sock = new Socket(server_name,TIME_PORT);
            BufferedReader is = new BufferedReader(new
                InputStreamReader(sock.getInputStream()));) {
            String remoteTime = is.readLine();
            System.out.println("Time on " + server_name + " is " + remoteTime);
        } catch (IOException e) {
            System.err.println(e);
        }
    }
}

The second example, shown in Example 12-5, shows both reading and writing on the same socket. The Echo server simply echoes back whatever lines of text you send it. It’s not a very clever server, but it is a useful one. It helps in network testing and also in testing clients of this type!

The converse() method holds a short conversation with the Echo server on the named host; if no host is named, it tries to contact localhost, a universal alias2 for “the machine the program is running on.”

Example 12-5. main/src/main/java/network/EchoClientOneLine.java
public class EchoClientOneLine {
    /** What we send across the net */
    String mesg = "Hello across the net";

    public static void main(String[] argv) {
        if (argv.length == 0)
            new EchoClientOneLine().converse("localhost");
        else
            new EchoClientOneLine().converse(argv[0]);
    }

    /** Hold one conversation across the net */
    protected void converse(String hostName) {
        try (Socket sock = new Socket(hostName, 7);) { // echo server.
            BufferedReader is = new BufferedReader(new
                InputStreamReader(sock.getInputStream()));
            PrintWriter os = new PrintWriter(sock.getOutputStream(), true);
            // Do the CRLF ourself since println appends only a 
 on
            // platforms where that is the native line ending.
            os.print(mesg + "
"); os.flush();
            String reply = is.readLine();
            System.out.println("Sent "" + mesg  + """);
            System.out.println("Got  "" + reply + """);
        } catch (IOException e) {
            System.err.println(e);
        }
    }
}

It might be a good exercise to isolate the reading and writing code from this method into a NetWriter class, possibly subclassing PrintWriter and adding the and the flushing.

12.6 Reading and Writing Binary or Serialized Data

Problem

Having connected, you wish to transfer binary data, either raw binary data or serialized Java objects.

Solution

For plain binary date, construct a DataInputStream or DataOutputStream from the socket’s getInputStream() or getOutputStream(). For serialized Java object data, construct an ObjectInputStream or ObjectOutputStream.

Discussion

The simplest paradigm for reading/writing on a socket is:

DataInputStream is = new DataInputStream(sock.getInputStream());
DataOutputStream is = new DataOutputStream(sock.getOutputStream( ));

If the volume of data might be large, insert a buffered stream for efficiency. The paradigm is:

DataInputStream is = new DataInputStream(
    new BufferedInputStream(sock.getInputStream( )));
DataOutputStream is = new DataOutputStream(
    new BufferedOutputStream(sock.getOutputStream( )));

The program example in Example 12-6 uses another standard service that gives out the time as a binary integer representing the number of seconds since 1900. Because the Java Date class base is 1970, we convert the time base by subtracting the difference between 1970 and 1900. When I used this exercise in a course, most of the students wanted to add this time difference, reasoning that 1970 is later. But if you think clearly, you’ll see that there are fewer seconds between 1999 and 1970 than there are between 1999 and 1900, so subtraction gives the correct number of seconds. And because the Date constructor needs milliseconds, we multiply the number of seconds by 1,000.

The time difference is the number of years multiplied by 365, plus the number of leap days between the two dates (in the years 1904, 1908, . . . , 1968)—i.e., 19 days.

The integer that we read from the server is a C-language unsigned int. But Java doesn’t provide an unsigned integer type; normally when you need an unsigned number, you use the next-larger integer type, which would be long. But Java also doesn’t give us a method to read an unsigned integer from a data stream. The DataInputStream method readInt() reads Java-style signed integers. There are readUnsignedByte() methods and readUnsignedShort() methods, but no readUnsignedInt() method. Accordingly, we synthesize the ability to read an unsigned int (which must be stored in a long, or else you’d lose the signed bit and be back where you started from) by reading unsigned bytes and reassembling them using Java’s bit-shifting operators:

At the end of the code, we use the new date/time API (see Chapter 6) to construct and print a LocalDateTime object to show the current date and time on the local (client) machine:

$ date
Thu Dec 26 09:48:36 EST 2019
java network.RDateClient aragorn
Remote time is 3786360519
BASE_DIFF is 2208988800
Time diff == 1577371719
Time on aragorn is 2019-12-26T09:48:39
Local date/time = 2019-12-26T09:48:41.208180
$

The name aragorn is the hostname of one of my OpenBSD Unix computers. Looking at the output, you can see that the server agrees within a second or two. That confirms the date calculation code in Example 12-6. This protocol is commonly known as rdate, so the client code is called RDateClient.

Example 12-6. main/src/main/java/network/RDateClient.java
public class RDateClient {
    /** The TCP port for the binary time service. */
    public static final short TIME_PORT = 37;
    /** Seconds between 1970, the time base for dates and times
     * Factors in leap years (up to 2100), hours, minutes, and seconds.
     * Subtract 1 day for 1900, add in 1/2 day for 1969/1970.
     */
    protected static final long BASE_DAYS =
        (long)((1970-1900)*365 + (1970-1900-1)/4);

    /* Seconds since 1970 */
    public static final long BASE_DIFF = (BASE_DAYS * 24 * 60 * 60);

    public static void main(String[] argv) {
        String hostName;
        if (argv.length == 0)
            hostName = "localhost";
        else
            hostName = argv[0];

        try (Socket sock = new Socket(hostName,TIME_PORT);) {
            DataInputStream is = new DataInputStream(new
                BufferedInputStream(sock.getInputStream()));
            // Read 4 bytes from the network, unsigned.
            // Do it yourself; there is no readUnsignedInt().
            // Long is 8 bytes on Java, but we are using the
            // existing time protocol, which uses 4-byte ints.
            long remoteTime = (
                ((long)(is.readUnsignedByte()) << 24) |
                ((long)(is.readUnsignedByte()) << 16) |
                ((long)(is.readUnsignedByte()) <<  8) |
                ((long)(is.readUnsignedByte()) <<  0));
            System.out.println("Remote time is " + remoteTime);
            System.out.println("BASE_DIFF is " + BASE_DIFF);
            System.out.println("Time diff == " + (remoteTime - BASE_DIFF));
            Instant time = Instant.ofEpochSecond(remoteTime - BASE_DIFF);
            LocalDateTime d = LocalDateTime.ofInstant(time, ZoneId.systemDefault());
            System.out.println("Time on " + hostName + " is " + d.toString());
            System.out.println("Local date/time = " + LocalDateTime.now());
        } catch (IOException e) {
            System.err.println(e);
        }
    }
}

Object serialization is the ability to convert in-memory objects to an external form that can be sent serially (a byte at a time). To read or write Java objects via serialization, you need only construct an ObjectInputStream or ObjectOutputStream from an InputStream or OutputStream; in this case, the socket’s getInputStream() or getOutputStream().

This program (and its server) provide a service that isn’t a standard part of the TCP/IP stack; it’s a service I made up as a demo. The server for this service is introduced in Recipe 13.3. The client code in Example 12-7 is quite similar to the DaytimeBinary program in the previous recipe, but the server sends us a LocalDateTime object already constructed. Example 12-7 shows the portion of the client code that differs from Example 12-6.

Example 12-7. main/src/main/java/network/DaytimeObject.java
        try (Socket sock = new Socket(hostName, TIME_PORT);) {
            ObjectInputStream is = new ObjectInputStream(new
                BufferedInputStream(sock.getInputStream()));

            // Read and validate the Object
            Object o = is.readObject();
            if (o == null) {
                System.err.println("Read null from server!");
            } else if ((o instanceof LocalDateTime)) {

                // Valid, so cast to LocalDateTime, and print
                LocalDateTime d = (LocalDateTime) o;
                System.out.println("Time on " + hostName + " is " + d);
            } else {
                throw new IllegalArgumentException(
                    String.format("Wanted LocalDateTime, got %s, a %s",
                        o, o.getClass()));
            }

I ask the operating system for the date and time, and then run the program, which prints the date and time on a remote machine:

$ date
Thu Dec 26 09:29:02 EST 2019
C:javasrc
etwork>java network.DaytimeObject aragorn
Time on aragorn is 2019-12-26T09:29:05.227397
C:javasrc
etwork>

Again, the results agree within a few seconds.

12.7 UDP Datagrams

Problem

You need to use a datagram connection (UDP) instead of a stream connection (TCP).

Solution

Use DatagramSocket and DatagramPacket.

Discussion

Datagram network traffic is a kindred spirit to the underlying packet-based Ethernet and IP (Internet protocol) layers. Unlike a stream-based connection such as TCP, datagram transports like UDP transmit each “packet,” or chunk of data, as a single “entity” with no necessary relation to any other.3 A common analogy is that TCP is like talking on the telephone, whereas UDP is like sending postcards or maybe fax messages.

The differences show up most in error handling. Packets can, like postcards, go astray. When was the last time the postman rang your bell to tell you that the post office had lost one of several postcards it was supposed to deliver to you? That’s not going to happen, because the post office doesn’t keep track of postcards. On the other hand, when you’re talking on the phone and there’s a noise burst—like somebody yelling in the room, or even a bad connection—you notice the failure in real time, and you can ask the person at the other end to repeat what they just said.

With a stream-based connection like a TCP socket, the network transport layer handles errors for you: it asks the other end to retransmit. With a datagram transport such as UDP, you have to handle retransmission yourself. It’s kind of like numbering the postcards you send so that you can go back and resend any that don’t arrive—a good excuse to return to your vacation spot, perhaps.

Another difference is that datagram transmission preserves message boundaries. That is, if you write 20 bytes and then write 10 bytes when using TCP, the program reading from the other end will not know if you wrote one chunk of 30 bytes, two chunks of 15, or even 30 individual characters. With a DatagramSocket, you construct a DatagramPacket object for each buffer, and its contents are sent as a single entity over the network; its contents will not be mixed together with the contents of any other buffer. The DatagramPacket object has methods like getLength(), setPort(), and so on.

So why would we even use UDP? UDP has a lot less overhead than TCP, which can be particularly valuable when sending huge amounts of data over a reliable local network or a few hops on the Internet. Over long-haul networks, TCP is probably preferred because TCP handles retransmission of lost packets for you. And obviously, if preserving record boundaries makes your life easier, that may be a reason for considering UDP. UDP is also the way to perform Multicast (broadcast to many receivers simultaneously), though Multicast is out of scope for this discussion.

Example 12-8 is a short program that connects via UDP to the Daytime date and time server used in Recipe 12.5. Because UDP has no real notion of “connection,” the client typically initiates the “conversation,” which sometimes means sending an empty packet; the UDP server uses the address information it gets from that to return its response.

Example 12-8. main/src/main/java/network/DaytimeUDP.java
public class DaytimeUDP {
    /** The UDP port number */
    public final static int DAYTIME_PORT = 13;

    /** A buffer plenty big enough for the date string */
    protected final static int PACKET_SIZE = 100;

    /** The main program that drives this network client.
     * @param argv[0] hostname, running daytime/udp server
     */
    public static void main(String[] argv) throws IOException {
        if (argv.length < 1) {
            System.err.println("usage: java DayTimeUDP host");
            System.exit(1);
        }
        String host = argv[0];
        InetAddress servAddr = InetAddress.getByName(host);
        DatagramSocket sock = new DatagramSocket();
        //sock.connect(servAddr, DAYTIME_PORT);
        byte[] buffer = new byte[PACKET_SIZE];

        // The udp packet we will send and receive
        DatagramPacket packet = new DatagramPacket(
            buffer, PACKET_SIZE, servAddr, DAYTIME_PORT);

        /* Send empty max-length (-1 for null byte) packet to server */
        packet.setLength(PACKET_SIZE-1);
        sock.send(packet);
        System.out.println("Sent request");

        // Receive a packet and print it.
        sock.receive(packet);
        System.out.println("Got packet of size " + packet.getLength());
        System.out.print("Date on " + host + " is " +
            new String(buffer, 0, packet.getLength()));

        sock.close();
    }
}

I’ll run it to my Unix box just to be sure that it works:

$
$ java network.DaytimeUDP aragorn
Sent request
Got packet of size 26
Date on aragorn is Sat Feb  8 20:22:12 2014
$

12.8 URI, URL, or URN?

Problem

Having heard these terms, you want to know the difference between a URI, URL, and URN.

Solution

Read on. Or see the javadoc for java.net.uri.

Discussion

A URL is the traditional name for a network address consisting of a scheme (like “http”) and an address (site name) and resource or pathname. But there are three distinct terms in all:

  • URI (Uniform Resource Identifier)

  • URL (Uniform Resource Locator)

  • URN (Uniform Resource Name)

A discussion near the end of the Java documentation for the new class explains the relationship among URI, URL, and URN. URIs form the set of all identifiers: URLs and URNs are subsets.

URIs are the most general; a URI is parsed for basic syntax without regard to the scheme, if any, that it specifies, and it need not refer to a particular server. A URL includes a hostname, scheme, and other components; the string is parsed according to rules for its scheme. When you construct a URL, an InputStream is created automatically. URNs name resources but do not explain how to locate them; typical examples of URNs that you will have seen include mailto: and news: references.

The main operations provided by the URI class are normalization (removing extraneous path segments including “..”) and relativization (this should be called “making relative,” but somebody wanted a single word to make a method name). A URI object does not have any methods for opening the URI; for that, you would normally use a string representation of the URI to construct a URL object, like so:

URL x = new URL(theURI.toString( ));

The program in Example 12-9 shows examples of normalizating, making relative, and constructing a URL from a URI.

Example 12-9. main/src/main/java/network/URIDemo.java
public class URIDemo {
    public static void main(String[] args)
    throws URISyntaxException, MalformedURLException {

        URI u = new URI("https://darwinsys.com/java/../openbsd/../index.jsp");
        System.out.println("Raw: " + u);
        URI normalized = u.normalize();
        System.out.println("Normalized: " + normalized);
        final URI BASE = new URI("https://darwinsys.com");
        System.out.println("Relativized to " + BASE + ": " + BASE.relativize(u));

        // A URL is a type of URI
        URL url = new URL(normalized.toString());
        System.out.println("URL: " + url);

        // Demo of non-URL but valid URI
        URI uri = new URI("bean:WonderBean");
        System.out.println(uri);
    }
}

12.9 Program: TFTP UDP Client

This program implements the client half of the TFTP application protocol, a once-well-known service that has been used in the Unix world for network booting of workstations since before Windows 3.1, now primarily used for network bootstrapping of computers. I chose this protocol because it’s widely implemented on the server side, so it’s easy to find a test server for it.

The TFTP protocol is a bit odd. The client contacts the server on the well-known UDP port number 69, from a generated port number,4 and the server responds to the client from a generated port number. Further communication is on the two generated port numbers.

Getting into more detail, as shown in Figure 12-1, the client initially sends a read request with the filename and reads the first packet of data. The read request consists of two bytes (a short) with the read request code (short integer with a value of 1, defined as OP_RRQ), two bytes for the sequence number, then the ASCII filename, null terminated, and the mode string, also null terminated. The server reads the read request from the client, verifies that it can open the file and, if so, sends the first data packet (OP_DATA), and then reads again. The client reads from its end and, if the read is OK, turns the packet into an acknowledgement packet, and sends it. This read-acknowledge cycle is repeated until all the data is read. Note that each packet is 516 bytes (512 bytes of data, plus 2 bytes for the packet type and 2 more for the packet number) except the last, which can be any length from 4 (zero bytes of data) to 515 (511 bytes of data). If a network I/O error occurs, the packet is resent. If a given packet goes astray, both client and server are supposed to perform a timeout cycle. This client does not, but the server does. You could add timeouts either using a thread (see Recipe 16.4) or by invoking setSoTimeout() on the socket and, if packets do get lost, catching the SocketTimeoutException, retransmitting the ACK (or RRQ), perhaps up to some max # of attempts. This is left as an exercise for the reader. The current version of the client code is shown in Example 12-10.

jcb4 1201
Figure 12-1. The TFTP protocol packet formats
Example 12-10. main/src/main/java/network/RemCat.java
public class RemCat {
    /** The UDP port number */
    public final static int TFTP_PORT = 69;
    /** The mode we will use - octet for everything. */
    protected final String MODE = "octet";

    /** The offset for the code/response as a byte */
    protected final int OFFSET_REQUEST = 1;
    /** The offset for the packet number as a byte */
    protected final int OFFSET_PACKETNUM = 3;

    /** Debugging flag */
    protected static boolean debug = false;

    /** TFTP op-code for a read request */
    public final int OP_RRQ = 1;
    /** TFTP op-code for a read request */
    public final int OP_WRQ = 2;
    /** TFTP op-code for a read request */
    public final int OP_DATA = 3;
    /** TFTP op-code for a read request */
    public final int OP_ACK    = 4;
    /** TFTP op-code for a read request */
    public final int OP_ERROR = 5;

    protected final static int PACKET_SIZE = 516;    // == 2 + 2 + 512
    protected String host;
    protected InetAddress servAddr;
    protected DatagramSocket sock;
    protected byte buffer[];
    protected DatagramPacket inp, outp;

    /** The main program that drives this network client.
     * @param argv[0] hostname, running TFTP server
     * @param argv[1..n] filename(s), must be at least one
     */
    public static void main(String[] argv) throws IOException {
        if (argv.length < 2) {
            System.err.println("usage: rcat host filename[...]");
            System.exit(1);
        }
        if (debug)
            System.err.println("Java RemCat starting");
        RemCat rc = new RemCat(argv[0]);
        for (int i = 1; i<argv.length; i++) {
            if (debug)
                System.err.println("-- Starting file " +
                    argv[0] + ":" + argv[i] + "---");
            rc.readFile(argv[i]);
        }
    }

    RemCat(String host) throws IOException {
        super();
        this.host = host;
        servAddr = InetAddress.getByName(host);
        sock = new DatagramSocket();
        buffer = new byte[PACKET_SIZE];
        outp = new DatagramPacket(buffer, PACKET_SIZE, servAddr, TFTP_PORT);
        inp = new DatagramPacket(buffer, PACKET_SIZE);
    }

    /* Build a TFTP Read Request packet. This is messy because the
     * fields have variable length. Numbers must be in
     * network order, too; fortunately Java just seems
     * naturally smart enough :-) to use network byte order.
     */
    void readFile(String path) throws IOException {
        buffer[0] = 0;
        buffer[OFFSET_REQUEST] = OP_RRQ;        // read request
        int p = 2;            // number of chars into buffer

        // Convert filename String to bytes in buffer , using "p" as an
        // offset indicator to get all the bits of this request
        // in exactly the right spot.
        byte[] bTemp = path.getBytes();    // i.e., ASCII
        System.arraycopy(bTemp, 0, buffer, p, path.length());
        p += path.length();
        buffer[p++] = 0;        // null byte terminates string

        // Similarly, convert MODE ("stream" or "octet") to bytes in buffer
        bTemp = MODE.getBytes();    // i.e., ASCII
        System.arraycopy(bTemp, 0, buffer, p, MODE.length());
        p += MODE.length();
        buffer[p++] = 0;        // null terminate

        /* Send Read Request to tftp server */
        outp.setLength(p);
        sock.send(outp);

        /* Loop reading data packets from the server until a short
         * packet arrives; this indicates the end of the file.
         */
        do {
            sock.receive(inp);
            if (debug)
                System.err.println(
                    "Packet # " + Byte.toString(buffer[OFFSET_PACKETNUM])+
                    "RESPONSE CODE " + Byte.toString(buffer[OFFSET_REQUEST]));
            if (buffer[OFFSET_REQUEST] == OP_ERROR) {
                System.err.println("rcat ERROR: " +
                    new String(buffer, 4, inp.getLength()-4));
                return;
            }
            if (debug)
                System.err.println("Got packet of size " +
                    inp.getLength());

            /* Print the data from the packet */
            System.out.write(buffer, 4, inp.getLength()-4);

            /* Ack the packet. The block number we
             * want to ack is already in buffer so
             * we just change the opcode. The ACK is
             * sent to the port number which the server
             * just sent the data from, NOT to port
             * TFTP_PORT.
             */
            buffer[OFFSET_REQUEST] = OP_ACK;
            outp.setLength(4);
            outp.setPort(inp.getPort());
            sock.send(outp);
        } while (inp.getLength() == PACKET_SIZE);

        if (debug)
            System.err.println("** ALL DONE** Leaving loop, last size " +
                inp.getLength());
    }
}

To test this client, you need a TFTP server. If you are on a Unix system that you administer, you can enable the TFTP server to test this client just by editing the file /etc/inetd.conf and restarting or reloading the inetd server (Linux uses a different mechanism, which may vary depending on which distribution you are on). inetd is a program that listens for a wide range of connections and starts the servers only when a connection from a client comes along (a kind of lazy evaluation)5. I set up the traditional /tftpboot directory, put this line in my inetd.conf, and reloaded inetd:

tftp dgram udp wait root /usr/libexec/tftpd tftpd -s /tftpboot

Then I put a few test files, one named foo, into the /tftpboot directory. Running:

$ java network.RemCat localhost foo

produced what looked like the file. But just to be safe, I tested the output of RemCat against the original file, using the Unix diff comparison program. No news is good news:

$ java network.RemCat localhost foo | diff - /tftpboot/foo

So far so good. Let’s not slip this program on an unsuspecting network without exercising the error handling at least briefly:

$ java network.RemCat localhost nosuchfile 
remcat ERROR: File not found
$

12.10 Program: Chat Client

This program is a simple chat program. You can’t break in on ICQ or AIM with it, because they each use their own protocol.6 Rather, this program simply writes to and reads from a server, The server for this will be presented in Chapter 13. How does it look when you run it? Figure 12-2 shows me chatting all by myself one day.

jcb4 1202
Figure 12-2. Chat client in action

The code is reasonably self-explanatory. We read from the remote server in a thread to make the input and the output run without blocking each other; this is discussed in Chapter 16. The reading and writing are discussed in this chapter. The program is shown in Example 12-11.

Example 12-11. main/src/main/java/chat/ChatClient.java
public class ChatClient extends JFrame {

    private static final long serialVersionUID = -3686334002367908392L;
    private static final String userName =
        System.getProperty("user.name", "User With No Name");
    /** The state of logged-in-ness */
    protected boolean loggedIn;
    /* The main Frame. */
    protected JFrame cp;
    /** The default port number */
    protected static final int PORTNUM = ChatProtocol.PORTNUM;
    /** The actual port number */
    protected int port;
    /** The network socket */
    protected Socket sock;
    /** PrintWriter for sending lines on socket */
    protected PrintWriter pw;
    /** TextField for input */
    protected JTextField tf;
    /** TextArea to display conversations */
    protected JTextArea ta;
    /** The Login Button */
    protected JButton loginButton;
    /** The LogOUT button */
    protected JButton logoutButton;
    /** The TitleBar title */
    final static String TITLE = "ChatClient: Ian Darwin's Chat Room Client";

    final Executor threadPool = Executors.newSingleThreadExecutor();

    /** set up the GUI */
    public ChatClient() {
        cp = this;
        cp.setTitle(TITLE);
        cp.setLayout(new BorderLayout());
        port = PORTNUM;

        // The GUI
        ta = new JTextArea(14, 80);
        ta.setEditable(false);        // readonly
        ta.setFont(new Font("Monospaced", Font.PLAIN, 11));
        cp.add(BorderLayout.NORTH, ta);

        JPanel p = new JPanel();

        // The login button
        p.add(loginButton = new JButton("Login"));
        loginButton.setEnabled(true);
        loginButton.requestFocus();
        loginButton.addActionListener(e -> {
                login();
                loginButton.setEnabled(false);
                logoutButton.setEnabled(true);
                tf.requestFocus();    // set keyboard focus in right place!
        });

        // The logout button
        p.add(logoutButton = new JButton("Logout"));
        logoutButton.setEnabled(false);
        logoutButton.addActionListener(e -> {
                logout();
                loginButton.setEnabled(true);
                logoutButton.setEnabled(false);
                loginButton.requestFocus();
        });

        p.add(new JLabel("Message here:"));
        tf = new JTextField(40);
        tf.addActionListener(e -> {
                if (loggedIn) {
                    pw.println(ChatProtocol.CMD_BCAST+tf.getText());
                    tf.setText("");
                }
        });
        p.add(tf);

        cp.add(BorderLayout.SOUTH, p);

        cp.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        cp.pack();
    }

    protected String serverHost = "localhost";

    /** LOG ME IN TO THE CHAT */
    public void login() {
        /** BufferedReader for reading from socket */
        BufferedReader is;

        showStatus("In login!");
        if (loggedIn)
            return;
        try {
            sock = new Socket(serverHost, port);
            is = new BufferedReader(new InputStreamReader(sock.getInputStream()));
            pw = new PrintWriter(sock.getOutputStream(), true);
            showStatus("Got socket");

            // FAKE LOGIN FOR NOW - no password needed
            pw.println(ChatProtocol.CMD_LOGIN + userName);

            loggedIn = true;

        } catch(IOException e) {
            warn("Can't get socket to " +
                serverHost + "/" + port + ": " + e);
            cp.add(new JLabel("Can't get socket: " + e));
            return;
        }

        // Construct and start the reader: from server to textarea.
        // Make a Thread to avoid lockups.
        Runnable readerThread = new Runnable() {
            public void run() {
                String line;
                try {
                    while (loggedIn && ((line = is.readLine()) != null))
                        ta.append(line + "
");
                } catch(IOException e) {
                    showStatus("Lost another client!
" + e);
                    return;
                }
            }
        };
        threadPool.execute(readerThread);
    }

    /** Log me out, Scotty, there's no intelligent life here! */
    public void logout() {
        if (!loggedIn)
            return;
        loggedIn = false;
        try {
            if (sock != null)
                sock.close();
        } catch (IOException ign) {
            // so what?
        }
    }

    public void showStatus(String message) {
        System.out.println(message);
    }

    private void warn(String message) {
        JOptionPane.showMessageDialog(this, message);
    }

    /** A main method to allow the client to be run as an Application */
    public static void main(String[] args) {
        ChatClient room101 = new ChatClient();
        room101.pack();
        room101.setVisible(true);
    }
}

See Also

There are many better structured ways to write a chat client, including WebSockets, RMI, and JMS. RMI is Java’s RPC interface, and is included both in Java SE and in Java EE; it is not described in this edition of this book, but you can find the RMI chapter from previous editions on the author’s website. The other technologies are part of the Java Enterprise so, again, we refer you to Arun Gupta’s Java EE 7 Essentials (O’Reilly).

If your communication goes over the public internet, you do need to encrypt your socket connection, so check out Sun’s JSSE (Java Secure Socket Extension). If you took my earlier advice and used the standard HTTP protocol, you can encrypt the conversation just by changing the URL to https.

For a good overview of network programming from the C programmer’s point of view, see the late W. Richard Stevens’ Unix Network Programming. Despite the book’s name, it’s really about socket and TCP/IP/UDP programming and covers all parts of the (Unix) networking API and protocols such as TFTP in amazing detail.

12.11 Program: Simple HTTP Link Checker

Checking links is an ongoing problem for website owners as well as those who write technical documentation that links to external sources (e.g., people like the author of the book you are now reading). Link Checkers are the tool they inevitably use to validate the links in their pages, be they web pages or book pages. Implementing a link checker is basically a matter of (a) extracting links and (b) opening them. Thus, this program. I call it KwikLinkChecker as it is a bit on the “quick and dirty” side—it doesn’t validate the content of the link to be sure it still contains what it once did, so if, say, an open source project forgets to renew its domain registration, and it gets taken over by a porn site, well, KwikLinkChecker will never know. But that said, it does its job reasonably well, and reasonably quickly:

Example 12-12. darwinsys-api/src/main/java/com/darwinsys/tools/KwikLinkChecker.java
    /**
     * Check one HTTP link; not recursive. Returns a LinkStatus with
     * boolean success, and the filename or an error message in the
     * message part of the LinkStatus.  The end of this method is one of
     * the few places where a whole raft of different "catch" clauses is
     * actually needed for the intent of the program.
     * @param urlString the link to check
     * @return the link's status
     */
    @SuppressWarnings("exports")
    public LinkStatus check(String urlString) {
        try {
            HttpResponse<String> resp = client.send(
                HttpRequest.newBuilder(URI.create(urlString))
                .header("User-Agent", getClass().getName())
                .GET()
                .build(),
                BodyHandlers.ofString());

            // Collect the results
            if (resp.statusCode() == 200) {
                System.out.println(resp.body());
            } else {
                System.out.printf("ERROR: Status %d on request %s
",
                    resp.statusCode(), urlString);
            }

            switch (resp.statusCode()) {
            case 200:
                return new LinkStatus(true, urlString);
            case 403:
                return new LinkStatus(false,"403: " + urlString );
            case 404:
                return new LinkStatus(false,"404: " + urlString );
            }
            return new LinkStatus(true, urlString);
        } catch (IllegalArgumentException | MalformedURLException e) {
            // JDK throws IAE if host can't be determined from URL string
            return new LinkStatus(false, "Malformed URL: " + urlString);
        } catch (UnknownHostException e) {
            return new LinkStatus(false, "Host invalid/dead: " + urlString);
        } catch (FileNotFoundException e) {
            return new LinkStatus(false,"NOT FOUND (404) " + urlString);
        } catch (ConnectException e) {
            return new LinkStatus(false, "Server not listening: " + urlString);
        } catch (SocketException e) {
            return new LinkStatus(false, e + ": " + urlString);
        } catch (IOException e) {
            return new LinkStatus(false, e.toString()); // includes failing URL
        } catch (Exception e) {
            return new LinkStatus(false, urlString + ": " + e);
        }
    }

Fancier link checkers are surely available, but this one works for me.

1 The location where it is looked up varies. It might be in a file named /etc/services on Unix; in the services file in a subdirectory of windows or winnt in Windows; in a centralized registry such as Sun’s Network Information Services (NIS, formerly YP); or in some other platform- or network-dependent location.

2 It used to be universal, when most networked systems were administered by full-time systems people who had been trained or served an apprenticeship. Today many machines on the Internet don’t have localhost configured properly.

3 The UDP packet may need to be fragmented by some networks, but this is not germane to us at the UDP level, because it will re-assemble the network packets into our “single entity” UDP packet at the other end.

4 When the application doesn’t care, these port numbers are usually made up by the operating system. For example, when you call a company from a pay phone or cell phone, the company doesn’t usually care what number you are calling from, and if it does, there are ways to find out. Generated port numbers generally range from 1024 (the first nonprivileged port; see Chapter 13) to 65535 (the largest value that can be held in a 16-bit port number).

5 Beware of security holes; don’t turn a TFTP server loose on the Internet without first reading a good security book, such as Building Internet Firewalls, (O’Reilly)

6 For an open source program that “AIMs” to let you talk to both from the same program, check out Jabber at http://www.jabber.org.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.254.90