■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 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. 不許無断複製 |