Java IPアドレスの取得方法

「いいNICの日」ということで、JavaでのNIC取得から
IPアドレスの取得方法について少し書いておきます。

Javaでは、まず使用するネットワークインタフェースを定めます
例えばイーサネットカードが2枚刺さっている場合、どちらのイーサネットカードのアドレスを取得するのか決めなければいけません。
取得方法は以下の通り。

for(Enumeration<NetworkInterface> nie = NetworkInterface.getNetworkInterfaces(); nie.hasMoreElements();){
     NetworkInterface ni = nie.nextElement();
     ......
}

NetworkInterfaceクラスのgetNetworkInterfacesメソッドを呼び出すと、使用できるNetwork Interfaceの一覧が返ってきます。
このメソッド、Genericsには対応しているくせにIterableではないEnumerationインスタンスが返って来るので、
Java 5以降で使えるようになったfor each構文ではなく、普通のfor文若しくはwhile文で回して取得してください。
Concurrencyに行うためなのかな・・・。早くこのメソッドは直して欲しい・・・。


愚痴が多くなりましたが、これでNetworkIntefaceを取得できました。
次にIPアドレスの取得です。
NetworkInterfaceには複数のアドレスが付いている場合があります。(v4とv6とかリンクローカルとか)
なので、それのどれを使うかを指定しないといけません。

     ....
     for(Enumeration<InetAddress> iae = ni.getInetAddresses(); ia.hasMoreElements();){
     InetAddress ia = iae.nextElement();
     .....

という感じに、こいつも何故かEnumerationを返すので、for文かwhile文を使ってください。
これでアドレスの取得は終了です。
が、問題はアプリケーションで使用するアドレスをこの一覧の中からどうやって決めるか、ということです。

  • 対処法その1:引数による指定

例えば引数なしで起動したときにIPアドレスに番号を付けて、次の起動時に指定させる・・・といった方法は、次の起動のタイミングでIPアドレスが変わったとか、アドレスが増えたとかいう時に予期せぬアドレスを指定しかねません。
対処法としては例えば引数無しで起動したときにInetAddressのハッシュ値を表示させ、引数の値と先頭一致させる方法が考えられます。
もしくは引数で直接アドレスかドメイン名を指定させる方法もありますが、
それなら最初から指定させておけば良いです。
後、コマンドによる実行が前提となるので、若干めんどくさいです。

  • 対処法その2:System.inもしくはGUIによる選択

取得した情報をSystem.outに出力し、System.inから入力を受け付けて選択する方法。
この場合もプログラムをコマンドライン等から入力する必要があります。
ということで、GUIが使える環境であれば、GUIを開いてユーザに選択してもらうのが一番手っ取り早く済みます。

最後に、私がよく使っているアドレス選択GUI画面のソースコードを以下に載せておきます。
使い方はnewして、addEndTaskにアドレス決定時の処理(Funcインタフェースを実装)して、openGUIでGUIを開きます。
無駄にConcurrentなクラスを使っていますが、あんまり意味はありません。
これと引数指定、もしくはConfigファイルによる指定の併用が割と便利だったりします。

public class SelectNetAddress {

	private final CopyOnWriteArrayList<AddressListElement> addressList;
	private final ConcurrentHashMap<Integer, ListDataListener> listeners;
	private final AtomicReference<AddressListElement> selectedAddr;
	private final CopyOnWriteArrayList<Func> taskList;

	public SelectNetAddress() throws SocketException {
		selectedAddr = new AtomicReference<AddressListElement>();
		addressList = new CopyOnWriteArrayList<AddressListElement>();
		taskList = new CopyOnWriteArrayList<Func>();
		listeners = new ConcurrentHashMap<Integer, ListDataListener>();
		
		for (Enumeration<NetworkInterface> nie = NetworkInterface.getNetworkInterfaces(); nie.hasMoreElements();) {
			NetworkInterface ni = nie.nextElement();
			for (Enumeration<InetAddress> ia = ni.getInetAddresses(); ia.hasMoreElements();) {
				AddressListElement e = new AddressListElement(ni, ia.nextElement());
				addressList.add(e);
			}
		}

	}

	public void openGUI() {
		final JList<AddressListElement> jlist = new JList<>(new ListModel<AddressListElement>() {
			@Override
			public void addListDataListener(ListDataListener l) {
				listeners.put(l.hashCode(), l);
			}

			@Override
			public void removeListDataListener(ListDataListener l) {
				listeners.remove(l.hashCode());
			}

			@Override
			public int getSize() {
				return addressList.size();
			}

			@Override
			public AddressListElement getElementAt(int index) {
				return addressList.get(index);
			}
		});

		final JFrame frame = new JFrame("Address Selector");
		frame.setSize(640, 400);
		frame.setLocation(100, 100);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		SpringLayout sl = new SpringLayout();
		Container cp = frame.getContentPane();
		cp.setLayout(sl);
		cp.add(jlist);

		final JButton button = new JButton("Select");
		cp.add(button);
		button.setEnabled(false);

		jlist.addListSelectionListener(new ListSelectionListener() {
			@Override
			public void valueChanged(ListSelectionEvent e) {
				button.setEnabled(true);
			}
		});

		button.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				Object obj = jlist.getSelectedValue();
				if (obj instanceof AddressListElement) {
					AddressListElement el = (AddressListElement) obj;
					selectedAddr.set(el);
					frame.setVisible(false);
					frame.dispose();
					for (Func t : taskList) {
						t.func(getAddress());
					}
				}
			}
		});

		sl.putConstraint(SpringLayout.NORTH, jlist, 10, SpringLayout.NORTH, cp);
		sl.putConstraint(SpringLayout.WEST, jlist, 10, SpringLayout.WEST, cp);
		sl.putConstraint(SpringLayout.EAST, jlist, -10, SpringLayout.EAST, cp);

		sl.putConstraint(SpringLayout.SOUTH, jlist, -10, SpringLayout.NORTH, button);

		sl.putConstraint(SpringLayout.SOUTH, button, -10, SpringLayout.SOUTH, cp);
		sl.putConstraint(SpringLayout.WEST, button, 10, SpringLayout.WEST, cp);
		sl.putConstraint(SpringLayout.EAST, button, -10, SpringLayout.EAST, cp);

		frame.setVisible(true);
	}

	public void addEndTask(Func t) {
		taskList.add(t);
	}

	public InetAddress getAddress() {
		return selectedAddr.get().getAddr();
	}
	
	
	public interface Func {
		public void func(InetAddress ia);
	}
}

