■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2007年09月02日

    楽しいJava講座 - 初心者から達人へのパスポート
                  vol.068

                                セルゲイ・ランダウ
 バックナンバー: http://www.flsi.co.jp/Java_text/
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[このメールマガジンは、画面を最大化して見てください。]


========================================================
◆ 01.ネットワーク系のプログラミング
========================================================


前回はソケット通信(TCP使用)のプログラムを作ってみました。
ソケット(socket)という言葉は、元々は(ケーブルなどの)差込口
の意味ですが、TCP/IPでは、ちょうど通信ケーブルの差込口のよう
なイメージから通信の窓口の意味で使われています。

TCPによるソケット通信の手順を簡単に説明しましょう。
まず、クライアントは通信の窓口を開くためにソケットのオブジェ
クト(インスタンス)を生成しますが、このとき相手(サーバー)
のIPアドレスとポート番号を指定する必要があります。
(前回お話したように、IPアドレスが相手のコンピューターを特定
し、ポート番号が相手のプログラムを特定します。)
これは、例えば
--------------------------------------------------------
Socket socket;
socket = new Socket("IPアドレス", ポート番号);
--------------------------------------------------------
という形式で、行います。

このインスタンス生成のコードが実行されると、指定した相手への
接続が試みられます。そして接続できればインスタンスが生成され、
接続できなかった場合はUnknownHostExceptionかIOExceptionの例外
が発生します。
UnknownHostExceptionは(指定したIPアドレスが実在しないなどの
理由で)相手のホストが見つからない場合に発生し、IOExceptionは
それ以外の理由で相手に接続できない場合に発生します。

接続に成功したら(例外が発生しなかったら)、サーバーからデータ
を受信したい場合はSocketオブジェクトのgetInputStream()を実行して
InputStreamオブジェクトを取得し、InputStreamオブジェクトのread()
メソッドを実行することによってデータを受信します。
あるいは、サーバーにデータを送信したい場合はSocketオブジェクト
のgetOutputStream()を実行してOutputStreamオブジェクトを取得し、
OutputStreamオブジェクトのwrite()メソッドを実行することによって
データを送信します。

なお、InputStreamオブジェクトのread()メソッドやOutputStreamオブ
ジェクトのwrite()メソッドを実行するときには、相手側(サーバー側)
のOutputStreamオブジェクトのwrite()メソッドやInputStreamオブジェ
クトのread()メソッドの実行とタイミングが合っている必要があります。
たとえば両方が同時にwrite()しようとしたり、同時にread()しようと
したりしたら、当然ながらエラーになってしまいます。一方がwrite()し
ているときには他方はread()していなければなりません。

受信もしくは送信が完了したら、InputStreamオブジェクトもしくは
OutputStreamオブジェクトをclose()し、Socketオブジェクトも
close()します。この時点でサーバーへの接続が切れます。

では、クライアントのソース・コードを確認してみてください。

