Network programming in a nutshell
Network programming is about writing computer programs that talk to eachother over a computer network. The world is full of such type of programs: for example, the web browser you are using to read this website is a piece of software that connects to a remote computer where the data is stored and grabs the text content to display on your screen.
The browser and the web server can do their networking job thanks to the operating systems they run on, where all the necessary network protocols have been implemented.
Networking Programming features of Java
John P. Slone
With this application-oriented background on networking concepts, one can now discuss Java networking features within the context of these concepts. In this section, Java networking primitives will be discussed that enable the programmer to develop applications that interact directly with the layer 4. These primitives are found within the java.net package.
Connection Oriented Networking
Connection-oriented networking employs a paradigm analogous to the telephone network. First, a connection is established between two parties. Once established, communications take place by having each party
send and listen as needed. Once finished, the connection is broken. As discussed above, rather than using a telephone, each party “speaks” into and “listens” from a socket.
As with practically everything else in Java, the concept of a socket is provided as a class; thus sockets are treated just like any other object in the language. For connection-oriented networking, there are actually two
classes of socket objects in Java: “Socket,” which implements client sockets, and “ServerSocket,” which implements server sockets. The distinction is needed to provide server applications with the processing capability of listening for and accepting connection requests from clients. Both are subclasses of SocketImpl, which is where most of the low-level methods and variables are defined. The two subclasses hide many of these details from the programmer. For example, among the constructors for the client-side socket are the following:
- Socket (InetAddress, int) — creates a socket and connects it to the specified port on the host at the specified IP address. With this one, simple constructor, the program will actually create a socket, bind it to a local port, issue a connection request to the remote IP address/port combination, and wait for an acknowledgment.
- Socket (String, int) — creates a socket and connects it to the specified port on the host named in String. In addition to everything in the previous constructor, this one also resolves the name of the remote host specified in String to find its IP address prior to issuing the connection request.
Constructors on the server side include the following:
- ServerSocket (int) — creates a server socket and binds it to the specified port on the local host.
- ServerSocket (int, int) — same as above, but allows the programmer to specify the maximum allowable backlog of pending requests.
- ServerSocket (int, int, InetAddress) — same as the previous constructor, but allows programmer to specify which network interface on multi-homed machines (e.g., machines sitting on firewalls).
Similarly powerful methods are available for these classes, including fairly high-level methods such as getInputStream and getOutputStream, as well as lower-level methods such as getInetAddress, getPort, and setTcpNoDelay. Thus, between the constructors and methods, the Socket and ServerSocket classes provide programmers with a fairly powerful assortment of tools for developing networked applications.
In contrast to connection-oriented networking, connectionless networking is accomplished by sending independent packets between the parties involved. Rather than having the notion of a connection, which is established at the beginning of the exchange and torn down following the exchange, each individual packet, called a datagram, must be provided with the necessary addressing information. This paradigm is closer to that of the postal system, in which a letter, complete with destination and return addresses, is dropped in the nearest mailbox and routed individually to its destination.
Java provides a separate type of socket class for this purpose: the DatagramSocket. In this case, there is no distinction between a server socket and a client socket, since datagrams are sent and received outside the context of a connection. Fundamentally a simpler concept, the DatagramSocket has only three constructors:
- DatagramSocket ( ) — This constructor creates a socket and binds it to any available port on the local machine.
- DatagramSocket (int) — This constructor creates a socket and binds it to the specified port on the local machine.
- DatagramSocket (int, InetAddress) — This constructor creates a socket and binds it to the specified port/interface combination on the local machine.
Note that this class of socket is only associated with the local machine. Because there is no notion of a connection, there is no association with a remote machine. Once a datagram socket object has been created, the programmer may invoke one of several methods. Of most importance are “send” and “receive,” used to send or receive datagrams, respectively. Datagrams are implemented as a separate class, known as a DatagramPacket. This class has two constructors:
- DatagramPacket (byte[ ], int) — used to create a datagram packet for receiving datagrams of length int
- DatagramPacket (byte[ ], int, InetAddress, int) — used to create a datagram packet for sending packets of length int to the address and port specified
DatagramPacket methods are provided to get or set the datagram’s address, port, data, or length as needed.
Multicast Connectionless Networking
Connectionless networking can also be accomplished between multiple parties, as opposed to the limited notion of point-to-point networking. Java provides a MulticastSocket class for this purpose as a subclass of DatagramSocket. Of particular interest are the methods joinGroup and leaveGroup, which allow the system to join and leave a particular multicast group, and the methods getTTL and setTTL, which handle the datagram’s time-to-live attribute.
High-Level Java Networking Abstractions
Up to this point, the discussion has been limited to fairly low-level networking concepts, focused at the transport layer service interface, the socket. Although extremely powerful when compared with the networking features of most other languages, these features are little more than primitives within the context of Java networking features. The remainder of this article focuses on several of the higher-level networking concepts
provided in Java.
The first such concept discussed is that of URL-based programming, a concept also supported within the java.net package. In general terms, URLbased programming allows the programmer to focus on the concepts associated with actually handling a remote object, rather than on all the lower-level mechanisms involved in creating a socket and binding it to a port, establishing a connection to the object’s machine, locating the object, and retrieving information from or sending information to the object. How this is accomplished should become clearer in the following paragraphs.
The fundamental class provided for this purpose is the URL. This class, whose name stands for Uniform Resource Locator, uses a standard notation for representing a resource on the network. Popularized by the World Wide Web, the basic URL provides four primary components: a protocol identifier (such as http, ftp, or gopher), a machine name (such as www.yahoo.com), a port number (if not specified, a default port is assumed), and a file name (including the path to the file). The file name component can also optionally contain an internal reference to a specific named label within the file. The Java URL class provides four constructors to allow flexibility in the way a URL object is created:
- URL (String) — allows the creation of a URL object by specifying a complete, familiar URL specification such as http://www.yahoo.com/
- URL (String, String, int, String) — allows the creation of a URL object by separately specifying the protocol, host name, port, and file name
- URL (String, String, String) — same as the previous constructor, except that the default port is assumed
- URL (URL, String) — allows the creation of a URL by specifying its path relative to an existing URL object
Once the URL object has been created, Java provides a number of low-level methods, such as those that parse the URL and return specific elements, as well as several high-level methods, providing powerful capabilities to the programmer. Examples of high-level methods include getContent, which returns the entire content of the specified URL with a single line of code. Other high-level methods include openStream, which creates a connection to the URL and opens an input stream for subsequent reading of the contents, and openConnection, which creates a connection to the URL and opens a bidirectional stream for subsequent
writing to or reading from the URL. This latter method is especially important for interacting with Web-enabled applications, such as those implementing the Common Gateway Interface (CGI).
As implied by the previous paragraph, there is a concept of a URL connection, a concept that is also provided as a class known as a URLConnection. Once a URLConnection object has been created, the programmer has nearly 40 methods at his disposal for handling the connection. Among the capabilities provided by these methods are those of reading selected header fields, testing to see whether the URL accepts input, whether the URL is cached, when it was last modified, or when it expires. These methods are in addition to the methods for obvious concepts such as reading and writing.
Remote Method Invocation (RMI)
As discussed above, URL-based programming allows the Java programmer to interact at a high level with primarily non-Java resources on the network. RMI provides the Java programmer with the capability of developing truly distributed, yet fully cooperative Java-only applications. These cooperating Java components can be peer applications, client and server applications, or client applets interacting with server applications. Compared with URL-based programming, RMI allows an order of magnitude increase in the degrees of complexity and sophistication of the resulting networked applications.
To achieve this level of sophistication, it is necessary to grasp concepts that go beyond the simple definition of classes and methods, although numerous classes are defined in the java.rmi family of packages. Instead, the exploitation of RMI requires a paradigm shift in the way one thinks about network-based programming. At a very high level, RMI requires the development of two components: a Java object that implements a method through a remote interface, and a Java object that remotely invokes that method. These two objects may be on the same machine or on different machines. Conceptually, all that is necessary to make this happen is for the calling object to obtain a valid reference to the called method in the form of a specially constructed URL. In most cases, the object reference is obtained either as a parameter or as the value returned by a method. The first such reference is typically obtained from an RMI remote object registry. For clarity of discussion, the remainder of this section will assume that the object that implements the remote method is a server application, and that the object requesting the remote invocation is an applet.
Looking first at the server, the necessary ingredients are the definition of a remote interface, the definition of constructors for the remote object, and the definition of methods that can be invoked remotely. In addition, there are security requirements, but treatment of security is beyond the scope of this article. Finally, at least one of the remote objects must be registered in the RMI remote object registry on the server machine. At execution time, the constructor for the remote object creates an instance of the object and exports the object by having it listen to a socket for incoming calls. Turning attention to the applet side, it can be seen that the calling object calls the remote method very much as it would any other method. However, instead of containing a reference to the actual remote object, it contains a reference to a locally implemented stub representing the remote object interface. The stubs are generated through the use of a special compiler tool called “rmic.” Thus, the actual remote method invocation is abstracted in a way that isolates the programmer from the details.
The invocation and all the necessary semantics are actually handled by the remote reference layer, and take place as follows. First, the applet calls a method making reference to a locally held stub. The stub then places the appropriate remote call, across the network if necessary, to its counterpart remote interface on the server side. That interface, in turn, invokes a method on the server side and passes the resulting return value back through the interface, across the network to the applet stub code, and on to the calling object. This interaction is depicted in image below. Thus it is shown that RMI provides the Java programmer with an extremely powerful set of networking capabilities. As with other high-level networking features of the Java language, RMI allows the programmer to focus on the essentials of writing and invoking methods that accomplish a certain task, while ignoring the details of the underlying network.
Java Naming and Directory Interface (JNDI)
The two high-level networking features of the Java language discussed thus far, URL-based programming and RMI, have one thing in common other than the simple fact that they facilitate the development of networked applications. Specifically, both involve the notion of binding a name to a network-based resource. With URL-based programming, this resource is either a file- or a stream-based interface to an application. With RMI, the resource is a method in some remote object. Broadening the perspective to yet another dimension, it is found that network programming for any purpose will inevitably involve the binding of names to resources.
At the broadest possible level, network-accessible resources can be any type of object. For example, in addition to files, stream interfaces, and methods, other valid resources could include printers, calendars, electronic mailboxes, telephones, pagers, humans, conference rooms, control valves, remote sensors, or practically anything else, limited only by the imagination. To expand programming to this broad horizon, the concept of a generalized directory is needed. In the broadest sense, a directory can be thought of as a system that provides a mapping between the name of an object and one or more attributes that describe the object.
In practice, there are multiple directories in existence in today’s networking environment. For example, the Internet Domain Name Service (DNS) is the directory that maps machine names (such as www.yahoo.com) to IP addresses (such as 18.104.22.168). Other directories, such as X.500, LDAP, NIS, and NDS, provide mapping between objects named in other name spaces and their attributes. Further complicating the situation, certain objects are actually identified by compound names — those names that exist in multiple, disjointed name spaces. URLs are a prime example, since part of the URL names the machine (named in the DNS name space), and part of it names the file (named within the name space managed by the particular machine).
To provide the simplest abstraction for programmers, what is needed is a mechanism that allows objects to be identified to programs by their compound names (such as a URL), while hiding the complexities of the underlying directory structures. This is precisely the objective of the Java Naming and Directory Interface (JNDI).
JNDI is implemented in three standard Java extension packages: javax.naming, javax.naming.directory, and javax.naming.spi. The first two packages comprise the JNDI Application Programming Interface (API), giving application programmers a suite of powerful classes and methods for handling object names and for interacting with the directory services. The third package makes up what is referred to as the JNDI Service Provider Interface (SPI). Conceptually, a Java application accesses the JNDI Implementation Manager through the JNDI API, while a variety of naming and directory services sit transparently behind the JNDI Implementation Manager, plugged in through the JNDI SPI. This concept is depicted in image above.
Of particular interest for this article is the naming and directory service shown in the lower left portion of Exhibit 4, RMI. As shown, the RMI object registry becomes a part of a substantially larger naming and directory
service, and is implemented through the same classes and methods used for other network-based resources. In this manner, the entire world of network-accessible resources is placed literally at the fingertips of the Java programmer