Java 7 NIO2 Asynchronous系の使い方

検索したけどあんまりちゃんとした情報を誰も載せてない気がしたので、
忘れないうちにまとめておきます。

まずはシンプルなServerの作り方。

System.out.println("Start");

try (AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();) {
	server.bind(new InetSocketAddress(12345));
	Future<AsynchronousSocketChannel> sock = server.accept();

	try (AsynchronousSocketChannel ch = sock.get();) {
		ByteBuffer buf = ByteBuffer.allocate(1024);
		Future<Integer> result = ch.read(buf);
		buf.flip();
		Integer i = result.get();
		System.out.println("Read :"+i);
		System.out.println(Charset.defaultCharset().decode(buf));
	}
} catch (IOException e) {
	e.printStackTrace();
} catch (ExecutionException e) {
	e.printStackTrace();
} catch (InterruptedException e) {
	e.printStackTrace();
}

普通のServerSocketと違うところはacceptの返り値がFuture型なところ。
acceptメソッドではブロックしないで、Future型を返す。
なので、Asynchronousの名前が付いているということ、でしょう。
このプログラムではとりあえずsock.get()を呼び出してそこでブロック。
getメソッドにはタイムアウトを指定できるのでスレッドを他の処理に回したい人はタイムアウトさせてください。
もっとも、そんな使い方するのならそもそもこのFuture型を使った実装は向いていない気がするので後述する方法を使った方がすっきりします。


で、get()の返り値としてAsynchronousSocketChannelが返って来るので、それを使ってクライアントと通信。
readメソッドを呼び出すと同様にFuture型が返って来るので同じようにgetメソッドの呼び出しでブロック。
後はsysoして終わり。
ブラウザ等でlocalhost:12345にアクセスするとHTTPのリクエストが見えるはずです。
この実装ではそれを表示して終わり。サーバー的に使うのであればWhileループさせればいいんだけど、
誰かのをReadしている間はAcceptできないので、Read部分はスレッドにするのが普通です。


ちなみに、SocketのOptionを指定するにはJava 7から下記のような方法がサポートされました。

server.setOption(StandardSocketOptions.SO_REUSEADDR, true);

setOptionメソッドではSocketOptionと、そのT型の引数を受け取ります。
普通のOptionはStandardSocketOptionsに入ってるので事足ります。
SO_REUSEADDRの場合はBooleanのGenericsが指定されているので、第2引数にBoolean以外を入れるとエラーになります。
プログラム中でいろんな引数を指定したいときはこの書き方が便利ですね。


