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).
#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.
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.
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.
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.
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).
You need to contact a server using TCP/IP.
Just create a java.net.Socket
, passing the hostname and port number into the constructor.
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.
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.
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.
You want to look up a host’s address name or number or get the address at the other end of a network connection.
Get an InetAddress
object.
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 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.
You want more detailed reporting than just IOException
if something goes wrong.
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.
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
;
}
}
}
Having connected, you wish to transfer textual data.
Construct a BufferedReader
or PrintWriter
from the socket’s getInputStream()
or getOutputStream()
.
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.
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.”
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
.
(
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.
Having connected, you wish to transfer binary data, either raw binary data or serialized Java objects.
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
.
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
.
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.
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.
You need to use a datagram connection (UDP) instead of a stream connection (TCP).
Use DatagramSocket
and DatagramPacket
.
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.
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
.
(
"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 $
Having heard these terms, you want to know the difference between a URI, URL, and URN.
Read on. Or see the javadoc for java.net.uri.
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.
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
);
}
}
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.
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 $
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.
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.
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
);
}
}
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.
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:
/**
* 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.
3.143.254.90