シリアライズ(直列化)したJavaオブジェクトのソケットによる送受信

 

通常ソケットで情報を送受信する場合、ソケットに渡す情報はバイト配列の形をとっている必要があるが、Javaアプリケーションの場合、情報は通常オブジェクトとして持っているのが普通で型情報にギャップ(一種のインピーダンスミスマッチ)が存在している。

 

で、Javaオブジェクトをバイト配列に変換するにはどうしたらよいか。

バイト配列用ストリームであるByteArrayInputStreamByteArrayOutputStreamを用いて、オブジェクト<->バイト配列の変換を行えばよい。

下記サンプルでは、このような状況におけるオブジェクトのバイト配列変換と、クライアント側ソケットとサーバ側ソケットによる送受信のための方法を記述している。

ここで若干混乱の元となっているのは、それぞれ別の用途(バイト配列変換とソケット送受信)のために別のストリームを用いているところであろうか。

処理の概略を以下にまとめる。

 

クライアント側:

@Javaオブジェクトをバイト配列にシリアライズするところでバイト配列用ストリームを利用

Aバイト配列をソケットから入手した出力ストリームを通じて送信

 

サーバ側:

Bソケットから入手した入力ストリームを通じてバイト配列を受信

Cバイト配列をJavaオブジェクトにデシリアライズ(非直列化)するところでバイト配列用ストリームを利用

 

特に、@のバイト配列用ストリームを利用の所では、普段のストリームでの記述とは一風変わった記述がなされている。

前置きはこれくらいにしてサンプルの解説。

 

下記サンプル(Family.java)では、普通のJavaオブジェクトであるFamilyクラスがExternalizableインタフェースを実装し、エクスターナライズ可能(*1)となっている。

*1: エクスターナライズもシリアライズの一種でアプリケーションにより細かい制御を可能とするための実装を可能とするためのインタフェースを提供する。

Externalizableインタフェースは、Serializableインタフェースを継承している。

 

package com.aksys.serialize;

 

import java.io.Externalizable;

import java.io.IOException;

import java.io.ObjectInput;

import java.io.ObjectOutput;

import java.util.Arrays;

 

public class Family implements Externalizable {

 

       private static final long serialVersionUID = -6337815127797603301L;

 

       private String firstName;

       private String lastName;

       private byte[] ids;

      

       public Family() {

       }

 

       public Family(String firstName, String lastName, byte[] ids) {

              super();

              this.firstName = firstName;

              this.lastName = lastName;

              this.ids = ids;

       }

 

       public String getFirstName() {

              return firstName;

       }

 

       public String getLastName() {

              return lastName;

       }

 

       public byte[] getIds() {

              return ids;

       }

 

       @Override

       public String toString() {

              return "Family [firstName=" + firstName + ", lastName=" + lastName

                            + ", ids=" + Arrays.toString(ids) + "]";

       }

 

       @Override

       public void writeExternal(ObjectOutput out) throws IOException {

              out.writeObject(firstName);

              out.writeObject("XX hidden XX");

              out.writeObject(ids);

       }

 

       @Override

       public void readExternal(ObjectInput in) throws IOException,

                     ClassNotFoundException {

              firstName = (String)in.readObject();

              lastName = (String)in.readObject();

              ids = (byte[])in.readObject();

       }

 

}

 

 

クライアント側(ソケット送信側)はこんな感じ。

 

package com.aksys.serialize;

 

import java.io.ByteArrayOutputStream;

import java.io.ObjectOutputStream;

import java.io.OutputStream;

import java.net.InetSocketAddress;

import java.net.Socket;

 

public class MyClientSocket {

 

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

              MyClientSocket cs = new MyClientSocket();

              cs.process();

       }

 

       private void process() throws Exception {

 

              Family f = new Family("Hinako", "Dxx", new byte[] { 0, 1, 2 });

 

              ByteArrayOutputStream baos = new ByteArrayOutputStream();

              ObjectOutputStream oos = null;

              byte[] yourBytes = null;

               Socket sock = new Socket();

        OutputStream os = null;

              try {

                     // オブジェクトをバイト配列化

                     oos = new ObjectOutputStream(baos);

                     oos.writeObject(f);

                     yourBytes = baos.toByteArray();

                    

                      // ソケットを作成してバイト配列をサーバに送信する。

                      sock.connect(new InetSocketAddress("localhost", 3153));

                      os = sock.getOutputStream();

                      os.write(yourBytes);

                      os.flush();

                    

              } finally {

                     oos.close();

                     baos.close();

                     sock.close();

                     os.close();

              }

       }

 

}

 

オブジェクトをバイト配列化」するところで、オブジェクト用の出力ストリームとしてObjectOutputStreamのインスタンスを作成する際に、ByteArrayOutputStreamのインスタンスを渡しておく。

これで作ったObjectOutputStreamwriteObjectメソッドで一旦出力ストリーム上でオブジェクトをシリアライズするのだが、インスタンスの実体がByteArrayOutputStreamのインスタンスであるため、ストリームの内部ではバイト配列によりデータを保持している模様。

その後、渡しておいたByteArrayOutputStreamのインスタンスのtoByteArrayメソッドを用いてシリアライズされたストリーム上のバイト配列のコピーを取り出している。

 

そのあとのソケットでの送信はバイト配列を送信するいつも通りのやり方。

 

サーバ側:

package com.aksys.serialize;

 

import java.io.ByteArrayInputStream;

import java.io.InputStream;

import java.io.ObjectInputStream;

import java.net.ServerSocket;

import java.net.Socket;

 

public class MyServerSocket {

 

       int port = 3153; // ポート番号

       int timeout_msec = 10000;

 

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

 

              MyServerSocket ss = new MyServerSocket();

              ss.process();

       }

 

       private void process() throws Exception {

 

              // サーバソケット

              ServerSocket serverSocket = null;

              Socket socket = null;

              InputStream is = null;

              ByteArrayInputStream bais = null;

              ObjectInputStream ois = null;

 

              try {

                     // サーバ側ソケット処理 バイト読み込みまで

                     serverSocket = new ServerSocket(port);

                     socket = serverSocket.accept();

                     is = socket.getInputStream();

                     byte[] yourBytes = new byte[is.available()];

                     is.read(yourBytes, 0, is.available());

 

                     // バイト配列をオブジェクトに復元

                     bais = new ByteArrayInputStream(yourBytes);

                     ois = new ObjectInputStream(bais);

                     Family f2 = (Family) ois.readObject();

                     System.out.println(f2);

              } catch (Exception e) {

                     e.printStackTrace();

              } finally {

                     serverSocket.close();

                     socket.close();

                     bais.close();

                     ois.close();

              }

       }

 

}

 

サーバ側でのソケットによるバイト配列の受信はやはりいつも通り。

受け取ったバイト配列のオブジェクトへのデシリアライズもこちらは普段通り。

 

もうこれでソケットで送れないJavaオブジェクトって無いな〜、多分。