このFutureを使った呼び出し方は、これまでの実装に配慮して、簡単に置換できるように用意されたんではないかなぁ、と。
Java 7でこれをやる意味はあんまり無いと思います。
ということで、次はCompletionHandlerを使ったサーバー。

public class AsyncServer {
	
	public class SimpleHandler implements CompletionHandler<AsynchronousSocketChannel, Integer> {
		@Override
		public void completed(AsynchronousSocketChannel result, Integer attachment) {
			server.accept(0, handler);
			ByteBuffer buf = ByteBuffer.allocate(1024);
			Future<Integer> f = result.read(buf);
			try {
				int i = f.get();
				buf.flip();
				System.out.println(Charset.defaultCharset().decode(buf));
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ExecutionException e) {
				e.printStackTrace();
			}
		}
		
		@Override
		public void failed(Throwable exc, Integer attachment) {
			
		}
	}
	
	private final AsynchronousServerSocketChannel server;
	private final SimpleHandler handler;
	
	public AsyncServer() throws IOException {
		server = AsynchronousServerSocketChannel.open();
		handler = new SimpleHandler();
	}
	
	public void start(){
		try {
			server.bind(new InetSocketAddress(12345));
			server.accept(0, handler);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) throws IOException {
		AsyncServer server = new AsyncServer();
		server.start();
		
		while(true);
	}
}

簡単に言うと、acceptするときに成功した場合と失敗した場合の処理を登録しておくのがCompletionHandler型を使った方法です。
最初に宣言されているインナクラスはaccept時に使うCompletionHandlerを実装しています。
Genericsの最初はさっきのFutureでいうところの返り値、二つ目は添付するObjectの型です。(Selectorを使ったときのattachと同じ使い方)
completedメソッドが成功したときに実行されるメソッド、failedが失敗したときに実行されるメソッドです。
completedメソッドは次のhandlerを登録しておいて、Acceptしたときの処理に進みます。
この例ではFuture型を使って最初の例と同様に表示して終わり。(Closeしてないけど)
当然、このreadメソッドもCompletionHandlerを使って書けます。

実行するには、acceptするときにattachするオブジェクトとCompletionHandlerを指定すれば、
後は勝手に指定したポートでacceptしたときにHandlerが呼ばれてスレッドによる処理が行われます。
このようにCompletionHandlerを使えば、acceptからreadまでブロックすることなく割と理解しやすい形で書けるのがAsynchronous系の特徴かもしれません。
ここで注意なのが、あくまでacceptしたときの処理を記述しただけなので、他に何も動いていなければJavaはプログラムを終了します。
JavaGUIなり他の監視スレッドなりが動いている必要があるので、このプログラムでは最後にwhile(true)とかしてます。


ちなみに、もっと直感的に書きたい人は匿名インナクラスとか使って下記のように書くのが普通かな、と。

	public static void main(String[] args) throws IOException {
		final AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
		server.bind(new InetSocketAddress(12345));
		server.accept(0, new CompletionHandler<AsynchronousSocketChannel, Integer>() {
			@Override
			public void completed(AsynchronousSocketChannel result, Integer attachment) {
				// completed
				server.accept(0, this);
			}
			
			@Override
			public void failed(Throwable exc, Integer attachment) {
				// failed
			}
		});
		
		while(true);
	}

とてもシンプルですね!
匿名インナクラス内で使いたいメンバにはfinalを付けるか、ちゃんと指定可能なようにしましょう。


CompletionHandlerを使うときに一つ注意しないといけないのは
CompletionHandlerの中身はマルチスレッドによる処理がなされるところです。
つまり、メソッドはスレッドセーフに書かないといけません。
仮にacceptした数をカウントしようとしてこんなソースを書いたらダメです。

	public class SimpleHandler implements CompletionHandler<AsynchronousSocketChannel, Integer> {
		private int i = 0;

