Java言語では豊富なクラスライブラリーが標準で提供されています。ここでは幾つかのクラスライブラリーを,これを使った簡単なアプリケーションと合わせて紹介します。
目次
[目次 ]
java.netパッケージに通信関係のクラスが多数用意されている。
自機のネットワークアドレスを取得する例題。
import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Vector; public class AddressChecker { static public void main(String arg[]) { try{ InetAddress localhost=InetAddress.getLocalHost(); print(localhost); InetAddress loopback=InetAddress.getLoopbackAddress(); print(loopback); }catch(UnknownHostException e){} } static void print(InetAddress inetAddress) throws UnknownHostException { System.out.println(); String hostname=inetAddress.getHostName(); System.out.printf("----- HostName=\"%s\" -----\n",hostname); //1つの名前に複数のアドレスが有る。 InetAddress list[]=InetAddress.getAllByName(hostname); for(InetAddress i:list){ System.out.printf("\n"); System.out.printf("HostAddress=\"%s\"\n",i.getHostAddress()); //そのアドレスの種類を判定して書き出す if(i.isAnyLocalAddress()) System.out.println(" ワイルドカード・アドレス"); if(i.isLinkLocalAddress()) System.out.println(" リンク・ローカル・アドレス"); if(i.isLoopbackAddress()) System.out.println(" ループバック・アドレス"); if(i.isMCGlobal()) System.out.println(" マルチキャスト・アドレスにグローバル・スコープがある"); if(i.isMCLinkLocal()) System.out.println(" マルチキャスト・アドレスにリンク・スコープがある"); if(i.isMCNodeLocal()) System.out.println(" マルチキャスト・アドレスにノード・スコープがある"); if(i.isMCOrgLocal()) System.out.println(" マルチキャスト・アドレスに組織スコープがある"); if(i.isMCSiteLocal()) System.out.println(" マルチキャスト・アドレスにサイト・スコープがある"); if(i.isSiteLocalAddress()) System.out.println(" サイト・ローカル・アドレス"); } } }
インターネット通信の受信と送信を行うソケットのクラスが用意されている。通信を待つ側の通信手順は次のようになる。
1) ポート番号を指定してServerSocketを開き受信を待つ。
2) ServerSocketが要求を受信しすると,相手との1対1の通信Socketを生成して戻す
3) 1対1の通信Socketで通信を行う(ここは並列処理に)
4) Socketを閉じて1対1の通信を終了
5) ServerSocketを切断して受信待機を終了
boolean flag=true;//要求待ちループのフラグ ServerSocket s_socket=null; try{ s_socket = new ServerSocket(80);//1)ポート番号80番のサーバソケットを作成 s_socket.setSoTimeout(100);//1回の要求待ちの時間を100msに設定 while(flag){//無限ループを止めるときはflagにfalseを代入する try{ Socket socket = s_socket.accept();//2)要求待ち /*ここで1対1のsocketを使って通信処理へ*/ this.new MyChannel(socket);//3) /*注意:次の要求待ちができないので通信処理は時間を食わないこと*/ } catch(SocketTimeoutException ste){} } s_socket.close();//4) s_socket=null; }catch(IOException e){ e.printStackTrace(); }
1対1の通信を行っているときに,ServerSocketを待たせたくないので。個々の通信はMyChannelクラスのオブジェクトに委譲し別スレッドで処理する。
class MyChannel implements Runnable { Socket socket=null; Thread thread=null; //ServerSocketを待たせないために1対1の通信は別スレッドで並列処理する MyChannel(Socket socket)//上のプログラムのスレッドで実行するのはこの部分のみ { this.socket = socket; thread=new Thread(this);//別のスレッドの生成 thread.start();//別スレッドのスタート } public void run(){//ServerSocketの受信待ちとは別のスレッドで実行する BufferedReader input=null; PrintWriter output=null; try{ //socketとの入出力ストリームを作る input=new BufferedReader( new InputStreamReader(socket.getInputStream(),"JISAutoDetect") ); output=new PrintWriter( new OutputStreamWriter(socket.getOutputStream(),"SJIS") ); ........input,outputを使って通信.......... }catch(IOException e){ e.printStackTrace(); }finally{//ここから後始末でストリームとsocketを閉じる try { if(output!=null){output.close();output=null;} if(input!=null){input.close();input=null;} if(socket!=null){socket.close();socket=null;} } catch (IOException e) {e.printStackTrace();} } } }
多くの要求が同時に来た時はsocketを作りすぎないように受信制限が必要。
プログラムを誤動作させるような要求の拒否
秘密のデータやファイルへの要求を拒否するセキュリティー
Webブラウザなどからの要求をそのままHTMLのテキストにして返信するHTTPサーバー。Webを利用したプログラムの動作確認をする。
受け付けるポート番号をHTTPに標準である80番にして要求の受信を待ちを行う。ログは要求側のアドレスとポート番号。
下図の水平線の間が送られてきた要求部分。最後の空白行が要求の終端を示す。
HTTPのクライアント側は相手が1つだけなのでプログラムは簡単になる。手順は
1) SocketをURLとポート番号を指定して生成
2) Socketから入出力ストリームを作る
3) 入出力ストリームを使って通信
4) ストリームとSocketを閉じる
Socket socket=null; BufferedReader input=null; PrintWriter outpu=null; try{ socket=new Socket("www.ibe.kagoshima-u.ac.jp",80);//サーバーのURLとポート番号 //受信用のストリーム input=new BufferedReader(new InputStreamReader(socket.getInputStream(),"JISAutoDetect")); //送信用のストリーム output=new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"SJIS")); //Topページを要求する output.print("GET /index.html HTTP/1.0\r\n"); output.print("\r\n"); //返事を標準出力に書き出す String line=""; while(true){ line=input.readLine();//入力が終端に来るとnullを返す if(line==null)break; System.out.println(line); } }catch(Exception ex){ ex.printStackTrace(); }finally{ output.close(); input.close(); socket.close(); }
※サーバーからファイルを貰うだけならURLクラスを用いるとSocketを使わずに簡単にできる。
WebブラウザのマネをしてWebサーバーに要求を出すプログラム例。サーバーの動作確認などを行える。
※サーバーへの要求は攻撃と瓦解されないように注意すること。ちょっとしたプログラムミスで結果的にサーバーへの攻撃になってしまうことがあります。
頻繁に要求を繰り返してサーバーに負荷を与えること。
公開されていないport番号にアクセスすること。
HTTPのプロトコルに違反する要求をサーバーに送って誤動作させること。
この他にもいろいろ考えられるのでHTTPプロトコルやサーバーの仕組みを学んで注意しよう
Windowsでは文字列や切り取った画像,そしてファイルなど 色々なものがドラッグ&ドロップできる。
とても繁雑なのでここでは一番使いそうなファイルのドロップを受け取る例を紹介する。これはレポートツールで課題ファイルを開くのにも使っている機能です。
例題は2つのファイルに分けました。一つはドラッグ&ドロップするファイルの受け取りを行う管理者。もう一つはこの管理者を使ってファイルリストを作るサンプルアプリです
この管理者はファイルをドロップするGUI部品(Componentを継承したオブジェクト)と,受け取ったファイルを貰うオブジェクトの仲介をします。貰うオブジェクトは管理者内に定義されたインターフェイスDnDFileManager.Receiverを実装することが条件です。
色々なものがドラッグ&ドロップできるのですが,相手に応じた対応が求められて繁雑なので,ここではファイルの受け取りに限定するために下記の管理者クラスを用意します。
簡単にファイルのドラッグ&ドロップを使いたいので,面倒な部分を管理者に代行してもらいます。
import java.awt.*; import java.awt.event.*; import java.awt.dnd.*;//ドラッグ&ドロップのパッケージ import java.awt.datatransfer.*;//データ転送のパッケージ import java.io.*; import java.util.List; import javax.swing.*; /** ドラッグ&ドロップされるファイルの管理者 指定されたComponentにドラッグ&ドロップされたファイルをReceiverに渡す管理者。 ドラッグ&ドロップは色々繁雑なので,ファイルの受け取りだけ行う管理者を作成。 この管理者を使う側は細かいことを知らなくても簡単に使えるようにする。 ドラッグ&ドロップのイベントを受け取るため管理者はDropTargetListenerを実装している。 */ public class DnDFileManager implements DropTargetListener // { /** ファイルを受け取るインターフェースReceiverを内部に定義。 これを実装したオブジェクトが受取オブジェクト */ static public interface Receiver { public void receive(File file); } protected Receiver receiver=null;//ファイルの受取オブジェクト /** ファイルがドロップされるComponentとファイルの受取オブジェクトを指定して管理者を生成 */ public DnDFileManager(Component target,Receiver receiver) { //ComponentにドロップしようとするときDropTargetListenerにイヴェントを投げるオブジェクトを生成 //DnDConstants.ACTION_COPY_OR_MOVEは受け入れるオプションの種類 //ここでは管理者自身(this)がDropTargetListenerとする。 //最後のtrueは現在受け入れ中で生成することを示す new DropTarget(target,DnDConstants.ACTION_COPY_OR_MOVE,this,true); //管理者がファイルを送る相手の設定 this.receiver=receiver; } //////////////////////////////////////////////////////// // 以下はDropTargetListenerの実装部分 public void dragOver(DropTargetDragEvent e) {} public void dropActionChanged(DropTargetDragEvent e) {} public void dragExit(DropTargetEvent e) {} /** ドラッグ中のカーソルがComponentに入るとCOPY_OR_MOVEの形に変形 */ public void dragEnter(DropTargetDragEvent e) { e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); } /** ドロップの処理 */ public void drop(DropTargetDropEvent event) { //ドラッグのモードがACTION_COPYまたはMOVEの場合のみ処理 switch (event.getDropAction()) { case DnDConstants.ACTION_COPY: event.acceptDrop(DnDConstants.ACTION_COPY); break; case DnDConstants.ACTION_MOVE: event.acceptDrop(DnDConstants.ACTION_MOVE); break; default: event.dropComplete(false);//上記以外のドロップは受け取らない return; } //文字列とか画像データとか色々なものがドラッグ&ドロップ可能だが //ファイルのドロップのみを受け取る try { //ドラッグしているものの情報を貰う Transferable content = event.getTransferable(); // if(content.isDataFlavorSupported(DataFlavor.javaFileListFlavor)){//ファイルリストなら //ファイルリストを受け取る //ここは型変換を行うので,コンパイルで警告が出ます List<File> filelist = (List<File>)content.getTransferData(DataFlavor.javaFileListFlavor); //ファイルを1つづつReceiverへ渡す for(File file:filelist) { receiver.receive(file); } event.dropComplete(true);//ドロップ完了 }else{//ファイル以外なら event.dropComplete(false);//ドロップを受け取らない } }catch(Exception e){ e.printStackTrace(); event.dropComplete(false);//例外が起きた時はドロップを受け取らない } } }
ドラッグ&ドロップされたテキストファイルの名前をリストアップするだけのサンプルです。繁雑な部分を上記の管理者に依頼することで,サンプル側はシンプルになるのが分ると思います。
import java.awt.*; import java.io.*; import javax.swing.*; /** JTextAreaにドラッグ&ドロップされたファイルの名前を表示するサンプル ファイル受け取りのためにインターフェイスDnDFileManager.Receiverを実装する */ public class DnDSample extends JFrame implements DnDFileManager.Receiver { static public void main(String args[]) { JFrame window=new DnDSample(); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.setSize(300,200); window.setVisible(true); } JTextArea logArea=null; public DnDSample() { super("DnDSample");//アプリの名前を画面のタイトルに表示 logArea=new JTextArea();//ファイル名を書き出すJTextAreaを生成しlogAreaとする logArea.setEditable(false);//logAreaの外部からの編集を不許可に //logAreaにドラッグ&ドロップされたファイルをthis(自分)へ送る管理者を作成 new DnDFileManager(logArea,this); JScrollPane pane=new JScrollPane(logArea);//logAreaをスクロール枠に入れる Container container=getContentPane();//コンテナを取り出して container.add(pane,BorderLayout.CENTER);//取り出したコンテナにスクロール枠を入れる } /** DnDFileManager.Receiverを実装*/ public void receive(File file) { //サンプルなので受け取ったファイルの名前をlogAreaに追記するだけ logArea.append(file.getPath()+"\n"); return; } }
ここではプロセスを作り並列で別のアプリケーションプログラムを動かす例を紹介します。javaのコンパイルなどもプロセスとして実行できます。
別のプログラムを起動するだけでいい,例えばランチャー(launcher)ならこれで十分です。
プロセスを起動し放置して終了を待つような使い方なら比較的簡単です。
ここで注意しなければいけない問題が有ります
import javax.swing.*; import java.awt.*; import java.awt.event.*; /** プロセスを起動し放置して終了を待つような使い方の例 プロセスとの会話的な処理を行うのは,このままでは難しい */ public class Runner implements Runnable { //----------テスト用のプログラムの始端------------------- // Runnerをテストするmain関数 static public void main(String arg[]) { JFrame test=new Test(); test.setSize(300,200); test.setVisible(true); } //GUIを使ってテストするためのTestクラス static class Test extends JFrame implements ActionListener, Runner.Listener { // JButton button; JTextField command; JLabel exit; Runner runner=null; /** Testのコンストラクタ 画面の部品配置など */ Test() { setLayout(new GridLayout(3,1)); // button=new JButton("START"); button.addActionListener(this); add(button); //コマンドを表示するテキストフィールド command=new JTextField("notepad.exe"); add(command); //プロセスの戻り値を表示するラベルy exit=new JLabel("exitValue"); add(exit); //×ボタンで終了 setDefaultCloseOperation(EXIT_ON_CLOSE); } /** ActionListenerの実装 */ public void actionPerformed(ActionEvent e) { String s=button.getText(); if(s.equals("START")){ //コマンドラインは,コマンドと各引数を文字配列に分割する必要がある。 //ここでは簡便にスペースでコマンドラインを分割した。 runner=new Runner(this, command.getText().split(" ")); runner.start(); button.setText("STOP"); }else if(s.equals("STOP")){ runner.stop();//強制終了 button.setText("START"); } } /** Runner.Listenerの実装 */ public void itCompletes(int v) { button.setText("START"); exit.setText("exitValue="+v); } } //----------テスト用のプログラムの終端------------------- /////////////////////////////////////////////////////////////// // ここからRunnerのプログラム /////////////////////////////////////////////////////////////// /** Runnerからプロセス終了の通知を受けるためのインター^フェース */ static interface Listener { public void itCompletes(int v);//プロセスの戻り値が渡される。 } //インスタンスの変数 ProcessBuilder pb=null; Process process=null; Thread thread=null; Listener listener=null; /** コンストラクタ 渡すコマンドラインは,引数にスペースを含を含まないこと。 */ public Runner(Listener listener,String command[]) { this.listener=listener; //コマンドを設定 pb = new ProcessBuilder(command); //ProcessBuilderには引数を含むコマンドラインを直接渡せない。 //プロセスの入出力を放置すると入出力が詰まってプロセスが停止する //そこで,プロセスの入出力を親の入出力に統合 pb.inheritIO(); //ProcessBuilderを使えば,ファイルへのリダイレクトを設定することも可能。 //ProcessBuilderはリダイレクトを行いたい場合にむしろ使いやすいようだ? } /**別スレッドでプロセスを起動する*/ public void start() { if(thread==null){ thread=new Thread(this); thread.start(); } } /**プロセスを強制終了する*/ public void stop(){quit();}//下記quitの別名として作った public void quit() { if(process!=null){ process.destroy(); } } /** 別スレッドで実行するのでRunnerを使う側は終了を待つあいだに別のことができる 終了すると使う側のListenerへ報告する。 */ public void run() { try{ process=pb.start();//プロセスの開始 process.waitFor();//終了まで,このスレッドは待ち状態に }catch(Exception e){ e.printStackTrace(); quit();//例外が起きたら強制終了 }finally{ if(process!=null){ if(listener!=null) //終了をListenerに報告 listener.itCompletes(process.exitValue()); } process=null; thread=null; } } }
この場合,起動したプロセスとのデータ交換が必要になります。通信を使うのも一つの方法ですが,ここではプロセスの標準入出力をアプリと繋ぐ方法を示します。
プログラムが大きくなるので,このページには重要な部分のみを説明することにする。詳細はソースプログラム(SubProcess.java)を見てください。
Processには,次のようなメソッドが用意されている。
※相手の出力が自分の入力になるので,名前の対応には注意
これを用いてアプリ側で使えるストリームを作る。しかし文字コードの問題には注意が必要だ。日本版Windowsなら「Windows-31J」からユニコードへの変換が必要になる。さらに,バッファ機能を加えてWriterとReaderを作ると,次のようなプログラムになる。
Process process が実行中のプロセスの場合 //プロセスの標準入力へ書き出す窓口 BufferedWriter stdin = new BufferedWriter( new OutputStreamWriter(process.getInputStream(),"Windows-31J") ); //プロセスの標準出力を読み込む窓口 BufferedReader stdout = new BufferedReader( new InputStreamReader(process.getOutputStream(),"Windows-31J") ); //プロセスの標準エラー出力を読み込む窓口 BufferedReader stderr = new BufferedReader( new InputStreamReader(process.getErrorStream(),"Windows-31J") );
プロセスの出力ストリームとデータ交換を行うとき,ストリームがデータで溢れないようにしなければならない。ストリームが詰まるとプロセスが止まるかもしれない。あるいは,受け取りが遅れて,データを取りこぼすかもしれない。
頻繁にストリームを監視しなければならないのでスレッドを用いた並列処理が欠かせない。プロセスの出力を受け取るReaderは,いつ来るか分からない出力を待ち受けるために別スレッドで頻繁にプロセスの出力をチェックすることにする。
protected class StreamReader implements Runnable { BufferedReader reader=null; Thread thread=null; int sleepTime=20;//[ms] int type=Listener.OTHER; InputStream in; String code="Windows-31J"; StreamReader(InputStream in,String code,int type) { this.type=type; this.in=in; this.code=code; start(); } boolean isReading() { if(reader!=null)return true; else return false; } void start() { if(thread==null){ thread=new Thread(this); thread.start(); } } void close(){stop();} void stop() { thread=null; try{ while(reader.ready())//読み終わるまで止まってはいけない Thread.sleep(sleepTime); }catch(Exception e){} } /**別スレッドで並列実行するメソッド*/ public void run() { try{ //Readerを作り reader=new BufferedReader(new InputStreamReader(in,code)); //文字列を読んでListenerび送る int length=0; char data[]=new char[256]; while(thread!=null || reader.ready()){//読み残しがある間は止まってはいけない。 while(reader.ready()){ length = reader.read(data,0,255); if(0<length){ talked(new String(data,0,length),type);//Listenerへサブプロセスの書き出し報告 } } //他のスレッドに時間を提供 try{ Thread.sleep(sleepTime); }catch(InterruptedException e){} } }catch(IOException ioe){ ioe.printStackTrace(); }finally{ //ストリームの閉鎖処理 if(reader!=null){ try{ reader.close(); }catch(IOException ioe){ ioe.printStackTrace(); } } reader=null; thread=null;//例外の場合もnullにしておく } } }
例題のプログラムではRunner.javaで使ったProcessBiluderではなくて,Runtimeを使ってプロセスを起動している。これはコマンドラインをそのままプロセスの起動に使えうのが容易なためです。
public void run(){ try{ //プロセスの起動 if(commandLine!=null){//単一行のコマンドを文字列で渡す process=Runtime.getRuntime().exec(commandLine,envArray,currentDir); }else if(processBuilder != null){//コマンドラインの引数を区切って渡す場合 process=processBuilder.start(); } started();//Listenerへサブプロセスの開始報告 //サブプロセスの標準入出力との担当窓口を内部クラスで生成 //変数名はサブプロセス側からみた名前にしている stdin=this.new StreamWriter(process.getOutputStream(),"Windows-31J", Listener.STDIN); stdout=this.new StreamReader(process.getInputStream(),"Windows-31J", Listener.STDOUT); stderr=this.new StreamReader(process.getErrorStream(), "Windows-31J",Listener.STDERR); //終わるまで待つ while(true){//無限ループ try{ Thread.sleep(sleepTime);//InterruptedExceptionの発生があり得る if(process!=null) exitValue=process.exitValue();//プロセス実行中は例外を発生 break;//例外が発生しなければ,ループから出る }catch(InterruptedException e){} catch(IllegalThreadStateException e){}//例外が起きればここへ跳んでbreakは実行されずループ } }catch(Exception ex){ ex.printStackTrace(); }finally{//プロセスが終わって,ここから後始末 if(stdin!=null)stdin.close(); if(stdout!=null)stdout.close();//出力を残さず読んでから閉じることが必要 if(stderr!=null)stderr.close();//出力を残さず読んでから閉じることが必要 try{ if(process!=null) exitValue=process.exitValue(); else exitValue=-1; }catch(Exception ex){ exitValue=-1; } ended(exitValue);//Listenerへサブプロセスの終了報告 process=null; thread=null; } }
プロセスの実行をOSに指示する手法の理解が必要。ここでは,OSをWindowsと仮定して,
cmd /c dir
でカレントフォルダのファイルリストを表示する例題になっている。
cmdはコマンドプロンプトのアプリケーションプログラムcmd.exeの起動を
/c cmdの第一引数で,cmd実行終了で閉じることを指示するオプション (/kにすると閉じない)
dir cmdの第二引数で,コマンドプロンプトでdirの実行を指示している。
javaのソースをコンパイルすると複数のクラスファイルが作られます。
javaの仮想マシンに起動するクラスファイルを示して実行することで,必要な時に他のクラスファイルも読み込んでプログラムが実行されます。
ここで,クラスファイルが幾つもあって,まとまっていないことが煩わしい
そこでクラスファイルをまとめ,起動するクラスの情報を加えて,javaの仮想マシンで実行可能な1つのファイルにまとめることができるようにしました。このまとめたファイルをJAR形式のファイルと言います。
どのクラスが起動クラスなのかを記述したマニフェストファイルを用意します。下記の赤字の部分がクラス名。
注意:最後は改行で終わること
Manifest-Version: 1.0
Main-Class: SubProcess
SubProcess.javaとMANIFEST.MFファイルがカレントフォルダにあり,そこで以下のようなバッチプログラムを実行することでjarファイルを作ることができます。
rem 新しくbinフォルダを作り,その中にコンパイルしたクラスファイルを作ります。 rem binファイルの中のクラスファイルを纏めてjarファイルを作ります。 echo off mkdir bin if errorlevel 1 goto END1 echo binフォルダを作成しました javac -Xlint:unchecked -d bin -deprecation SubProcess.java if errorlevel 1 goto END2 echo binフォルダにクラスファイルを作成しました jar cmf MANIFEST.MF SubProcess.jar -C bin\ . if errorlevel 1 goto END3 echo JARファイルを作成しました。 java -jar SubProcess.jar goto END :END1 echo すでにbinフォルダがありますbinフォルダを削除してください goto END :END2 echo コンパイルに失敗しました goto END :END3 echo JARファイル作成に失敗しました goto END :END pause
上のバッチで作成したjarファイルです。
javaは豊富なクラスライブラリーを用意しています。チュートリアルも豊富なので色々なことに使ってみてください。このとき大事なのは継承とか実装,クラスとインスタンスのようなオブジェクト指向言語の基礎知識です。
APIから自分で使えるクラスを探せるようになれば,たいていのことはできるはずです。