Java中通過數據報包輸送對象

Java 1.1 吸引人的特性之一就是新增了 ObjectInputStreamObjectOutputStream 這兩個類。有了這個新的 API(ObjectOutputStream 類中的 writeObject(Object o) 方法和 ObjectInputStream 類中的 object readObject()),您就可以隨時獲取運行對象的快照,而不管它的對象圖有多複雜。因爲這種快照是通過 ObjectOutputStream 類(OutputStream 類的子類)提供的,所以您很容易將它包裝在其他輸出流中,從而實現所需的任何功能(如 FileOutputStream)。

Java 1.1 中提供的這些新類使得在網上傳輸運行對象成爲可能。爲此,該對象以及那些被引用的對象必須可序列化 -- 即能夠轉換爲字節流。幸運的是,在 Java 1.1 中,多數內建的類都是可序列化的。但是,某些類是不可序列化的(Object 類就是一個典型的例子)。不過別擔心。如果您的類繼承自不可序列化的類,您還可以用 ObjectOutputStream 類中的 defaultWriteObject() 方法實現序列化,隨後還可用 ObjectInputStream 類中的 defaultReadObject() 方法解除序列化。

一旦進行了序列化,對象就可在網上傳輸了。以下示例說明生成可序列化對象並通過流套接字發送它的方法:

//對象輸出
import java.net.*;
import java.io.*;

//要發送的類樣例:Factory
class Factory implements Serializable
{
  private void writeObject(ObjectOutputStream out) throws IOException
  {
    out.defaultWriteObject();
  }

  private void readObject(ObjectInputStream in)
               throws IOException, ClassNotFoundException
  {
    in.defaultReadObject();
  }
}

public class ShowObjOutput
{
  public static void main(String[] arg)
  {
    try
    {
      ObjectOutputStream os;
      Socket sock = new Socket("panda.cs.uno.edu", 6000); //panda 爲主機名
      Factory fa = new Factory();

      os = new ObjectOutputStream( new
             BufferedOutputStream(sock.getOutputStream()));
      os.writeObject(fa);
    }
    catch (IOException ex)
    {}
  }
}

下一示例說明了 ObjectInputStream 如何從流套接字接收對象:

//對象輸入
import java.net.*;
import java.io.*;

public class ShowObjInput
{
  public static void main(String[] arg)
  {
    try
    {
      ObjectInputStream is;
      ServerSocket servSock = new ServerSocket(6000);
      Sock sock;

      sock = servSock.accept();
      is = new ObjectInputStream( new
               BufferedInputStream(sock.getInputStream()));
      Factory o = (Factory)is.readObject();
    }
    catch (IOException ex)
    {}
  }
}

除了緊密耦合的套接字之外,Java 還提供了 DatagramSocket 類來支持無連接的數據報通信。我們可以使用數據報通信完成對象輸入/輸出嗎?完成此功能不象使用流套接字那麼簡單?問題在於 DatagramSocket 未連接到任何流;爲了執行發送和接收操作,DatagramSocket 使用一個字節數組作爲參數。

可以想像,爲了構造數據報包,對象必須轉換成字節數組。如果對象涉及到一個複雜的對象圖,這種轉換可能極難完成。以前發表的許多文章討論了實現對象序列化的方法 -- 即將 Java 對象打包(序列化)成字節流以及將字節流解包爲 Java 對象。然而,由於對象圖可能很複雜,則將常規對象圖轉換成字節數組可能需要編寫大量的代碼。

那麼,如何避免編寫複雜的打包代碼呢?以下提供了一種利用數據報包傳輸對象的方法,而且無需編寫打包代碼。

jw-javatip40.gif

上圖說明了使用數據報傳輸對象時的數據流。按以下給出的七個步驟,您就能實現這個數據流,它可傳輸任何類型的對象,myObject

  • 第一步。準備:通過實現 Serializable 接口使您的對象(比方說 myObject)可序列化。

  • 第二步。創建 ByteArrayOutputStream 對象,比方說,名爲 baoStream

  • 第三步。用 baoStream 構造一個 ObjectOutputStream 對象,比方說 ooStream

  • 第四步。通過調用 ooStreamwriteObject() 方法將對象 myObject 寫入 baoStream 中。

  • 第五步。使用 baoStreamtoByteArray() 方法從 baoStream 中檢索字節數組緩衝區。

  • 第六步。使用由第五步檢索到的數組緩衝區構造 DatagramPacket,比方說 dPacket

  • 第七步。通過調用 DatagramSocketsend() 方法發送 dPacket

要接收對象,以逆序完成以上所列各步,用 ObjectInputStream 代替 ObjectOutputStream,同時用 ByteArrayInputStream 代替 ByteArrayOutputStream

當用套接字編程時,sendTo 是無連接協議中使用的一個標準函數。爲了能夠傳輸對象,我重寫了這個函數。以下代碼示例展示瞭如何在 Sender 類中實現 send 方法:

import java.io.*;
import java.net.*;

public class Sender
{
  public void sendTo(Object o, String hostName, int desPort)
  {
    try
    {
      InetAddress address = InetAddress.getByName(hostName);
      ByteArrayOutputStream byteStream = new
          ByteArrayOutputStream(5000);
      ObjectOutputStream os = new ObjectOutputStream(new
                              BufferedOutputStream(byteStream));
      os.flush();
      os.writeObject(o);
      os.flush();

      // 檢索字節數組
      byte[] sendBuf = byteStream.toByteArray();
      DatagramPacket packet = new DatagramPacket(
                          sendBuf, sendBuf.length, address, desPort);
      int byteCount = packet.getLength();
      dSock.send(packet);
      os.close();
    }
    catch (UnknownHostException e)
    {
      System.err.println("Exception: " + e);
      e.printStackTrace ();
    }
    catch (IOException e)
    { e.printStackTrace(); }
  }
}

以下代碼清單說明了如何在 Receiver 類中實現 receive 方法。recvObjFrom 方法是供接收者接收對象的。您應在您的代碼中包含此方法以接收運行時對象。

import java.io.*;
import java.net.*;

public class Receiver
{
  public Object recvObjFrom()
  {
    try
    {
      byte[] recvBuf = new byte[5000];
      DatagramPacket packet = new DatagramPacket(recvBuf,
                                                 recvBuf.length);

      dSock.receive(packet);
      int byteCount = packet.getLength();

      ByteArrayInputStream byteStream = new
                                  ByteArrayInputStream(recvBuf);

      ObjectInputStream is = new
           ObjectInputStream(new BufferedInputStream(byteStream));
      Object o = is.readObject();
      is.close();
      return(o);
    }
    catch (IOException e)
    {
      System.err.println("Exception: " + e);
      e.printStackTrace ();
    }
    catch (ClassNotFoundException e)
    { e.printStackTrace(); }

    return(null);
  }
}

人們可能會擔心字節數組的大小 -- 因爲當您構造 ByteArrayOutputStreamByteArrayInputStream 時,您必須指定數組的大小。既然您不知道運行時對象的大小,您就很難指定其大小。運行時對象的大小通常是不可預知的。幸運的是,Java 的 ByteArrayInputStreamByteArrayOutputStream 類可根據需要自動擴展其大小。

小結
通過利用 Java 的內建序列化代碼,我闡述了一種使用數據報包傳輸對象的方法。正如您所見,技巧就是使用字節數組流將對象流化爲字節數組。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章