		@Override
		public void completed(AsynchronousSocketChannel result, Integer attachment) {
			server.accept(0, handler);
			i++;

i++の実行はスレッドセーフではないので、複数のスレッドが走る可能性がある部分に書いてはいけません。
ついでにiの宣言にfinalもvolatileも付いてないので全然ダメです。
カウントしたい人はAtomicIntegerを使いましょう。


最後に、プログラム中でExecutorServiceを既に使っていて、スレッドをちゃんと管理したいという人はChannelを開くときにGroupを指定できます。

		ExecutorService service = Executors.newCachedThreadPool();
		AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(service);
		AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group);

AsynchronousChannelGroupの作り方は他にもいろいろあるのでAPIを参考にしてください。


以上でAsynchronous系の使い方はだいたい終わりです。
個人的にはSelectorを使う方法よりかなり使いやすくなったなぁ、と。
この例ではacceptとreadだけだけど、当然writeやconnectもAsynchronousに実行できます。
Selector使うとその辺りまで厳密にNonBlockingに書くのはめんどくさかったので、結構助かります。


ただ、問題は、「Asynchronous」ってのが長すぎる!
Eclipse前提だとしても長すぎる。上記のgroup指定するコードなんてぱっと見がカオス状態。
Asynchronous → Asyncにするだけでかなり違うのになぁ。
まぁ、Javaに今更そんなこと言っても仕方ないですね。

Smart.fmの使い方

だんだんSmart.fmの使い方が分かってきたので、
このへんでまとめておきます。
とはいえ、この使い方は勝手に自分が解釈して使っているだけなので、
本来の使い方とは違うかもしれない。

とにかく数をこなす

基礎英語1から基礎英語10まで頑張る。
流石に基礎英語1は簡単すぎるというレベルですらないが、
もしもDictationを一発でクリアすることができなければやる価値がある。
どんなに長い文章でも平易な文章だと一発でクリアできるはず。誰でも。
そう考えると、使われている単語や意味が分かっても、
Dictationを一発でクリアできないのであればそれは構文か何かが自分にとって苦手の証。
勝手にそう思って、とにかくやりまくる。
そうするとサクサク進める方法とかもわかってくるので、意外とすんなり終わる。
iKnowは単語ドリルを全部終えてから。
100個ぐらいは1時間もかからないから、さっさと単語ドリルを終わらせる。
全部を学習中にするだけなら1日もいらないぐらい。
分からない単語は後のiKnowに任せるとして単語ドリルをひたすら消化する。
Dictationとか慣れるとすぐに100%になる。
なんか勿体無い気もするけれど気にしない。とにかく消費する。
iKnowは思い出したときにやればいいぐらいでいい。
MasterにするにはiKnowの性質上時間がかかるので放置。
とにかく数をこなして数字的な達成感を得る方がいいと思う。

分からない時はYesを押さない

数をこなすついでに、自分にとって学習になるコンテンツをこなす。
目安は10個に1個は聞いたことがあるけれど意味がすぐに出てこない単語があるコース。
で、パッと見たときに意味が出てこない、なんとなくのニュアンスしか出てこないのであればYesを押してはいけない。
完璧にわかる!確実に!って時だけYesを押す。
そうしないと、日本語例の中から「あー、これこれ」って感じで見つけてしまう。
こうやってなんとなく覚えてしまった単語はDictationの時に困るので、最初からきっちり学ぶことが大事。
iKnowは反復期間を素晴らしく設定してくれるので、悔しいぐらい忘れそうなタイミングで提示してくれる。
こうやって単語を見ただけで意味やニュアンスが即座にわかるようになるまでYesを押さない。
下手すると2個前に出たのに忘れてる時があるけれど、正直にNoを押しましょう。

難しいコースはしない

オバマ大統領の演説だとか、ビジネスに欠かせない英語表現だとか、そういうのはかなり後になってから。もう、TOEIC850以上を目指すコースのDictationが簡単すぎて常にスキルレベルが80以上だぜーってなったらやるぐらいでいいと思う。
息抜きにやってもいいけど。
でも数が多くなると疲れるので、気の迷いで登録してしまったコースはさっさと消す。

成長しないことを悲観しない

Dictationのスキルレベルは簡単に上がらないが、これは上がらない。
上げたかったら瞬間英作文とかやる方がいいと思う。
Dicatationは英語に触れる機会を増やしているぐらいの認識でいい。
Smart.fmはとにかく語彙力をつけることをメインにした方が割り切れていいと思う。
後、なんとなくの意味しかわかっていない単語の強化。
基礎力のUP.
サッカーでいうと、パスのセンスとかシュートの速さとかディフェンスのセンスとかを磨くのではなく、ただの筋トレ。
後、リフティング、ドリブル、インサイドパス的なことかな。
もちろん、試合でレギュラーを勝ち取ることはできないだろうけれど、
そういう人の練習相手にはなれるわけで。
過度な期待はしない。TOEICも上がるわけではない。他のことをしないと上がらない。けれど、基礎力の底上げには確実に貢献する。


とりあえずこんな感じかなぁ。
もしかしたらTOEICのスコアは上がるかもしれないけど、
やっぱりスピーキングの部分は弱いままだと思う。
後は瞬間的に英作文できる能力は実用で絶対必要なので、それはそれでトレーニングしないといけないかな。

クラウドって何?

ITpro「クラウドという技術はない」
http://itpro.nikkeibp.co.jp/article/Watcher/20091218/342382/

まさしく思ってることを全て語ってくれたような内容.
今のところクラウドっていうのは思想であって技術ではない.
挙げられているような4つの技術は方向性としてクラウドであり,
クラウドを構成する技術ではあるけれども,それらは昔からあった技術の延長に過ぎない.
技術の伸びている方向がクラウドであって,エンハンスしている気もするし,そもそも社会活動的に必然である方向性に「クラウド」って名前を付けただけなのかもしれない.
名前を付けることで伸びが早くなることもあるから,それはそれで必要かも知れんけど,盲目的になりすぎるとクラウドを追い越して明後日の方向に行ってしまう気はする.
で,個人的にはそれらを更に包含する「ユビキタス」っていう哲学があると思う.
でもクラウドユビキタスって微妙に目指してる方向が違う気もするな.
やっぱ名前って意味ないかもね.

Google Public DNSについて

http://hen.kooss.com/archive/2009/1204001.html

Pingで評価してるけど,Pingっていちいち名前引かないんじゃ・・・?
現にアドレスに変換されてるし,そうしないとPing計測の意味が無いような.

DNSサーバにPingしたのならわかるけど,
それならどう考えてもISPDNSサーバの方が早いし.
試しにやってみたけど,
Google DNS = 41ms
学内DNS < 1ms
って,学内だからそりゃそうですよねー.
この40msの差を埋められるほど早くなるんだろうか.
というかその差って有意なのか?


で,Google DNSが早くなる理由は
http://code.google.com/intl/ja/speed/public-dns/docs/performance.html
に書かれてますが,(まだほとんど読んでないけど)
そもそもChromeとか最近のFirefoxってDNSプリフェッチするからあんまり効果ないんじゃ・・・.
たぶん,Chromeとか作ってた人が
DNSってなんてダメダメなんだ!俺が作ってやる!」
っていう20%ルール的に作ったんじゃないのかな.
とはいえ,弱小ISPかつIEしか使っちゃダメっていう環境下で,
DNSの設定変更は許されているダメダメポリシー環境下だと,
確かに早くはなるかもしれないかな.8.8.8.8っていうのも覚えやすいし.


それよりも,一番衝撃なのはGoogleDNSまで支配しようと考えているってことかな.
DNSのリクエストからトレンドとか見るつもりなんじゃないかな.
DNS=インターネットといっても過言ではない気がするわけで,
いよいよGoogleによる世界征服が始まる?かも?


追記:
そもそもPingを打った先がラウンドロビンで飛ばされたアドレスなので,
Google DNSがネットワーク距離の近いアドレスを返すとしたら
Pingが早くなることもあるかもね.
でもそうなるとどうやってネットワーク距離を測ってるのかなぁ.単にアドレスの近似性とかだろうか.
後,DNSプリフェッチしてもページの読み込み時にコンテンツの名前とか引くからウェブページ見るときに早くなることはあるのか,な?

追記の追記:
仮定が正しいかどうかコマンド実行してみた.
nslookup www.google.com 8.8.8.8
ってやって,毎回結果が異なるかどうか測定.
結果:サーバーの名前が変わる・・・?
アドレスは8.8.8.8だけどサーバーの名前が変わりますなぁ.なんかやってるんだろうなぁ.
で,肝心の結果は普通にラウンドロビンっぽいですねぇ.
ますます深まる謎.

優れたジャーナリズム

優れたジャーナリズムには金がかかると思う.
けれど,「だから金を出してください」というと,なかなか出てこない.
視聴率はバラエティの方が高いし,宣伝効果もそっちの方が高かったりするわけで.(例外はあるだろうけど)
いわゆる「思想は低きに流れる」というのがある限り,
優れたジャーナリズムが大衆に支持されるとは思えない.
官僚主義的な世の中であれば優れたジャーナリズムにお金が支払われるだろうけれど,
民主主義的な世の中で優れたジャーナリズムに価値がないってのは
もう遙か太古の昔のギリシャ時代とかの衆愚政治で分かっているわけで.
てか,そもそも優れたジャーナリズムってなんなんだろうね.
パパラッチの中にも社会正義的ジャーナリズムの心を持ってる奴はいるかもしれないし,
もしかしたらパパラッチが必要悪の役割を果たしているのかも知れず.
世の中を平和にするジャーナリストが優れているという意見もあれば,
既存の利権構造を破壊するジャーナリストが優れているという意見もあるだろうし.
オオカミとブタではそもそも幸福の価値観が異なるだろうしねぇ.
やっぱ民主主義的に多数決を取るのが一番文句が少なくていいのかなぁ.
違うと思うけどなぁ.