[クライアント]
--------------------------------------------------------
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class TcpSocketClient {

   public static void main(String[] args) {
      Socket socket;
      InputStream sIn;
      OutputStream sOut;
     
      try {
         int c;
         socket = new Socket("127.0.0.1", 49152);
         sIn = socket.getInputStream();
         while((c = sIn.read()) != -1) {
            System.out.print(c);
         }
         sIn.close();
         socket.close();
         socket = new Socket("127.0.0.1", 49152);
         sOut = socket.getOutputStream();
         String sendData = "Data1";
         for (int i = 0; i < 5; i++) {
            sOut.write((int)sendData.charAt(i));
         }
         sOut.close();
         socket.close();
      } catch (UnknownHostException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }

}
--------------------------------------------------------

sIn(InputStreamオブジェクト)のread()メソッドを実行すると1バイト
のデータを取り出してint型にして返します。再度read()メソッドを実行
すると次の1バイトのデータを取り出してint型にして返します。もう
データがないときには-1を返します。(実際に取り出すデータは1バイト
ですから、データがあるときは0〜255の値が返ってきます。)
したがって、while((c = sIn.read()) != -1)というコードによって、
データがある間は返ってきた値をcに代入し、もうデータがないとき
(返ってきた値が-1のとき)はループを終了するようにしています。

こうしてread()メソッドを何回も実行することによって、細切れに
なったデータを受け取りますが、受け取るデータの順番はサーバー側
で送信した順番と同じであることがTCPプロトコルによって保証されて
います。

なお、read()メソッドには引数つきのものもあり、複数のバイトを一挙
に取り出すこともできます。詳しくはAPIリファレンスで調べてください。

sOut(OutputStreamオブジェクト)のwrite()メソッド(引数はint型)を
実行すると1バイトのデータが送信されます。したがって、引数に指定し
たint型の値のうち、実際に送信されるのは下位の8ビットのみになります。
0〜255の値を引数に指定している限り問題はありませんが、それより大きい
値もしくは負の値を指定した場合は、実際に送信される値は異なってしまう
ので注意が必要です。

read()メソッドと同じく、このwrite()メソッドを何回も実行することに
よって、細切れのデータを複数回に分けて送信します。

なお、write()メソッドには、複数のバイトを一挙に送信できるものも
あります。詳しくはAPIリファレンスで調べてください。

OutputStreamやInputStreamで普通のテキスト・データの送受信をする
のはちょっと面倒ですが、vol.017で紹介したBufferedWriterや
BufferedReaderを使用するともっと楽になります。
そのためには、例えば
--------------------------------------------------------
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
--------------------------------------------------------
といふうにしてInputStreamにBufferedReaderを着せておけば、
inのreadLine()メソッドでデータを1行ずつ読み込むことができます。
また、
--------------------------------------------------------
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
--------------------------------------------------------
といふうにしてOutputStreamにBufferedWriterを着せておけば、
outのwrite()メソッドでデータを書き出すことができます。
この場合、データの送信は、outのflush()メソッドを実行すること
によって行われます。


続いてサーバーのほうを説明しましょう。
サーバーでは、例えば
--------------------------------------------------------
ServerSocket server;
server = new ServerSocket(ポート番号, 受信する接続要求の待ち行列の数);
--------------------------------------------------------
という形式で、ソケットのオブジェクトを生成します。

クライアントのソケット・オブジェクトの生成時に指定するポート番号
は、このServerSocketに指定したポート番号です。

ネットワークにつながっている複数のクライアントから接続要求が来る
可能性がありますが、クライアントからの接続要求はいったん待ち行列
(queue)に入れられ、順番に処理されます。
「受信する接続要求の待ち行列の数」という引数は、この待ち行列(queue)
に入れられる要求の最大数を指定するものです。接続要求の数がこの引数
の数に達すると、つまり待ち行列がいっぱいになると、それ以上は接続要求
は受け付けられません。しかし、処理が進んで待ち行列の中が空いてくると、
それ以後に来た接続要求はまた待ち行列に入るようになります。

なお、サーバーなので、クライアントからの接続要求を待つだけであり、
自分から相手に接続にいくことはしません。したがって、相手(クライア
ント)のIPアドレスの指定する必要はありません。
(クライアントから接続の要求が来たときには、IPプロトコルのレベルで
送信者(クライアント)のIPアドレスの情報も渡されるので、相手(クライア
ント)のIPアドレスはわかります。)

ServerSocketのオブジェクトを生成したら、accept()メソッドを実行する
ことによって、クライアントからの接続要求を待ちます。accept()メソッド
は接続要求が来るまでひたすら待機します。

接続要求が来ると、accept()メソッドは、Socketオブジェクトを返します。
これは相手(クライアント)のSocketオブジェクトを表現します。
このSocketオブジェクトを通してデータの送受信を行います。ここら辺は
クライアントと同じ操作をします。ただし、クライアントとは送信と受信
を逆にしてタイミングを合わせる必要があります。

では、サーバーのソース・コードを確認してみてください。
クライアントのソース・コードとも見比べて、送信と受信が互いに逆になっ
ていることも確認してください。

[サーバー]
--------------------------------------------------------
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpSocketServer {

   public static void main(String[] args) {
      ServerSocket server = null;
      Socket socket;
      int[] sendData = new int[3];
      for (int i = 0; i < 3; i++) {
         sendData[i] = i + 1;
      }
      OutputStream sOut;
      InputStream sIn;
     
      try {
         server = new ServerSocket(49152, 200);
         while (true) {
            socket = server.accept();
            sOut = socket.getOutputStream();
            for (int i = 0; i < 3; i++) {
               sOut.write(sendData[i]);
            }
            sOut.close();
            socket.close();
            socket = server.accept();
            sIn = socket.getInputStream();
            int c;
            while((c = sIn.read()) != -1) {
               System.out.print((char)c);
            }
            System.out.println();
            sIn.close();
            socket.close();
         }
      } catch (IOException e) {
         e.printStackTrace();
      }
   }

}
--------------------------------------------------------


なお、このTCPを使ったソケット通信では、サーバーが起動されていない
ときにクライアントを実行するとエラー(接続が受け付けられない旨の
例外)になりますね。

その点、UDPを使ったソケット通信では、サーバーが起動されていないとき
にクライアントからデータを送信してもエラーにはなりません。

では、UDPを使ったソケット通信のプログラムを作って試してみましょう。


[UDPを使ったサーバー]
--------------------------------------------------------
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpSocketServer {

   public static void main(String[] args) {
      DatagramSocket socket = null;
      byte[] inData = new byte[5];
      int inLength = inData.length;
      DatagramPacket packet = null;

      try {
         socket = new DatagramSocket(49152);
      } catch (SocketException e) {
         e.printStackTrace();
      }
      packet = new DatagramPacket(inData, inLength);
      for(int n = 0; n < 5; n++) {
         try {
            socket.receive(packet);
         } catch (IOException e) {
            e.printStackTrace();
         }
         for (int i = 0; i < inLength; i++) {
            System.out.print(inData[i]);
         }
         System.out.println();
      }
      socket.close();
   }

}
--------------------------------------------------------

ここでは、「for(int n = 0; n < 5; n++)」というfor文がありますから、
このサーバーはデータ(DatagramPacket)を5回受信したら終了するように
なっています。


[UDPを使ったクライアント]
--------------------------------------------------------
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

public class UdpSocketClient {

   public static void main(String[] args) {
      DatagramSocket socket = null;
      byte[] outData = {5, 4, 3, 2, 1};
      int outLength = outData.length;
      DatagramPacket packet = null;

      try {
         socket = new DatagramSocket();
      } catch (SocketException e) {
         e.printStackTrace();
      }
      try {
         packet = new DatagramPacket(outData, outLength, InetAddress.getByName("localhost"), 49152);
         System.out.println("Data 54321 sent.");
      } catch (UnknownHostException e1) {
         e1.printStackTrace();
      }
      try {
         socket.send(packet);
      } catch (IOException e) {
         e.printStackTrace();
      }
      socket.close();
   }

}
--------------------------------------------------------
上記のソース・コードでは、「InetAddress.getByName("localhost")」の
代わりに「new byte[]{127, 0, 0, 1}」という指定をすることもできます。


ソース・コードの保管(コンパイル)が終わったら、コマンド・プロンプト
でサーバーを起動し、クライアントを6回起動してみてください。5回目で
サーバーは終了しますね。
そうすると、6回目ではサーバーは存在していないわけですが、クライアント
側には何もエラーが発生しないことがわかりますね。

UDPでは、データが相手に渡ったかどうかの確認はされないので、エラーには
ならないのです。


では、このソース・コードの解説は次回行います。

何か、わからないところがありましたら、下記のWebページまで質問を
お寄せください。


(続く)



========================================================
◆ 02.Java(文法等)解説 [JavaBeans (3)]
========================================================

[JavaBeans (3) プロパティー<2>]


<(1) シンプル・プロパティー(simple property)>

シンプル・プロパティーは一番基本的で一番単純な一番普通のプロパティー
です。
設定されたり取り出されたりする値は1つだけで、値の変更は他のBeanから
独立して行われます。
これは、次のようなgetter(getメソッド)、setter(setメソッド)によっ
て定義されます。

[getter]
--------------------------------------------------------
public  プロパティーの型  getプロパティー名()
--------------------------------------------------------

[setter]
--------------------------------------------------------
public  void  setプロパティー名(プロパティーの型  arg)
--------------------------------------------------------
ここでargは引数の変数名であり、何でもいいのですが、わかりやすい名前に
したほうがいいでしょう。


たとえば、String型のnameというプロパティーを定義するためには、次のよう
なgetter、setterを用意します。
--------------------------------------------------------
public  String  getName()
--------------------------------------------------------
--------------------------------------------------------
public  void  setName(String  newName)
--------------------------------------------------------

具体的に実装の例をあげると、たとえば下記のようになります。
--------------------------------------------------------
private String name = null;

public  String  getName() {
   return name;
}

public  void  setName(String  newName) {
   name = newName;
}
--------------------------------------------------------



なお、boolean型の場合は特別で、getterは次のように定義します。
--------------------------------------------------------
public  boolean  isプロパティー名()
--------------------------------------------------------


例えば、boolean型のactiveというプロパティーを定義するためには、
次のようなgetter、setterを用意します。
--------------------------------------------------------
public  boolean  isActive()
--------------------------------------------------------
--------------------------------------------------------
public  void  setActive(boolean  b)
--------------------------------------------------------

具体的に実装の例をあげると、たとえば下記のようになります。
--------------------------------------------------------
private boolean  active = false;

public  boolean  isActive() {
   return active;
}

public  void  setActive(boolean  b) {
   active = b;
}
--------------------------------------------------------


普通はsetterとgetterの両方を定義しますが、プロパティーの値を
読み取りのみ可能(書き込み不可)にしたい場合はgetterのみを定義
し、プロパティーの値を書き込みのみ可能(読み取り不可)にしたい
場合はsetterのみを定義します。



<(2) 結合プロパティー(bound property)>

値が変更されると他のBeanに通知されるプロパティーです。
値が変更されたときに通知されるのは、PropertyChangeEventという
イベントです。
PropertyChangeEventのイベント・リスナーはPropertyChangeListener
というインターフェースを実装して作ります。(これらは、java.beans
パッケージにはいっています。)
このイベントが通知されるときに呼び出されるメソッドは
PropertyChangeListenerのpropertyChange()メソッドです。
propertyChange()メソッドは次のようなシグネチャーになっています。
--------------------------------------------------------
void propertyChange(PropertyChangeEvent evt)
--------------------------------------------------------
このイベント・リスナーは、通知を受けるBean側で直接実装してもいい
のですが、もっといい方法はイベントを通知するBeanと通知を受けるBean
の間に仲介役のクラスを作成し、これにイベント・リスナーを実装させる
ことです(ビルダー・ツールは通常、この仲介役のクラスを自動生成する
ことによって生産性を向上します)。詳しくは後述します。


結合プロパティーを持つクラスには、シンプル・プロパティーの場合の
定義(setter、getterの定義)の他に下記のような定義が必要になります。

[1] PropertyChangeSupportクラスのインスタンスの生成

PropertyChangeSupportは結合プロパティーをサポート(支援、支持)する
ために用意されているクラスです。これを例えば次のようにしてインスタ
ンス生成します。(ここではBeanのクラス名をBean01とします。)
--------------------------------------------------------
private PropertyChangeSupport support = null;

public Bean01() { // コンストラクター
   support = new PropertyChangeSupport(this);
}
--------------------------------------------------------

[2] イベント・リスナーの登録/削除用のメソッドの定義

結合プロパティーを持つクラスは次のようなイベント・リスナー登録/削除
用のメソッドを持っていなければなりません。
--------------------------------------------------------
public void addPropertyChangeListener(PropertyChangeListener listener)
public void removePropertyChangeListener(PropertyChangeListener listener)
--------------------------------------------------------
しかし、実際の登録/削除の処理はPropertyChangeSupportの同名のメソッ
ドを使って行うことができますので、これらのメソッドは具体的には次の
ように実装します。
--------------------------------------------------------
public void addPropertyChangeListener(PropertyChangeListener listener) {
   support.addPropertyChangeListener(listener);
}

public void removePropertyChangeListener(PropertyChangeListener listener)
   support.removePropertyChangeListener(listener);
}
--------------------------------------------------------

