UNPKG

epubjs

Version:

Render ePub documents in the browser, across many devices

196 lines (194 loc) 25.8 kB
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"><head><title>Simple Serialized Object Protocols</title><link rel="stylesheet" href="core.css" type="text/css"/><meta name="generator" content="DocBook XSL Stylesheets V1.74.0"/></head><body><div class="sect1" title="Simple Serialized Object Protocols"><div class="titlepage"><div><div><h1 class="title"><a id="learnjava3-CHP-13-SECT-3"/>Simple Serialized Object Protocols</h1></div></div></div><p><a id="idx10779" class="indexterm"/> <a id="idx10804" class="indexterm"/>Earlier in this chapter, we showed a hypothetical conversation in which a client and server exchanged some primitive data and a serialized Java object. Passing an object between two programs may not have seemed like a big deal at the time, but, in the context of Java as a portable bytecode language, it has big implications. In this section, we show how a protocol can be built using serialized Java objects.</p><p>Before we move on, it’s worth considering network protocols. Most programmers would consider working with sockets to be tedious and complex. Even though Java makes sockets much easier to use than many other languages, sockets still provide only an unstructured flow of bytes between their endpoints. If you want to do serious communications using sockets, the first thing you have to do is come up with a protocol that defines the data you are sending and receiving. The most complex part of that protocol usually involves how to marshal (package) your data for transfer over the Net and unpack it on the other side.</p><p>As we’ve seen, Java’s <code class="literal">DataInputStream</code> and <code class="literal">DataOuputStream</code> classes solve this problem for simple data types. We can read and write numbers, <code class="literal">String</code>s, and Java primitives in a standard format that can be understood on any other Java platform. To do real work, however, we need to be able to put simple types together into larger structures. Java object serialization solves this problem elegantly by allowing us to send our data in the state in which we will use it—as Java objects. Serialization can even pack up entire graphs of interconnected objects and put them back together at a later time in another Java VM.</p><div class="sect2" title="A Simple Object-Based Server"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-13-SECT-3.1"/>A Simple Object-Based Server</h2></div></div></div><p>In the following example, a client sends a serialized object to the server, and the server responds in kind. The object sent by the client represents a request and the object returned by the server represents the response. The conversation ends when the client closes the connection. It’s hard to imagine a simpler protocol. All the hairy details are taken care of by object serialization, which allows us to work with standard Java objects as we are used to doing.</p><p>To start, we define a class—<code class="literal">Request</code>—to serve as a base class for the various kinds of requests we make to the server. Using a common base class is a convenient way to identify the object as a type of request. In a real application, we might also use it to hold basic information, such as client names and passwords, timestamps, serial numbers, and so on. In our example, <code class="literal">Request</code> can be an empty class that exists so that others can extend it:</p><a id="I_13_tt861"/><pre class="programlisting"> <code class="c1">//file: Request.java</code> <code class="kd">public</code> <code class="kd">class</code> <code class="nc">Request</code> <code class="kd">implements</code> <code class="n">java</code><code class="o">.</code><code class="na">io</code><code class="o">.</code><code class="na">Serializable</code> <code class="o">{}</code></pre><p><code class="literal">Request</code> implements <code class="literal">Serializable</code>, so all its subclasses are serializable by default. Next, we create some specific kinds of <code class="literal">Request</code>s. The first, <code class="literal">DateRequest</code>, is also a trivial class. We use it to ask the server to send us a <code class="literal">java.util.Date</code> object as a response:</p><a id="I_13_tt862"/><pre class="programlisting"> <code class="c1">//file: DateRequest.java</code> <code class="kd">public</code> <code class="kd">class</code> <code class="nc">DateRequest</code> <code class="kd">extends</code> <code class="n">Request</code> <code class="o">{}</code></pre><p>Next, we create a generic <code class="literal">WorkRequest</code> object. The client sends a <code class="literal">WorkRequest</code> to get the server to perform some computation. The server calls the <code class="literal">WorkRequest</code> object’s <code class="literal">execute()</code> method to do the work on the server side and then returns the resulting object as a response:</p><a id="I_13_tt863"/><pre class="programlisting"> <code class="c1">//file: WorkRequest.java</code> <code class="kd">public</code> <code class="kd">abstract</code> <code class="kd">class</code> <code class="nc">WorkRequest</code> <code class="kd">extends</code> <code class="n">Request</code> <code class="o">{</code> <code class="kd">public</code> <code class="kd">abstract</code> <code class="n">Object</code> <code class="nf">execute</code><code class="o">();</code> <code class="o">}</code></pre><p>For our application, we subclass <code class="literal">WorkRequest</code> to create <code class="literal">MyCalculation</code>, which adds code that performs a specific calculation; in this case, we just square a number:</p><a id="I_13_tt864"/><pre class="programlisting"> <code class="c1">//file: MyCalculation.java</code> <code class="kd">public</code> <code class="kd">class</code> <code class="nc">MyCalculation</code> <code class="kd">extends</code> <code class="n">WorkRequest</code> <code class="o">{</code> <code class="kt">int</code> <code class="n">n</code><code class="o">;</code> <code class="err"> </code> <code class="kd">public</code> <code class="nf">MyCalculation</code><code class="o">(</code> <code class="kt">int</code> <code class="n">n</code> <code class="o">)</code> <code class="o">{</code> <code class="k">this</code><code class="o">.</code><code class="na">n</code> <code class="o">=</code> <code class="n">n</code><code class="o">;</code> <code class="o">}</code> <code class="kd">public</code> <code class="n">Object</code> <code class="nf">execute</code><code class="o">()</code> <code class="o">{</code> <code class="k">return</code> <code class="k">new</code> <code class="nf">Integer</code><code class="o">(</code> <code class="n">n</code> <code class="o">*</code> <code class="n">n</code> <code class="o">);</code> <code class="o">}</code> <code class="o">}</code></pre><p>As far as data content is concerned, <code class="literal">MyCalculation</code> really doesn’t do much; it only really transports an integer value for us. But keep in mind that a request object could hold lots of data, including references to many other objects in complex structures, such as arrays or linked lists. The only requirement is that all the objects to be sent must be serializable or must be able to be discarded by marking them as transient (see <a class="xref" href="ch12.html" title="Chapter 12. Input/Output Facilities">Chapter 12</a>). Note that <code class="literal">MyCalculation</code> also contains behavior—the <code class="literal">execute()</code> operation. While Java object serialization sends only the data content of a class, in our discussion of RMI later in this chapter, we’ll see how Java’s ability to dynamically download bytecode for classes can make both the data content and behavior portable over the network.</p><p>It’s also important to note that even without dynamically loading classes over the network (which is uncommon in practice), this design pattern, sometimes called the <span class="emphasis"><em>command pattern</em></span>, is an important one. Using polymorphism to hide behavior details of tasks from the server allows the application to be easily extended. Polymorphism and Java object serialization are a powerful combination.</p><p>Now that we have our protocol, we need the server. The following <code class="literal">Server</code> class looks a lot like the <code class="literal">TinyHttpd</code> server we developed earlier in this chapter:</p><a id="I_13_tt865"/><pre class="programlisting"> <code class="c1">//file: Server.java</code> <code class="kn">import</code> <code class="nn">java.net.*</code><code class="o">;</code> <code class="kn">import</code> <code class="nn">java.io.*</code><code class="o">;</code> <code class="kd">public</code> <code class="kd">class</code> <code class="nc">Server</code> <code class="o">{</code> <code class="kd">public</code> <code class="kd">static</code> <code class="kt">void</code> <code class="nf">main</code><code class="o">(</code> <code class="n">String</code> <code class="n">argv</code><code class="o">[]</code> <code class="o">)</code> <code class="kd">throws</code> <code class="n">IOException</code> <code class="o">{</code> <code class="n">ServerSocket</code> <code class="n">ss</code> <code class="o">=</code> <code class="k">new</code> <code class="n">ServerSocket</code><code class="o">(</code> <code class="n">Integer</code><code class="o">.</code><code class="na">parseInt</code><code class="o">(</code><code class="n">argv</code><code class="o">[</code><code class="mi">0</code><code class="o">])</code> <code class="o">);</code> <code class="k">while</code> <code class="o">(</code> <code class="kc">true</code> <code class="o">)</code> <code class="k">new</code> <code class="nf">ServerConnection</code><code class="o">(</code> <code class="n">ss</code><code class="o">.</code><code class="na">accept</code><code class="o">()</code> <code class="o">).</code><code class="na">start</code><code class="o">();</code> <code class="o">}</code> <code class="o">}</code> <code class="c1">// end of class Server</code> <code class="err"> </code> <code class="kd">class</code> <code class="nc">ServerConnection</code> <code class="kd">extends</code> <code class="n">Thread</code> <code class="o">{</code> <code class="n">Socket</code> <code class="n">client</code><code class="o">;</code> <code class="n">ServerConnection</code> <code class="o">(</code> <code class="n">Socket</code> <code class="n">client</code> <code class="o">)</code> <code class="kd">throws</code> <code class="n">SocketException</code> <code class="o">{</code> <code class="k">this</code><code class="o">.</code><code class="na">client</code> <code class="o">=</code> <code class="n">client</code><code class="o">;</code> <code class="o">}</code> <code class="err"> </code> <code class="kd">public</code> <code class="kt">void</code> <code class="nf">run</code><code class="o">()</code> <code class="o">{</code> <code class="k">try</code> <code class="o">{</code> <code class="n">ObjectInputStream</code> <code class="n">in</code> <code class="o">=</code> <code class="k">new</code> <code class="nf">ObjectInputStream</code><code class="o">(</code> <code class="n">client</code><code class="o">.</code><code class="na">getInputStream</code><code class="o">()</code> <code class="o">);</code> <code class="n">ObjectOutputStream</code> <code class="n">out</code> <code class="o">=</code> <code class="k">new</code> <code class="nf">ObjectOutputStream</code><code class="o">(</code> <code class="n">client</code><code class="o">.</code><code class="na">getOutputStream</code><code class="o">()</code> <code class="o">);</code> <code class="k">while</code> <code class="o">(</code> <code class="kc">true</code> <code class="o">)</code> <code class="o">{</code> <code class="n">out</code><code class="o">.</code><code class="na">writeObject</code><code class="o">(</code> <code class="n">processRequest</code><code class="o">(</code> <code class="n">in</code><code class="o">.</code><code class="na">readObject</code><code class="o">()</code> <code class="o">)</code> <code class="o">);</code> <code class="n">out</code><code class="o">.</code><code class="na">flush</code><code class="o">();</code> <code class="o">}</code> <code class="o">}</code> <code class="k">catch</code> <code class="o">(</code> <code class="n">EOFException</code> <code class="n">e3</code> <code class="o">)</code> <code class="o">{</code> <code class="c1">// Normal EOF</code> <code class="k">try</code> <code class="o">{</code> <code class="n">client</code><code class="o">.</code><code class="na">close</code><code class="o">();</code> <code class="o">}</code> <code class="k">catch</code> <code class="o">(</code> <code class="n">IOException</code> <code class="n">e</code> <code class="o">)</code> <code class="o">{</code> <code class="o">}</code> <code class="o">}</code> <code class="k">catch</code> <code class="o">(</code> <code class="n">IOException</code> <code class="n">e</code> <code class="o">)</code> <code class="o">{</code> <code class="n">System</code><code class="o">.</code><code class="na">out</code><code class="o">.</code><code class="na">println</code><code class="o">(</code> <code class="s">"I/O error "</code> <code class="o">+</code> <code class="n">e</code> <code class="o">);</code> <code class="c1">// I/O error</code> <code class="o">}</code> <code class="k">catch</code> <code class="o">(</code> <code class="n">ClassNotFoundException</code> <code class="n">e2</code> <code class="o">)</code> <code class="o">{</code> <code class="n">System</code><code class="o">.</code><code class="na">out</code><code class="o">.</code><code class="na">println</code><code class="o">(</code> <code class="n">e2</code> <code class="o">);</code> <code class="c1">// unknown type of request object</code> <code class="o">}</code> <code class="o">}</code> <code class="err"> </code> <code class="kd">private</code> <code class="n">Object</code> <code class="nf">processRequest</code><code class="o">(</code> <code class="n">Object</code> <code class="n">request</code> <code class="o">)</code> <code class="o">{</code> <code class="k">if</code> <code class="o">(</code> <code class="n">request</code> <code class="k">instanceof</code> <code class="n">DateRequest</code> <code class="o">)</code> <code class="k">return</code> <code class="k">new</code> <code class="n">java</code><code class="o">.</code><code class="na">util</code><code class="o">.</code><code class="na">Date</code><code class="o">();</code> <code class="k">else</code> <code class="nf">if</code> <code class="o">(</code> <code class="n">request</code> <code class="k">instanceof</code> <code class="n">WorkRequest</code> <code class="o">)</code> <code class="k">return</code> <code class="o">((</code><code class="n">WorkRequest</code><code class="o">)</code><code class="n">request</code><code class="o">).</code><code class="na">execute</code><code class="o">();</code> <code class="k">else</code> <code class="k">return</code> <code class="kc">null</code><code class="o">;</code> <code class="o">}</code> <code class="o">}</code></pre><p>The <code class="literal">Server</code> handles each request in a separate thread. For each connection, the <code class="literal">run()</code> method creates an <code class="literal">ObjectInputStream</code> and an <code class="literal">ObjectOutputStream</code>, which the server uses to receive the request and send the response. The <code class="literal">processRequest()</code> method decides what the request means and comes up with the response. To figure out what kind of request we have, we use the <code class="literal">instanceof</code> operator to look at the object’s type.</p><p>Finally, we get to our <code class="literal">Client</code>, which is even simpler:</p><a id="I_13_tt866"/><pre class="programlisting"> <code class="c1">//file: Client.java</code> <code class="kn">import</code> <code class="nn">java.net.*</code><code class="o">;</code> <code class="kn">import</code> <code class="nn">java.io.*</code><code class="o">;</code> <code class="err"> </code> <code class="kd">public</code> <code class="kd">class</code> <code class="nc">Client</code> <code class="o">{</code> <code class="kd">public</code> <code class="kd">static</code> <code class="kt">void</code> <code class="nf">main</code><code class="o">(</code> <code class="n">String</code> <code class="n">argv</code><code class="o">[]</code> <code class="o">)</code> <code class="o">{</code> <code class="k">try</code> <code class="o">{</code> <code class="n">Socket</code> <code class="n">server</code> <code class="o">=</code> <code class="k">new</code> <code class="nf">Socket</code><code class="o">(</code> <code class="n">argv</code><code class="o">[</code><code class="mi">0</code><code class="o">],</code> <code class="n">Integer</code><code class="o">.</code><code class="na">parseInt</code><code class="o">(</code><code class="n">argv</code><code class="o">[</code><code class="mi">1</code><code class="o">])</code> <code class="o">);</code> <code class="n">ObjectOutputStream</code> <code class="n">out</code> <code class="o">=</code> <code class="k">new</code> <code class="nf">ObjectOutputStream</code><code class="o">(</code> <code class="n">server</code><code class="o">.</code><code class="na">getOutputStream</code><code class="o">()</code> <code class="o">);</code> <code class="n">ObjectInputStream</code> <code class="n">in</code> <code class="o">=</code> <code class="k">new</code> <code class="nf">ObjectInputStream</code><code class="o">(</code> <code class="n">server</code><code class="o">.</code><code class="na">getInputStream</code><code class="o">()</code> <code class="o">);</code> <code class="err"> </code> <code class="n">out</code><code class="o">.</code><code class="na">writeObject</code><code class="o">(</code> <code class="k">new</code> <code class="n">DateRequest</code><code class="o">()</code> <code class="o">);</code> <code class="n">out</code><code class="o">.</code><code class="na">flush</code><code class="o">();</code> <code class="n">System</code><code class="o">.</code><code class="na">out</code><code class="o">.</code><code class="na">println</code><code class="o">(</code> <code class="n">in</code><code class="o">.</code><code class="na">readObject</code><code class="o">()</code> <code class="o">);</code> <code class="err"> </code> <code class="n">out</code><code class="o">.</code><code class="na">writeObject</code><code class="o">(</code> <code class="k">new</code> <code class="n">MyCalculation</code><code class="o">(</code> <code class="mi">2</code> <code class="o">)</code> <code class="o">);</code> <code class="n">out</code><code class="o">.</code><code class="na">flush</code><code class="o">();</code> <code class="n">System</code><code class="o">.</code><code class="na">out</code><code class="o">.</code><code class="na">println</code><code class="o">(</code> <code class="n">in</code><code class="o">.</code><code class="na">readObject</code><code class="o">()</code> <code class="o">);</code> <code class="err"> </code> <code class="n">server</code><code class="o">.</code><code class="na">close</code><code class="o">();</code> <code class="o">}</code> <code class="k">catch</code> <code class="o">(</code> <code class="n">IOException</code> <code class="n">e</code> <code class="o">)</code> <code class="o">{</code> <code class="n">System</code><code class="o">.</code><code class="na">out</code><code class="o">.</code><code class="na">println</code><code class="o">(</code> <code class="s">"I/O error "</code> <code class="o">+</code> <code class="n">e</code> <code class="o">);</code> <code class="c1">// I/O error</code> <code class="o">}</code> <code class="k">catch</code> <code class="o">(</code> <code class="n">ClassNotFoundException</code> <code class="n">e2</code> <code class="o">)</code> <code class="o">{</code> <code class="n">System</code><code class="o">.</code><code class="na">out</code><code class="o">.</code><code class="na">println</code><code class="o">(</code> <code class="n">e2</code> <code class="o">);</code> <code class="c1">// unknown type of response object</code> <code class="o">}</code> <code class="o">}</code> <code class="o">}</code></pre><p>Just like the server, <code class="literal">Client</code> creates the pair of object streams. It sends a <code class="literal">DateRequest</code> and prints the response; it then sends a <code class="literal">MyCalculation</code> object and prints the response. Finally, it closes the connection. On both the client and the server, we call the <code class="literal">flush()</code> method after each call to <code class="literal">writeObject()</code>. This method forces the system to send any buffered data, ensuring that the other side sees the entire request before we wait for a response. When the client closes the connection, our server catches the <code class="literal">EOFException</code> that is thrown and ends the session. Alternatively, our client could write a special object, perhaps <code class="literal">null</code>, to end the session; the server could watch for this item in its main loop.</p><p>The order in which we construct the object streams is important. We create the output streams first because the constructor of an <code class="literal">ObjectInputStream</code> tries to read a header from the stream to make sure that the <code class="literal">InputStream</code> really is an object stream. If we tried to create both of our input streams first, we would deadlock waiting for the other side to write the headers.</p><p>Finally, we run the example, giving it a port number as an argument:</p><a id="I_13_tt867"/><pre class="programlisting"> <code class="o">%</code> <strong class="userinput"><code><code class="n">java</code> <code class="n">Server</code> <code class="mi">1234</code></code></strong></pre><p>Then we run the <code class="literal">Client</code>, telling it the server’s hostname and port number:</p><a id="I_13_tt868"/><pre class="programlisting"> <code class="o">%</code> <strong class="userinput"><code><code class="n">java</code> <code class="n">Client</code> <code class="n">flatland</code> <code class="mi">1234</code></code></strong></pre><p>The result should look something like this:</p><a id="I_13_tt869"/><pre class="programlisting"> <code class="n">Sun</code> <code class="n">Mar</code> <code class="mi">5</code> <code class="mi">14</code><code class="o">:</code><code class="mi">25</code><code class="o">:</code><code class="mi">25</code> <code class="n">PDT</code> <code class="mi">2006</code> <code class="mi">4</code></pre><p>All right, the result isn’t that impressive, but it’s easy to think of more substantial applications. Imagine that you need to perform a complex computation on many large datasets. Using serialized objects makes maintenance of the data objects natural and sending them over the wire trivial. There is no need to deal with byte-level protocols at all.</p><div class="sect3" title="Limitations"><div class="titlepage"><div><div><h3 class="title"><a id="learnjava3-CHP-13-SECT-3.1.1"/>Limitations</h3></div></div></div><p>As we mentioned earlier, there is one catch in this scenario: both the client and server need access to the necessary classes. That is, all the <code class="literal">Request</code> classes—including <code class="literal">MyCalculation</code>, which is really the property of the <code class="literal">Client</code>—must be deployed in the classpath on both the client and the server machines. In the next section, we’ll see that it’s possible to send the Java bytecode along with serialized objects to allow completely new kinds of objects to be transported dynamically over the network. We could create this solution on our own, adding to the earlier example using a network class loader to load the classes for us. But we don’t have to: Java’s RMI facility handles that for us. The ability to send both serialized data and class definitions over the network is not always needed, but it makes Java a powerful tool for prototyping and developing advanced distributed applications.<a id="I_indexterm13_id769332" class="indexterm"/><a id="I_indexterm13_id769339" class="indexterm"/></p></div></div></div></body></html>