[3] 結合プロパティーの値の変更時のイベント通知処理

結合プロパティーのsetterの中でイベント通知を行います。実際のイベント
通知はPropertyChangeSupportのfirePropertyChange()メソッドを使って行う
ことができます。
例えば、先ほどシンプル・プロパティーで例にあげたnameプロパティーの
場合は次のように実装します。
--------------------------------------------------------
public  void  setName(String  newName) {
   String prevName = name;
   name = newName;
   support.firePropertyChange("name", prevName, newName);
}
--------------------------------------------------------
このfirePropertyChange("name", prevName, newName);の実行によって、これ
らの引数の情報を持つPropertyChangeEventオブジェクトが作られ通知されます。


<(3) 制約付きプロパティー(constrained property)>

値の変更が他のBeanによってチェックされるプロパティーです。不適切な
変更を行おうとしたときに他のBeanから拒否できるようにしたい場合など
に使用します。


このプロパティーでは、値が変更される前にPropertyChangeEventが通知
されます。
制約付きプロパティーの場合のPropertyChangeEventのイベント・リスナー
はVetoableChangeListenerというインターフェースを実装して作ります。
(これも、java.beansパッケージにはいっています。)
また、呼び出されるメソッドはVetoableChangeListenerのvetoableChange()
メソッドです。
vetoableChange()メソッドは次のようなシグネチャーになっています。
--------------------------------------------------------
void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException
--------------------------------------------------------
(ちなみに、vetoは拒否権の意味で、vetoableは拒否可能の意味です。)

このイベント・リスナーは、通知を受けるBean側で直接実装してもいいの
ですが、イベントを通知するBeanと通知を受けるBeanの間に仲介役のクラス
を作成し、これにイベント・リスナーを実装させることもできます
(ビルダー・ツールは通常、この仲介役のクラスを自動生成することに
よって生産性を向上します)。詳しくは後述します。


制約付きプロパティーを持つクラスには、シンプル・プロパティーの場合
の定義(setter、getterの定義)の他に下記のような定義が必要になり
ます。

[1] VetoableChangeSupportクラスのインスタンスの生成

VetoableChangeSupportは制約付きプロパティーをサポートするために
用意されているクラスです。これを例えば次のようにしてインスタンス
生成します。(ここではBeanのクラス名をBean01とします。)
--------------------------------------------------------
private VetoableChangeSupport support = null;

public Bean01() { // コンストラクター
   support = new VetoableChangeSupport(this);
}
--------------------------------------------------------

[2] イベント・リスナーの登録/削除用のメソッドの定義

制約付きプロパティーを持つクラスは次のようなイベント・リスナー
登録/削除用のメソッドを持っていなければなりません。
--------------------------------------------------------
public void addVetoableChangeListener(VetoableChangeListener listener)
public void removeVetoableChangeListener(VetoableChangeListener listener)
--------------------------------------------------------
しかし、実際の登録/削除の処理はVetoableChangeSupportの同名の
メソッドを使って行うことができますので、これらのメソッドは
具体的には次のように実装します。
--------------------------------------------------------
public void addVetoableChangeListener(VetoableChangeListener listener) {
   support.addVetoableChangeListener(listener);
}

public void removeVetoableChangeListener(VetoableChangeListener listener)
   support.removeVetoableChangeListener(listener);
}
--------------------------------------------------------

[3] 制約付きプロパティーの値の変更時のイベント通知処理

制約付きプロパティーのsetterの中でイベント通知を行います。実際の
イベント通知はVetoableChangeSupportのfireVetoableChange()メソッド
を使って行うことができます。
例えば、先ほどシンプル・プロパティーで例にあげたnameプロパティー
の場合は次のように実装します。
--------------------------------------------------------
public  void  setName(String  newName) throws PropertyVetoException {
   String prevName = name;
   support.fireVetoableChange("name", prevName, newName);
   name = newName;
}
--------------------------------------------------------
イベント通知(support.fireVetoableChange("name", prevName, newName);)
を実行したときに、もし拒否権が発行された(イベント・リスナーの
vetoableChange()メソッドが実行されたときにPropertyVetoExceptionが発生
した)場合には、「support.fireVetoableChange("name", prevName, newName);」
がPropertyVetoExceptionをthrowします。したがって、「name = newName;」の
行は実行されません。
一方、PropertyVetoExceptionが発生しなかった場合は、「name = newName;」
の行が実行されます。

なお、イベント・リスナーのvetoableChange()メソッドは例えば次のように
実装して拒否権(PropertyVetoException)を発行します。
--------------------------------------------------------
void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
   String newValue = (String) evt.getNewValue();
   if (newValue.equals("名無しのごんべい"))
      throw new PropertyVetoException("名無しのごんべい is not allowed.", evt):
}
--------------------------------------------------------



<(4) インデックス・プロパティー(indexed property)>

配列によって複数の値を含むプロパティーです。

例えば、luckyNumberというintの配列型のインデックス・プロパティーが
あったとする(配列の要素数は10とする)と、以下のような定義ができます。
--------------------------------------------------------
private int[] luckyNumber;

public Bean01() { // コンストラクター
   luckyNumber = new int[10];
}

public  int[]  getLuckyNumber() {
   return luckyNumber;
}

public  void  setLuckyNumber(int[] number) {
   luckyNumber = number;
}

public  int  getLuckyNumber(int index) {
   return luckyNumber[index];
}

public  void  setLuckyNumber(int number, int index) {
   luckyNumber[index] = number;
}
--------------------------------------------------------





(続く)



以上、今回は
┌───────────────────────────┐
・ソケット通信(TCP)のプログラミング
・JavaBeansのシンプル・プロパティー
・JavaBeansの結合プロパティー
・JavaBeansの制約付きプロパティー
・JavaBeansのインデックス・プロパティー
└───────────────────────────┘
を学習しました。
では、また来週。



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      http://www.flsi.co.jp/Java_text/
★このメールマガジンは
     「まぐまぐ(http://www.mag2.com)」
 を利用して発行しています。
★バックナンバーは
      http://www.flsi.co.jp/Java_text/
 にあります。
★このメールマガジンの登録/解除は下記Webページでできます。
      http://www.mag2.com/m/0000193915.html
★このメールマガジンへの質問は下記Webページにて受け付けて
 います。わからない所がありましたら、どしどしと質問をお寄
 せください。
      http://www.flsi.co.jp/Java_text/
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Copyright (C) 2007 Future Lifestyle Inc. 不許無断複製